From b5894295465be136fbcffe5d3a1f864d864ee2da Mon Sep 17 00:00:00 2001 From: Veddy Date: Wed, 10 Jan 2024 16:21:38 +0800 Subject: [PATCH 01/71] MG-2051 - Fixed unable to reset user password --- users/service.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/users/service.go b/users/service.go index e40ce0aade..2db117e7e9 100644 --- a/users/service.go +++ b/users/service.go @@ -317,12 +317,13 @@ func (svc service) ResetSecret(ctx context.Context, resetToken, secret string) e return err } c = mgclients.Client{ + ID: id, Credentials: mgclients.Credentials{ - Identity: c.Credentials.Identity, - Secret: secret, + Secret: secret, }, UpdatedAt: time.Now(), UpdatedBy: id, + Status: c.Status, } if _, err := svc.clients.UpdateSecret(ctx, c); err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) From f0e7989bd1368acebda9b02d189af422668d44fe Mon Sep 17 00:00:00 2001 From: Veddy Date: Thu, 11 Jan 2024 11:46:27 +0800 Subject: [PATCH 02/71] MG-2051 - Reset secret tests --- users/service_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/users/service_test.go b/users/service_test.go index 5c0e48731d..3edca63655 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -1451,6 +1451,70 @@ func TestRefreshToken(t *testing.T) { } } +func TestResetSecret(t *testing.T) { + cRepo := new(mocks.Repository) + auth := new(authmocks.Service) + e := mocks.NewEmailer() + svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + + cases := []struct { + desc string + resetToken string + secret string + client mgclients.Client + err error + }{ + { + desc: "reset secret with valid reset token for an existing client", + resetToken: validToken, + client: client, + secret: "newSecret", + err: nil, + }, + { + desc: "reset secret with valid reset token for an non-existing client", + resetToken: validToken, + client: mgclients.Client{}, + secret: "newSecret", + err: repoerr.ErrNotFound, + }, + { + desc: "reset secret with wrong password format for an existing client", + resetToken: validToken, + client: client, + secret: "secret", + err: errors.ErrAuthentication, + }, + { + desc: "reset secret with invalid reset token for an existing client", + resetToken: inValidToken, + client: client, + secret: "newSecret", + err: svcerr.ErrAuthentication, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: client.ID}, nil) + if tc.resetToken == inValidToken { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) + } + repoCall1 := cRepo.On("RetrieveByID", context.Background(), client.ID).Return(tc.client, tc.err) + repoCall2 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.client, tc.err) + err := svc.ResetSecret(context.Background(), tc.resetToken, tc.secret) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.client.ID) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "UpdateSecret", context.Background(), mock.Anything) + assert.True(t, ok, fmt.Sprintf("UpdateSecret was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + func clientsToUUIDs(clients []mgclients.Client) []string { ids := []string{} for _, c := range clients { From 293fc1bcb70b2c267374481402fa486d34b031a3 Mon Sep 17 00:00:00 2001 From: Veddy Date: Thu, 11 Jan 2024 16:24:08 +0800 Subject: [PATCH 03/71] MG-2051 - Reset secret tests --- users/service_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/users/service_test.go b/users/service_test.go index 3edca63655..0fd93e3d2d 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -29,6 +29,7 @@ var ( idProvider = uuid.New() phasher = hasher.New() secret = "strongsecret" + weakSecret = "secret" validCMetadata = mgclients.Metadata{"role": "client"} client = mgclients.Client{ ID: testsutil.GenerateUUID(&testing.T{}), @@ -1468,28 +1469,28 @@ func TestResetSecret(t *testing.T) { desc: "reset secret with valid reset token for an existing client", resetToken: validToken, client: client, - secret: "newSecret", + secret: secret, err: nil, }, { desc: "reset secret with valid reset token for an non-existing client", resetToken: validToken, client: mgclients.Client{}, - secret: "newSecret", + secret: secret, err: repoerr.ErrNotFound, }, { desc: "reset secret with wrong password format for an existing client", resetToken: validToken, client: client, - secret: "secret", + secret: weakSecret, err: errors.ErrAuthentication, }, { desc: "reset secret with invalid reset token for an existing client", resetToken: inValidToken, client: client, - secret: "newSecret", + secret: secret, err: svcerr.ErrAuthentication, }, } From ca49888f2aa2627c392d7e209259a80015b0d0b4 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:48:41 +0300 Subject: [PATCH 04/71] NOISSUE - Add group service tests (#241) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- internal/groups/mocks/groups.go | 107 -- internal/groups/service.go | 26 +- internal/groups/service_test.go | 2656 +++++++++++++++++++++++++++++++ internal/groups/status_test.go | 50 + pkg/groups/groups.go | 3 + pkg/groups/mocks/repository.go | 253 +++ pkg/groups/mocks/service.go | 311 ++++ pkg/sdk/go/channels_test.go | 26 +- pkg/sdk/go/groups_test.go | 32 +- pkg/sdk/go/things_test.go | 2 +- pkg/sdk/go/users_test.go | 2 +- things/service_test.go | 2 +- 12 files changed, 3314 insertions(+), 156 deletions(-) delete mode 100644 internal/groups/mocks/groups.go create mode 100644 internal/groups/status_test.go create mode 100644 pkg/groups/mocks/repository.go create mode 100644 pkg/groups/mocks/service.go diff --git a/internal/groups/mocks/groups.go b/internal/groups/mocks/groups.go deleted file mode 100644 index 3d8113b763..0000000000 --- a/internal/groups/mocks/groups.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mocks - -import ( - "context" - - mgclients "github.com/absmach/magistrala/pkg/clients" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - mggroups "github.com/absmach/magistrala/pkg/groups" - "github.com/stretchr/testify/mock" -) - -const WrongID = "wrongID" - -var _ mggroups.Repository = (*Repository)(nil) - -type Repository struct { - mock.Mock -} - -func (m *Repository) ChangeStatus(ctx context.Context, group mggroups.Group) (mggroups.Group, error) { - ret := m.Called(ctx, group) - - if group.ID == WrongID { - return mggroups.Group{}, repoerr.ErrNotFound - } - - if group.Status != mgclients.EnabledStatus && group.Status != mgclients.DisabledStatus { - return mggroups.Group{}, repoerr.ErrMalformedEntity - } - - return ret.Get(0).(mggroups.Group), ret.Error(1) -} - -func (m *Repository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, ids ...string) (mggroups.Page, error) { - ret := m.Called(ctx, gm) - - return ret.Get(0).(mggroups.Page), ret.Error(1) -} - -func (m *Repository) MembershipsByGroupIDs(ctx context.Context, gm mggroups.Page) (mggroups.Page, error) { - ret := m.Called(ctx, gm) - - return ret.Get(0).(mggroups.Page), ret.Error(1) -} - -func (m *Repository) RetrieveAll(ctx context.Context, gm mggroups.Page) (mggroups.Page, error) { - ret := m.Called(ctx, gm) - - return ret.Get(0).(mggroups.Page), ret.Error(1) -} - -func (m *Repository) RetrieveByID(ctx context.Context, id string) (mggroups.Group, error) { - ret := m.Called(ctx, id) - - if id == WrongID { - return mggroups.Group{}, repoerr.ErrNotFound - } - - return ret.Get(0).(mggroups.Group), ret.Error(1) -} - -func (m *Repository) Save(ctx context.Context, g mggroups.Group) (mggroups.Group, error) { - ret := m.Called(ctx, g) - - if g.Parent == WrongID { - return mggroups.Group{}, repoerr.ErrCreateEntity - } - - if g.Owner == WrongID { - return mggroups.Group{}, repoerr.ErrCreateEntity - } - - return g, ret.Error(1) -} - -func (m *Repository) Update(ctx context.Context, g mggroups.Group) (mggroups.Group, error) { - ret := m.Called(ctx, g) - - if g.ID == WrongID { - return mggroups.Group{}, repoerr.ErrNotFound - } - - return ret.Get(0).(mggroups.Group), ret.Error(1) -} - -func (m *Repository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - ret := m.Called(ctx, parentGroupID, groupIDs) - - return ret.Error(0) -} - -func (m *Repository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - ret := m.Called(ctx, parentGroupID, groupIDs) - - return ret.Error(0) -} - -func (m *Repository) Delete(ctx context.Context, groupID string) error { - ret := m.Called(ctx, groupID) - if groupID == WrongID { - return repoerr.ErrNotFound - } - return ret.Error(0) -} diff --git a/internal/groups/service.go b/internal/groups/service.go index 0af806f403..44297b73ba 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -104,7 +104,7 @@ func (svc service) CreateGroup(ctx context.Context, token, kind string, g groups }) } if _, err := svc.auth.AddPolicies(ctx, &policies); err != nil { - return groups.Group{}, err + return g, err } return g, nil @@ -125,14 +125,7 @@ func (svc service) ViewGroupPerms(ctx context.Context, token, id string) ([]stri return nil, err } - permissions, err := svc.listUserGroupPermission(ctx, res.GetId(), id) - if err != nil { - return nil, err - } - if len(permissions) == 0 { - return nil, errors.ErrAuthorization - } - return permissions, nil + return svc.listUserGroupPermission(ctx, res.GetId(), id) } func (svc service) ListGroups(ctx context.Context, token, memberKind, memberID string, gm groups.Page) (groups.Page, error) { @@ -215,12 +208,8 @@ func (svc service) ListGroups(ctx context.Context, token, memberKind, memberID s return groups.Page{}, err } default: - err := svc.checkSuperAdmin(ctx, res.GetUserId()) - switch { - case err == nil: - if res.GetDomainId() == "" { - return groups.Page{}, errors.ErrMalformedEntity - } + switch svc.checkSuperAdmin(ctx, res.GetUserId()) { + case nil: gm.PageMeta.OwnerID = res.GetDomainId() default: // If domain is disabled , then this authorization will fail for all non-admin domain users @@ -280,6 +269,9 @@ func (svc service) listUserGroupPermission(ctx context.Context, userID, groupID if err != nil { return []string{}, err } + if len(lp.GetPermissions()) == 0 { + return []string{}, errors.ErrAuthorization + } return lp.GetPermissions(), nil } @@ -505,7 +497,7 @@ func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID return svc.groups.AssignParentGroup(ctx, parentGroupID, groupIDs...) } -func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) error { +func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) (err error) { groupsPage, err := svc.groups.RetrieveByIDs(ctx, groups.Page{PageMeta: groups.PageMeta{Limit: 1<<63 - 1}}, groupIDs...) if err != nil { return errors.Wrap(errRetrieveGroups, err) @@ -517,7 +509,7 @@ func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupI var deletePolicies magistrala.DeletePoliciesReq for _, group := range groupsPage.Groups { if group.Parent != "" && group.Parent != parentGroupID { - return fmt.Errorf("%s group doesn't have same parent", group.ID) + return errors.Wrap(errors.ErrConflict, fmt.Errorf("%s group doesn't have same parent", group.ID)) } addPolicies.AddPoliciesReq = append(addPolicies.AddPoliciesReq, &magistrala.AddPolicyReq{ Domain: domain, diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index 365aa8f767..481482b5f4 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -2,3 +2,2659 @@ // SPDX-License-Identifier: Apache-2.0 package groups_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/0x6flab/namegenerator" + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/auth" + authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/clients" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + mggroups "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/groups/mocks" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + idProvider = uuid.New() + token = "token" + namegen = namegenerator.NewNameGenerator() + validGroup = mggroups.Group{ + Name: namegen.Generate(), + Description: namegen.Generate(), + Metadata: map[string]interface{}{ + "key": "value", + }, + Status: clients.Status(groups.EnabledStatus), + } + allowedIDs = []string{ + testsutil.GenerateUUID(&testing.T{}), + testsutil.GenerateUUID(&testing.T{}), + testsutil.GenerateUUID(&testing.T{}), + } +) + +func TestCreateGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + kind string + group mggroups.Group + idResp *magistrala.IdentityRes + idErr error + authzResp *magistrala.AuthorizeRes + authzErr error + authzTknResp *magistrala.AuthorizeRes + authzTknErr error + repoResp mggroups.Group + repoErr error + addPolResp *magistrala.AddPoliciesRes + addPolErr error + err error + }{ + { + desc: "successfully", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + authzTknResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + CreatedAt: time.Now(), + Owner: testsutil.GenerateUUID(t), + }, + addPolResp: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + }, + { + desc: "with invalid token", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + { + desc: "with empty id or domain id but with no grpc error", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{}, + idErr: nil, + err: errors.ErrDomainAuthorization, + }, + { + desc: "with failed to authorize domain membership", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + err: errors.ErrAuthorization, + }, + { + desc: "with failed to authorize domain membership with grpc error", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with invalid status", + token: token, + kind: auth.NewGroupKind, + group: mggroups.Group{ + Name: namegen.Generate(), + Description: namegen.Generate(), + Status: clients.Status(100), + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + err: apiutil.ErrInvalidStatus, + }, + { + desc: "successfully with parent", + token: token, + kind: auth.NewGroupKind, + group: mggroups.Group{ + Name: namegen.Generate(), + Description: namegen.Generate(), + Status: clients.Status(groups.EnabledStatus), + Parent: testsutil.GenerateUUID(t), + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + authzTknResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + CreatedAt: time.Now(), + Owner: testsutil.GenerateUUID(t), + Parent: testsutil.GenerateUUID(t), + }, + addPolResp: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + }, + { + desc: "unsuccessfully with parent due to authorization error", + token: token, + kind: auth.NewGroupKind, + group: mggroups.Group{ + Name: namegen.Generate(), + Description: namegen.Generate(), + Status: clients.Status(groups.EnabledStatus), + Parent: testsutil.GenerateUUID(t), + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + authzTknResp: &magistrala.AuthorizeRes{}, + authzTknErr: errors.ErrAuthorization, + repoResp: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + Parent: testsutil.GenerateUUID(t), + }, + addPolResp: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + err: errors.ErrAuthorization, + }, + { + desc: "with repo error", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + authzTknResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Group{}, + repoErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "with failed to add policies", + token: token, + kind: auth.NewGroupKind, + group: validGroup, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + authzTknResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + }, + addPolResp: &magistrala.AddPoliciesRes{}, + addPolErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.MembershipPermission, + Object: tc.idResp.GetDomainId(), + ObjectType: auth.DomainType, + }).Return(tc.authzResp, tc.authzErr) + repocall2 := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.EditPermission, + Object: tc.group.Parent, + ObjectType: auth.GroupType, + }).Return(tc.authzTknResp, tc.authzTknErr) + repocall3 := repo.On("Save", context.Background(), mock.Anything).Return(tc.repoResp, tc.repoErr) + repocall4 := authsvc.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPolResp, tc.addPolErr) + got, err := svc.CreateGroup(context.Background(), tc.token, tc.kind, tc.group) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.NotEmpty(t, got.ID) + assert.NotEmpty(t, got.CreatedAt) + assert.NotEmpty(t, got.Owner) + assert.WithinDuration(t, time.Now(), got.CreatedAt, 2*time.Second) + ok := repocall3.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + repocall3.Unset() + repocall4.Unset() + }) + } +} + +func TestViewGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + id string + authzResp *magistrala.AuthorizeRes + authzErr error + repoResp mggroups.Group + repoErr error + err error + }{ + { + desc: "successfully", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: validGroup, + }, + { + desc: "with invalid token", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with failed to authorize", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: nil, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.ViewPermission, + Object: tc.id, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.repoResp, tc.repoErr) + got, err := svc.ViewGroup(context.Background(), tc.token, tc.id) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.Equal(t, tc.repoResp, got) + ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + }) + } +} + +func TestViewGroupPerms(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + id string + idResp *magistrala.IdentityRes + idErr error + listResp *magistrala.ListPermissionsRes + listErr error + err error + }{ + { + desc: "successfully", + token: token, + id: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + listResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "with invalid token", + token: token, + id: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + { + desc: "with failed to list permissions", + token: token, + id: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + listErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with empty permissions", + token: token, + id: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + listResp: &magistrala.ListPermissionsRes{ + Permissions: []string{}, + }, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := authsvc.On("ListPermissions", context.Background(), &magistrala.ListPermissionsReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetId(), + Object: tc.id, + ObjectType: auth.GroupType, + }).Return(tc.listResp, tc.listErr) + got, err := svc.ViewGroupPerms(context.Background(), tc.token, tc.id) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.Equal(t, tc.listResp.Permissions, got) + } + repocall.Unset() + repocall1.Unset() + }) + } +} + +func TestUpdateGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + group mggroups.Group + authzResp *magistrala.AuthorizeRes + authzErr error + repoResp mggroups.Group + repoErr error + err error + }{ + { + desc: "successfully", + token: token, + group: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: validGroup, + }, + { + desc: "with invalid token", + token: token, + group: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with failed to authorize", + token: token, + group: mggroups.Group{ + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: nil, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.EditPermission, + Object: tc.group.ID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repo.On("Update", context.Background(), mock.Anything).Return(tc.repoResp, tc.repoErr) + got, err := svc.UpdateGroup(context.Background(), tc.token, tc.group) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.Equal(t, tc.repoResp, got) + ok := repo.AssertCalled(t, "Update", context.Background(), mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + }) + } +} + +func TestEnableGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + id string + authzResp *magistrala.AuthorizeRes + authzErr error + retrieveResp mggroups.Group + retrieveErr error + changeResp mggroups.Group + changeErr error + err error + }{ + { + desc: "successfully", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{ + Status: clients.Status(groups.DisabledStatus), + }, + changeResp: validGroup, + }, + { + desc: "with invalid token", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with failed to authorize", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: nil, + err: errors.ErrAuthorization, + }, + { + desc: "with enabled group", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{ + Status: clients.Status(groups.EnabledStatus), + }, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "with retrieve error", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{}, + retrieveErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.EditPermission, + Object: tc.id, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall1 := repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveResp, tc.retrieveErr) + repocall2 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeResp, tc.changeErr) + got, err := svc.EnableGroup(context.Background(), tc.token, tc.id) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.Equal(t, tc.changeResp, got) + ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + }) + } +} + +func TestDisableGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + id string + authzResp *magistrala.AuthorizeRes + authzErr error + retrieveResp mggroups.Group + retrieveErr error + changeResp mggroups.Group + changeErr error + err error + }{ + { + desc: "successfully", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{ + Status: clients.Status(groups.EnabledStatus), + }, + changeResp: validGroup, + }, + { + desc: "with invalid token", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "with failed to authorize", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: nil, + err: errors.ErrAuthorization, + }, + { + desc: "with enabled group", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{ + Status: clients.Status(groups.DisabledStatus), + }, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "with retrieve error", + token: token, + id: testsutil.GenerateUUID(t), + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + retrieveResp: mggroups.Group{}, + retrieveErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.EditPermission, + Object: tc.id, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall1 := repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveResp, tc.retrieveErr) + repocall2 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeResp, tc.changeErr) + got, err := svc.DisableGroup(context.Background(), tc.token, tc.id) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.Equal(t, tc.changeResp, got) + ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + }) + } +} + +func TestListMembers(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + groupID string + permission string + memberKind string + authzResp *magistrala.AuthorizeRes + authzErr error + listSubjectResp *magistrala.ListSubjectsRes + listSubjectErr error + listObjectResp *magistrala.ListObjectsRes + listObjectErr error + err error + }{ + { + desc: "successfully with things kind", + token: token, + groupID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + }, + { + desc: "successfully with users kind", + token: token, + groupID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + permission: auth.ViewPermission, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + }, + { + desc: "with invalid kind", + token: token, + groupID: testsutil.GenerateUUID(t), + memberKind: auth.GroupsKind, + permission: auth.ViewPermission, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + err: errors.New("invalid member kind"), + }, + { + desc: "with invalid token", + token: token, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + err: errors.ErrAuthorization, + }, + { + desc: "failed to list objects with things kind", + token: token, + groupID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: []string{}, + }, + listObjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "failed to list subjects with users kind", + token: token, + groupID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + permission: auth.ViewPermission, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: []string{}, + }, + listSubjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Subject: tc.token, + Permission: auth.ViewPermission, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall1 := authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.GroupType, + Subject: tc.groupID, + Relation: auth.GroupRelation, + ObjectType: auth.ThingType, + }).Return(tc.listObjectResp, tc.listObjectErr) + repocall2 := authsvc.On("ListAllSubjects", context.Background(), &magistrala.ListSubjectsReq{ + SubjectType: auth.UserType, + Permission: tc.permission, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.listSubjectResp, tc.listSubjectErr) + got, err := svc.ListMembers(context.Background(), tc.token, tc.groupID, tc.permission, tc.memberKind) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.NotEmpty(t, got) + } + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + }) + } +} + +func TestListGroups(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + memberKind string + memberID string + page mggroups.Page + idResp *magistrala.IdentityRes + idErr error + authzResp *magistrala.AuthorizeRes + authzErr error + listSubjectResp *magistrala.ListSubjectsRes + listSubjectErr error + listObjectResp *magistrala.ListObjectsRes + listObjectErr error + listObjectFilterResp *magistrala.ListObjectsRes + listObjectFilterErr error + authSuperAdminResp *magistrala.AuthorizeRes + authSuperAdminErr error + repoResp mggroups.Page + repoErr error + listPermResp *magistrala.ListPermissionsRes + listPermErr error + err error + }{ + { + desc: "successfully with things kind", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "successfully with groups kind", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.GroupsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "successfully with channels kind", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ChannelsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "successfully with users kind non admin", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "successfully with users kind admin", + token: token, + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + UserId: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "unsuccessfully with users kind admin", + token: token, + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + UserId: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with users kind admin with nil error", + token: token, + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + UserId: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with things kind due to failed to authorize", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with things kind due to failed to list subjects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{}, + listSubjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with things kind due to failed to list filtered objects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{}, + listObjectFilterErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with groups kind due to failed to authorize", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.GroupsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with groups kind due to failed to list subjects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.GroupsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{}, + listObjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with groups kind due to failed to list filtered objects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.GroupsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{}, + listObjectFilterErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with channels kind due to failed to authorize", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ChannelsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with channels kind due to failed to list subjects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ChannelsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{}, + listSubjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with channels kind due to failed to list filtered objects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ChannelsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{}, + listObjectFilterErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with users kind due to failed to authorize", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with users kind due to failed to list subjects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{}, + listObjectErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with users kind due to failed to list filtered objects", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{}, + listObjectFilterErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "successfully with users kind admin", + token: token, + memberKind: auth.UsersKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + UserId: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listObjectResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{ + Permissions: []string{ + auth.ViewPermission, + auth.EditPermission, + }, + }, + }, + { + desc: "unsuccessfully with invalid kind", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: "invalid", + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + err: errors.New("invalid member kind"), + }, + { + desc: "unsuccessfully with things kind due to repo error", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{}, + repoErr: errors.ErrViewEntity, + err: errors.ErrViewEntity, + }, + { + desc: "unsuccessfully with things kind due to failed to list permissions", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + listSubjectResp: &magistrala.ListSubjectsRes{ + Policies: allowedIDs, + }, + listObjectFilterResp: &magistrala.ListObjectsRes{ + Policies: allowedIDs, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + listPermResp: &magistrala.ListPermissionsRes{}, + listPermErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with invalid token", + token: token, + memberID: testsutil.GenerateUUID(t), + memberKind: auth.ThingsKind, + page: mggroups.Page{ + Permission: auth.ViewPermission, + ListPerms: true, + }, + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := &mock.Call{} + repocall2 := &mock.Call{} + repocall3 := &mock.Call{} + adminCheck := &mock.Call{} + switch tc.memberKind { + case auth.ThingsKind: + repocall1 = authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.ViewPermission, + Object: tc.memberID, + ObjectType: auth.ThingType, + }).Return(tc.authzResp, tc.authzErr) + repocall2 = authsvc.On("ListAllSubjects", context.Background(), &magistrala.ListSubjectsReq{ + SubjectType: auth.GroupType, + Permission: auth.GroupRelation, + ObjectType: auth.ThingType, + Object: tc.memberID, + }).Return(tc.listSubjectResp, tc.listSubjectErr) + repocall3 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetId(), + Permission: tc.page.Permission, + ObjectType: auth.GroupType, + }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) + case auth.GroupsKind: + repocall1 = authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: tc.page.Permission, + Object: tc.memberID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall2 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.GroupType, + Subject: tc.memberID, + Permission: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + }).Return(tc.listObjectResp, tc.listObjectErr) + repocall3 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetId(), + Permission: tc.page.Permission, + ObjectType: auth.GroupType, + }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) + case auth.ChannelsKind: + repocall1 = authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.ViewPermission, + Object: tc.memberID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall2 = authsvc.On("ListAllSubjects", context.Background(), &magistrala.ListSubjectsReq{ + SubjectType: auth.GroupType, + Permission: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + Object: tc.memberID, + }).Return(tc.listSubjectResp, tc.listSubjectErr) + repocall3 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetId(), + Permission: tc.page.Permission, + ObjectType: auth.GroupType, + }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) + case auth.UsersKind: + adminCheckReq := &magistrala.AuthorizeReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetUserId(), + Permission: auth.AdminPermission, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + } + adminCheck = authsvc.On("Authorize", context.Background(), adminCheckReq).Return(tc.authzResp, tc.authzErr) + authReq := &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.AdminPermission, + Object: tc.idResp.GetDomainId(), + ObjectType: auth.DomainType, + } + if tc.memberID == "" { + authReq.Domain = "" + authReq.Permission = auth.MembershipPermission + } + repocall1 = authsvc.On("Authorize", context.Background(), authReq).Return(tc.authzResp, tc.authzErr) + repocall2 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.UserType, + Subject: auth.EncodeDomainUserID(tc.idResp.GetDomainId(), tc.memberID), + Permission: tc.page.Permission, + ObjectType: auth.GroupType, + }).Return(tc.listObjectResp, tc.listObjectErr) + repocall3 = authsvc.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: auth.UserType, + Subject: tc.idResp.GetId(), + Permission: tc.page.Permission, + ObjectType: auth.GroupType, + }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) + } + repocall4 := repo.On("RetrieveByIDs", context.Background(), mock.Anything, mock.Anything).Return(tc.repoResp, tc.repoErr) + repocall5 := authsvc.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermResp, tc.listPermErr) + got, err := svc.ListGroups(context.Background(), tc.token, tc.memberKind, tc.memberID, tc.page) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + if err == nil { + assert.NotEmpty(t, got) + } + repocall.Unset() + switch tc.memberKind { + case auth.ThingsKind, auth.GroupsKind, auth.ChannelsKind, auth.UsersKind: + repocall1.Unset() + repocall2.Unset() + repocall3.Unset() + if tc.memberID == "" { + adminCheck.Unset() + } + } + repocall4.Unset() + repocall5.Unset() + }) + } +} + +func TestAssign(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + groupID string + relation string + memberKind string + memberIDs []string + idResp *magistrala.IdentityRes + idErr error + authzResp *magistrala.AuthorizeRes + authzErr error + addPoliciesRes *magistrala.AddPoliciesRes + addPoliciesErr error + repoResp mggroups.Page + repoErr error + addParentPoliciesRes *magistrala.AddPoliciesRes + addParentPoliciesErr error + deleteParentPoliciesRes *magistrala.DeletePoliciesRes + deleteParentPoliciesErr error + repoParentGroupErr error + err error + }{ + { + desc: "successfully with things kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + }, + { + desc: "successfully with channels kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ChannelsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + }, + { + desc: "successfully with groups kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + repoParentGroupErr: nil, + }, + { + desc: "successfully with users kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.UsersKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + }, + { + desc: "unsuccessfully with groups kind due to repo err", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{}, + repoErr: errors.ErrViewEntity, + err: errors.ErrViewEntity, + }, + { + desc: "unsuccessfully with groups kind due to empty page", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{}, + }, + err: errors.New("invalid group ids"), + }, + { + desc: "unsuccessfully with groups kind due to non empty parent", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + { + ID: testsutil.GenerateUUID(t), + Parent: testsutil.GenerateUUID(t), + }, + }, + }, + err: errors.ErrConflict, + }, + { + desc: "unsuccessfully with groups kind due to failed to add policies", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: false, + }, + addPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with groups kind due to failed to assign parent", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + repoParentGroupErr: errors.ErrConflict, + err: errors.ErrConflict, + }, + { + desc: "unsuccessfully with groups kind due to failed to assign parent and delete policy", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: true, + }, + deleteParentPoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: false, + }, + deleteParentPoliciesErr: errors.ErrAuthorization, + repoParentGroupErr: errors.ErrConflict, + err: apiutil.ErrRollbackTx, + }, + { + desc: "unsuccessfully with invalid kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: "invalid", + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + err: errors.New("invalid member kind"), + }, + { + desc: "unsuccessfully with invalid token", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.UsersKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + { + desc: "unsuccessfully with failed to authorize", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to add policies", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + addPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: false, + }, + addPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.EditPermission, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + retrieveByIDsCall := &mock.Call{} + deletePoliciesCall := &mock.Call{} + assignParentCall := &mock.Call{} + policies := magistrala.AddPoliciesReq{} + switch tc.memberKind { + case auth.ThingsKind: + for _, memberID := range tc.memberIDs { + policies.AddPoliciesReq = append(policies.AddPoliciesReq, &magistrala.AddPolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + SubjectKind: auth.ChannelsKind, + Subject: tc.groupID, + Relation: tc.relation, + ObjectType: auth.ThingType, + Object: memberID, + }) + } + case auth.GroupsKind: + retrieveByIDsCall = repo.On("RetrieveByIDs", context.Background(), mggroups.Page{PageMeta: mggroups.PageMeta{Limit: 1<<63 - 1}}, mock.Anything).Return(tc.repoResp, tc.repoErr) + var deletePolicies magistrala.DeletePoliciesReq + for _, group := range tc.repoResp.Groups { + policies.AddPoliciesReq = append(policies.AddPoliciesReq, &magistrala.AddPolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: tc.groupID, + Relation: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + Object: group.ID, + }) + deletePolicies.DeletePoliciesReq = append(deletePolicies.DeletePoliciesReq, &magistrala.DeletePolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: tc.groupID, + Relation: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + Object: group.ID, + }) + } + deletePoliciesCall = authsvc.On("DeletePolicies", context.Background(), &deletePolicies).Return(tc.deleteParentPoliciesRes, tc.deleteParentPoliciesErr) + assignParentCall = repo.On("AssignParentGroup", context.Background(), tc.groupID, tc.memberIDs).Return(tc.repoParentGroupErr) + case auth.ChannelsKind: + for _, memberID := range tc.memberIDs { + policies.AddPoliciesReq = append(policies.AddPoliciesReq, &magistrala.AddPolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: memberID, + Relation: tc.relation, + ObjectType: auth.GroupType, + Object: tc.groupID, + }) + } + case auth.UsersKind: + for _, memberID := range tc.memberIDs { + policies.AddPoliciesReq = append(policies.AddPoliciesReq, &magistrala.AddPolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + Subject: auth.EncodeDomainUserID(tc.idResp.GetDomainId(), memberID), + Relation: tc.relation, + ObjectType: auth.GroupType, + Object: tc.groupID, + }) + } + } + repocall2 := authsvc.On("AddPolicies", context.Background(), &policies).Return(tc.addPoliciesRes, tc.addPoliciesErr) + err := svc.Assign(context.Background(), tc.token, tc.groupID, tc.relation, tc.memberKind, tc.memberIDs...) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + if tc.memberKind == auth.GroupsKind { + retrieveByIDsCall.Unset() + deletePoliciesCall.Unset() + assignParentCall.Unset() + } + }) + } +} + +func TestUnassign(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + groupID string + relation string + memberKind string + memberIDs []string + idResp *magistrala.IdentityRes + idErr error + authzResp *magistrala.AuthorizeRes + authzErr error + deletePoliciesRes *magistrala.DeletePoliciesRes + deletePoliciesErr error + repoResp mggroups.Page + repoErr error + addParentPoliciesRes *magistrala.AddPoliciesRes + addParentPoliciesErr error + deleteParentPoliciesRes *magistrala.DeletePoliciesRes + deleteParentPoliciesErr error + repoParentGroupErr error + err error + }{ + { + desc: "successfully with things kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + }, + { + desc: "successfully with channels kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ChannelsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + }, + { + desc: "successfully with groups kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + repoParentGroupErr: nil, + }, + { + desc: "successfully with users kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.UsersKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + }, + { + desc: "unsuccessfully with groups kind due to repo err", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{}, + repoErr: errors.ErrViewEntity, + err: errors.ErrViewEntity, + }, + { + desc: "unsuccessfully with groups kind due to empty page", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{}, + }, + err: errors.New("invalid group ids"), + }, + { + desc: "unsuccessfully with groups kind due to non empty parent", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + { + ID: testsutil.GenerateUUID(t), + Parent: testsutil.GenerateUUID(t), + }, + }, + }, + err: errors.ErrConflict, + }, + { + desc: "unsuccessfully with groups kind due to failed to add policies", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: false, + }, + deletePoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with groups kind due to failed to unassign parent", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + repoParentGroupErr: errors.ErrConflict, + err: errors.ErrConflict, + }, + { + desc: "unsuccessfully with groups kind due to failed to unassign parent and add policy", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.GroupsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + repoResp: mggroups.Page{ + Groups: []mggroups.Group{ + validGroup, + validGroup, + validGroup, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + repoParentGroupErr: errors.ErrConflict, + addParentPoliciesRes: &magistrala.AddPoliciesRes{ + Authorized: false, + }, + addParentPoliciesErr: errors.ErrAuthorization, + err: errors.ErrConflict, + }, + { + desc: "unsuccessfully with invalid kind", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: "invalid", + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + err: errors.New("invalid member kind"), + }, + { + desc: "unsuccessfully with invalid token", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.UsersKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + { + desc: "unsuccessfully with failed to authorize", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to add policies", + token: token, + groupID: testsutil.GenerateUUID(t), + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + memberIDs: allowedIDs, + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{ + Deleted: false, + }, + deletePoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.EditPermission, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + retrieveByIDsCall := &mock.Call{} + addPoliciesCall := &mock.Call{} + assignParentCall := &mock.Call{} + policies := magistrala.DeletePoliciesReq{} + switch tc.memberKind { + case auth.ThingsKind: + for _, memberID := range tc.memberIDs { + policies.DeletePoliciesReq = append(policies.DeletePoliciesReq, &magistrala.DeletePolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + SubjectKind: auth.ChannelsKind, + Subject: tc.groupID, + Relation: tc.relation, + ObjectType: auth.ThingType, + Object: memberID, + }) + } + case auth.GroupsKind: + retrieveByIDsCall = repo.On("RetrieveByIDs", context.Background(), mggroups.Page{PageMeta: mggroups.PageMeta{Limit: 1<<63 - 1}}, mock.Anything).Return(tc.repoResp, tc.repoErr) + var addPolicies magistrala.AddPoliciesReq + for _, group := range tc.repoResp.Groups { + policies.DeletePoliciesReq = append(policies.DeletePoliciesReq, &magistrala.DeletePolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: tc.groupID, + Relation: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + Object: group.ID, + }) + addPolicies.AddPoliciesReq = append(addPolicies.AddPoliciesReq, &magistrala.AddPolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: tc.groupID, + Relation: auth.ParentGroupRelation, + ObjectType: auth.GroupType, + Object: group.ID, + }) + } + addPoliciesCall = authsvc.On("AddPolicies", context.Background(), &addPolicies).Return(tc.addParentPoliciesRes, tc.addParentPoliciesErr) + assignParentCall = repo.On("UnassignParentGroup", context.Background(), tc.groupID, tc.memberIDs).Return(tc.repoParentGroupErr) + case auth.ChannelsKind: + for _, memberID := range tc.memberIDs { + policies.DeletePoliciesReq = append(policies.DeletePoliciesReq, &magistrala.DeletePolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.GroupType, + Subject: memberID, + Relation: tc.relation, + ObjectType: auth.GroupType, + Object: tc.groupID, + }) + } + case auth.UsersKind: + for _, memberID := range tc.memberIDs { + policies.DeletePoliciesReq = append(policies.DeletePoliciesReq, &magistrala.DeletePolicyReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + Subject: auth.EncodeDomainUserID(tc.idResp.GetDomainId(), memberID), + Relation: tc.relation, + ObjectType: auth.GroupType, + Object: tc.groupID, + }) + } + } + repocall2 := authsvc.On("DeletePolicies", context.Background(), &policies).Return(tc.deletePoliciesRes, tc.deletePoliciesErr) + err := svc.Unassign(context.Background(), tc.token, tc.groupID, tc.relation, tc.memberKind, tc.memberIDs...) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + if tc.memberKind == auth.GroupsKind { + retrieveByIDsCall.Unset() + addPoliciesCall.Unset() + assignParentCall.Unset() + } + }) + } +} + +func TestDeleteGroup(t *testing.T) { + repo := new(mocks.Repository) + authsvc := new(authmocks.Service) + svc := groups.NewService(repo, idProvider, authsvc) + + cases := []struct { + desc string + token string + groupID string + idResp *magistrala.IdentityRes + idErr error + authzResp *magistrala.AuthorizeRes + authzErr error + deleteChildPoliciesRes *magistrala.DeletePolicyRes + deleteChildPoliciesErr error + deleteThingsPoliciesRes *magistrala.DeletePolicyRes + deleteThingsPoliciesErr error + deleteDomainsPoliciesRes *magistrala.DeletePolicyRes + deleteDomainsPoliciesErr error + deleteUsersPoliciesRes *magistrala.DeletePolicyRes + deleteUsersPoliciesErr error + repoErr error + err error + }{ + { + desc: "successfully", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteUsersPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + }, + { + desc: "unsuccessfully with invalid token", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{}, + idErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, + }, + { + desc: "unsuccessfully with authorization error", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: false, + }, + authzErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to remove child group policy", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: false, + }, + deleteChildPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to remove things policy", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: false, + }, + deleteThingsPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to remove domain policy", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: false, + }, + deleteDomainsPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with failed to remove user policy", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteUsersPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: false, + }, + deleteUsersPoliciesErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "unsuccessfully with repo err", + token: token, + groupID: testsutil.GenerateUUID(t), + idResp: &magistrala.IdentityRes{ + Id: testsutil.GenerateUUID(t), + DomainId: testsutil.GenerateUUID(t), + }, + authzResp: &magistrala.AuthorizeRes{ + Authorized: true, + }, + deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ + Deleted: true, + }, + repoErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + repocall := authsvc.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(tc.idResp, tc.idErr) + repocall1 := authsvc.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: tc.idResp.GetDomainId(), + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Subject: tc.idResp.GetId(), + Permission: auth.DeletePermission, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.authzResp, tc.authzErr) + repocall2 := authsvc.On("DeletePolicy", context.Background(), &magistrala.DeletePolicyReq{ + SubjectType: auth.GroupType, + Subject: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.deleteChildPoliciesRes, tc.deleteChildPoliciesErr) + repocall3 := authsvc.On("DeletePolicy", context.Background(), &magistrala.DeletePolicyReq{ + SubjectType: auth.GroupType, + Subject: tc.groupID, + ObjectType: auth.ThingType, + }).Return(tc.deleteThingsPoliciesRes, tc.deleteThingsPoliciesErr) + repocall4 := authsvc.On("DeletePolicy", context.Background(), &magistrala.DeletePolicyReq{ + SubjectType: auth.DomainType, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.deleteDomainsPoliciesRes, tc.deleteDomainsPoliciesErr) + repocall5 := repo.On("Delete", context.Background(), tc.groupID).Return(tc.repoErr) + repocall6 := authsvc.On("DeletePolicy", context.Background(), &magistrala.DeletePolicyReq{ + SubjectType: auth.UserType, + Object: tc.groupID, + ObjectType: auth.GroupType, + }).Return(tc.deleteUsersPoliciesRes, tc.deleteUsersPoliciesErr) + err := svc.DeleteGroup(context.Background(), tc.token, tc.groupID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + repocall.Unset() + repocall1.Unset() + repocall2.Unset() + repocall3.Unset() + repocall4.Unset() + repocall5.Unset() + repocall6.Unset() + }) + } +} diff --git a/internal/groups/status_test.go b/internal/groups/status_test.go new file mode 100644 index 0000000000..e052538120 --- /dev/null +++ b/internal/groups/status_test.go @@ -0,0 +1,50 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package groups_test + +import ( + "testing" + + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/groups" + "github.com/stretchr/testify/assert" +) + +func TestStatus_String(t *testing.T) { + cases := []struct { + name string + status groups.Status + expected string + }{ + {"Enabled", groups.EnabledStatus, "enabled"}, + {"Disabled", groups.DisabledStatus, "disabled"}, + {"All", groups.AllStatus, "all"}, + {"Unknown", groups.Status(100), "unknown"}, + } + + for _, tc := range cases { + got := tc.status.String() + assert.Equal(t, tc.expected, got, "Status.String() = %v, expected %v", got, tc.expected) + } +} + +func TestToStatus(t *testing.T) { + cases := []struct { + name string + status string + gstatus groups.Status + err error + }{ + {"Enabled", "enabled", groups.EnabledStatus, nil}, + {"Disabled", "disabled", groups.DisabledStatus, nil}, + {"All", "all", groups.AllStatus, nil}, + {"Unknown", "unknown", groups.Status(0), apiutil.ErrInvalidStatus}, + } + + for _, tc := range cases { + got, err := groups.ToStatus(tc.status) + assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.gstatus, got, "ToStatus() = %v, expected %v", got, tc.gstatus) + } +} diff --git a/pkg/groups/groups.go b/pkg/groups/groups.go index aa78d9070a..9994f2102f 100644 --- a/pkg/groups/groups.go +++ b/pkg/groups/groups.go @@ -66,6 +66,8 @@ type Page struct { } // Repository specifies a group persistence API. +// +//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false type Repository interface { // Save group. Save(ctx context.Context, g Group) (Group, error) @@ -95,6 +97,7 @@ type Repository interface { Delete(ctx context.Context, groupID string) error } +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false type Service interface { // CreateGroup creates new group. CreateGroup(ctx context.Context, token, kind string, g Group) (Group, error) diff --git a/pkg/groups/mocks/repository.go b/pkg/groups/mocks/repository.go new file mode 100644 index 0000000000..b554e39590 --- /dev/null +++ b/pkg/groups/mocks/repository.go @@ -0,0 +1,253 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + groups "github.com/absmach/magistrala/pkg/groups" + mock "github.com/stretchr/testify/mock" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AssignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs +func (_m *Repository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { + ret := _m.Called(ctx, parentGroupID, groupIDs) + + if len(ret) == 0 { + panic("no return value specified for AssignParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { + r0 = rf(ctx, parentGroupID, groupIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ChangeStatus provides a mock function with given fields: ctx, group +func (_m *Repository) ChangeStatus(ctx context.Context, group groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, group) + + if len(ret) == 0 { + panic("no return value specified for ChangeStatus") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, group) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, group) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, group) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, groupID +func (_m *Repository) Delete(ctx context.Context, groupID string) error { + ret := _m.Called(ctx, groupID) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, groupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAll provides a mock function with given fields: ctx, gm +func (_m *Repository) RetrieveAll(ctx context.Context, gm groups.Page) (groups.Page, error) { + ret := _m.Called(ctx, gm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAll") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Page) (groups.Page, error)); ok { + return rf(ctx, gm) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Page) groups.Page); ok { + r0 = rf(ctx, gm) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Page) error); ok { + r1 = rf(ctx, gm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByID provides a mock function with given fields: ctx, id +func (_m *Repository) RetrieveByID(ctx context.Context, id string) (groups.Group, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByID") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (groups.Group, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) groups.Group); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByIDs provides a mock function with given fields: ctx, gm, ids +func (_m *Repository) RetrieveByIDs(ctx context.Context, gm groups.Page, ids ...string) (groups.Page, error) { + ret := _m.Called(ctx, gm, ids) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByIDs") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Page, ...string) (groups.Page, error)); ok { + return rf(ctx, gm, ids...) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Page, ...string) groups.Page); ok { + r0 = rf(ctx, gm, ids...) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Page, ...string) error); ok { + r1 = rf(ctx, gm, ids...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Save provides a mock function with given fields: ctx, g +func (_m *Repository) Save(ctx context.Context, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, g) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, g) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnassignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs +func (_m *Repository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { + ret := _m.Called(ctx, parentGroupID, groupIDs) + + if len(ret) == 0 { + panic("no return value specified for UnassignParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { + r0 = rf(ctx, parentGroupID, groupIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, g +func (_m *Repository) Update(ctx context.Context, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, g) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, g) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/groups/mocks/service.go b/pkg/groups/mocks/service.go new file mode 100644 index 0000000000..311fca0189 --- /dev/null +++ b/pkg/groups/mocks/service.go @@ -0,0 +1,311 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + groups "github.com/absmach/magistrala/pkg/groups" + mock "github.com/stretchr/testify/mock" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// Assign provides a mock function with given fields: ctx, token, groupID, relation, memberKind, memberIDs +func (_m *Service) Assign(ctx context.Context, token string, groupID string, relation string, memberKind string, memberIDs ...string) error { + ret := _m.Called(ctx, token, groupID, relation, memberKind, memberIDs) + + if len(ret) == 0 { + panic("no return value specified for Assign") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...string) error); ok { + r0 = rf(ctx, token, groupID, relation, memberKind, memberIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateGroup provides a mock function with given fields: ctx, token, kind, g +func (_m *Service) CreateGroup(ctx context.Context, token string, kind string, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, token, kind, g) + + if len(ret) == 0 { + panic("no return value specified for CreateGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, groups.Group) (groups.Group, error)); ok { + return rf(ctx, token, kind, g) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, groups.Group) groups.Group); ok { + r0 = rf(ctx, token, kind, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, groups.Group) error); ok { + r1 = rf(ctx, token, kind, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteGroup provides a mock function with given fields: ctx, token, id +func (_m *Service) DeleteGroup(ctx context.Context, token string, id string) error { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DisableGroup provides a mock function with given fields: ctx, token, id +func (_m *Service) DisableGroup(ctx context.Context, token string, id string) (groups.Group, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for DisableGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (groups.Group, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) groups.Group); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EnableGroup provides a mock function with given fields: ctx, token, id +func (_m *Service) EnableGroup(ctx context.Context, token string, id string) (groups.Group, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for EnableGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (groups.Group, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) groups.Group); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListGroups provides a mock function with given fields: ctx, token, memberKind, memberID, gm +func (_m *Service) ListGroups(ctx context.Context, token string, memberKind string, memberID string, gm groups.Page) (groups.Page, error) { + ret := _m.Called(ctx, token, memberKind, memberID, gm) + + if len(ret) == 0 { + panic("no return value specified for ListGroups") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, groups.Page) (groups.Page, error)); ok { + return rf(ctx, token, memberKind, memberID, gm) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, groups.Page) groups.Page); ok { + r0 = rf(ctx, token, memberKind, memberID, gm) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, groups.Page) error); ok { + r1 = rf(ctx, token, memberKind, memberID, gm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListMembers provides a mock function with given fields: ctx, token, groupID, permission, memberKind +func (_m *Service) ListMembers(ctx context.Context, token string, groupID string, permission string, memberKind string) (groups.MembersPage, error) { + ret := _m.Called(ctx, token, groupID, permission, memberKind) + + if len(ret) == 0 { + panic("no return value specified for ListMembers") + } + + var r0 groups.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (groups.MembersPage, error)); ok { + return rf(ctx, token, groupID, permission, memberKind) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) groups.MembersPage); ok { + r0 = rf(ctx, token, groupID, permission, memberKind) + } else { + r0 = ret.Get(0).(groups.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok { + r1 = rf(ctx, token, groupID, permission, memberKind) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unassign provides a mock function with given fields: ctx, token, groupID, relation, memberKind, memberIDs +func (_m *Service) Unassign(ctx context.Context, token string, groupID string, relation string, memberKind string, memberIDs ...string) error { + ret := _m.Called(ctx, token, groupID, relation, memberKind, memberIDs) + + if len(ret) == 0 { + panic("no return value specified for Unassign") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...string) error); ok { + r0 = rf(ctx, token, groupID, relation, memberKind, memberIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateGroup provides a mock function with given fields: ctx, token, g +func (_m *Service) UpdateGroup(ctx context.Context, token string, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, token, g) + + if len(ret) == 0 { + panic("no return value specified for UpdateGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, groups.Group) (groups.Group, error)); ok { + return rf(ctx, token, g) + } + if rf, ok := ret.Get(0).(func(context.Context, string, groups.Group) groups.Group); ok { + r0 = rf(ctx, token, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, groups.Group) error); ok { + r1 = rf(ctx, token, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ViewGroup provides a mock function with given fields: ctx, token, id +func (_m *Service) ViewGroup(ctx context.Context, token string, id string) (groups.Group, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for ViewGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (groups.Group, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) groups.Group); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ViewGroupPerms provides a mock function with given fields: ctx, token, id +func (_m *Service) ViewGroupPerms(ctx context.Context, token string, id string) ([]string, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for ViewGroupPerms") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok { + r0 = rf(ctx, token, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index 8b02d84da1..7cc2bad9fb 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -16,13 +16,13 @@ import ( authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/groups" - "github.com/absmach/magistrala/internal/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/groups/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/things" api "github.com/absmach/magistrala/things/api/http" @@ -105,7 +105,7 @@ func TestCreateChannel(t *testing.T) { desc: "create channel with invalid parent", channel: sdk.Channel{ Name: gName, - ParentID: mocks.WrongID, + ParentID: wrongID, Status: mgclients.EnabledStatus.String(), }, token: token, @@ -115,7 +115,7 @@ func TestCreateChannel(t *testing.T) { desc: "create channel with invalid owner", channel: sdk.Channel{ Name: gName, - OwnerID: mocks.WrongID, + OwnerID: wrongID, Status: mgclients.EnabledStatus.String(), }, token: token, @@ -278,7 +278,7 @@ func TestListChannels(t *testing.T) { repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) - repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertChannels(tc.response)}, tc.err) + repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertChannels(tc.response)}, tc.err) pm := sdk.PageMetadata{ Offset: tc.offset, Limit: tc.limit, @@ -288,7 +288,7 @@ func TestListChannels(t *testing.T) { assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, len(tc.response), len(page.Channels), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("RetrieveByIDs was not called on %s", tc.desc)) } repoCall.Unset() @@ -340,7 +340,7 @@ func TestViewChannel(t *testing.T) { { desc: "view channel for wrong id", token: validToken, - channelID: mocks.WrongID, + channelID: wrongID, response: sdk.Channel{Children: []*sdk.Channel{}}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), }, @@ -438,7 +438,7 @@ func TestUpdateChannel(t *testing.T) { { desc: "update channel name with invalid channel id", channel: sdk.Channel{ - ID: mocks.WrongID, + ID: wrongID, Name: "NewName", }, response: sdk.Channel{}, @@ -448,7 +448,7 @@ func TestUpdateChannel(t *testing.T) { { desc: "update channel description with invalid channel id", channel: sdk.Channel{ - ID: mocks.WrongID, + ID: wrongID, Description: "NewDescription", }, response: sdk.Channel{}, @@ -458,7 +458,7 @@ func TestUpdateChannel(t *testing.T) { { desc: "update channel metadata with invalid channel id", channel: sdk.Channel{ - ID: mocks.WrongID, + ID: wrongID, Metadata: sdk.Metadata{ "field": "value2", }, @@ -636,7 +636,7 @@ func TestListChannelsByThing(t *testing.T) { repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&magistrala.ListSubjectsRes{Policies: toIDs(tc.response)}, nil) repoCall3 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) - repoCall4 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertChannels(tc.response)}, tc.err) + repoCall4 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertChannels(tc.response)}, tc.err) page, err := mgsdk.ChannelsByThing(tc.clientID, tc.page, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page.Channels, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page.Channels)) @@ -668,7 +668,7 @@ func TestEnableChannel(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableChannel("wrongID", validToken) assert.Equal(t, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), err, fmt.Sprintf("Enable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) @@ -721,8 +721,8 @@ func TestDisableChannel(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) - repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) + repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) _, err := mgsdk.DisableChannel("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index dfb19d0d1c..62760e1d8d 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -14,13 +14,13 @@ import ( authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/groups" - "github.com/absmach/magistrala/internal/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/groups/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" @@ -91,7 +91,7 @@ func TestCreateGroup(t *testing.T) { token: token, group: sdk.Group{ Name: gName, - ParentID: mocks.WrongID, + ParentID: wrongID, Status: clients.EnabledStatus.String(), }, err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusInternalServerError), @@ -101,7 +101,7 @@ func TestCreateGroup(t *testing.T) { token: token, group: sdk.Group{ Name: gName, - OwnerID: mocks.WrongID, + OwnerID: wrongID, Status: clients.EnabledStatus.String(), }, err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), @@ -271,7 +271,7 @@ func TestListGroups(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) - repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) + repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) pm := sdk.PageMetadata{ Offset: tc.offset, Limit: tc.limit, @@ -281,7 +281,7 @@ func TestListGroups(t *testing.T) { assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("RetrieveByIDs was not called on %s", tc.desc)) } repoCall.Unset() @@ -402,7 +402,7 @@ func TestListParentGroups(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) - repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) + repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) pm := sdk.PageMetadata{ Offset: tc.offset, Limit: tc.limit, @@ -412,7 +412,7 @@ func TestListParentGroups(t *testing.T) { assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("RetrieveByIDs was not called on %s", tc.desc)) } repoCall.Unset() @@ -534,7 +534,7 @@ func TestListChildrenGroups(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) - repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) + repoCall3 := grepo.On("RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything).Return(mggroups.Page{Groups: convertGroups(tc.response)}, tc.err) pm := sdk.PageMetadata{ Offset: tc.offset, Limit: tc.limit, @@ -544,7 +544,7 @@ func TestListChildrenGroups(t *testing.T) { assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByIDs", mock.Anything, mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("RetrieveByIDs was not called on %s", tc.desc)) } repoCall.Unset() @@ -596,7 +596,7 @@ func TestViewGroup(t *testing.T) { { desc: "view group for wrong id", token: validToken, - groupID: mocks.WrongID, + groupID: wrongID, response: sdk.Group{Children: []*sdk.Group{}}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), }, @@ -694,7 +694,7 @@ func TestUpdateGroup(t *testing.T) { { desc: "update group name with invalid group id", group: sdk.Group{ - ID: mocks.WrongID, + ID: wrongID, Name: "NewName", }, response: sdk.Group{}, @@ -704,7 +704,7 @@ func TestUpdateGroup(t *testing.T) { { desc: "update group description with invalid group id", group: sdk.Group{ - ID: mocks.WrongID, + ID: wrongID, Description: "NewDescription", }, response: sdk.Group{}, @@ -714,7 +714,7 @@ func TestUpdateGroup(t *testing.T) { { desc: "update group metadata with invalid group id", group: sdk.Group{ - ID: mocks.WrongID, + ID: wrongID, Metadata: sdk.Metadata{ "field": "value2", }, @@ -803,7 +803,7 @@ func TestEnableGroup(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableGroup("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Enable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) @@ -856,8 +856,8 @@ func TestDisableGroup(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) - repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) + repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) _, err := mgsdk.DisableGroup("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 77a3a1e493..f0f64fec46 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -14,13 +14,13 @@ import ( authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/groups" - gmocks "github.com/absmach/magistrala/internal/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + gmocks "github.com/absmach/magistrala/pkg/groups/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/things" api "github.com/absmach/magistrala/things/api/http" diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 3285a30370..f65dc037fc 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -14,13 +14,13 @@ import ( authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/groups" - gmocks "github.com/absmach/magistrala/internal/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + gmocks "github.com/absmach/magistrala/pkg/groups/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" diff --git a/things/service_test.go b/things/service_test.go index cdc0433bd8..14d91a5095 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -10,12 +10,12 @@ import ( "github.com/absmach/magistrala" authmocks "github.com/absmach/magistrala/auth/mocks" - gmocks "github.com/absmach/magistrala/internal/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + gmocks "github.com/absmach/magistrala/pkg/groups/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/things" "github.com/absmach/magistrala/things/mocks" From 73a2c072bcad0a411c5cc90f8a82c9230ccaaea2 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:32:09 +0530 Subject: [PATCH 05/71] MG-1968 - List all status domains by default (#248) Signed-off-by: Arvindh --- auth/api/http/domains/decode.go | 2 +- auth/postgres/domains.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/api/http/domains/decode.go b/auth/api/http/domains/decode.go index 249d1709a1..f49659fa9d 100644 --- a/auth/api/http/domains/decode.go +++ b/auth/api/http/domains/decode.go @@ -146,7 +146,7 @@ func decodeListUserDomainsRequest(ctx context.Context, r *http.Request) (interfa } func decodePageRequest(_ context.Context, r *http.Request) (page, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + s, err := apiutil.ReadStringQuery(r, api.StatusKey, auth.All) if err != nil { return page{}, errors.Wrap(apiutil.ErrValidation, err) } diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index 56f4b8a563..e6d8d8a6b1 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -552,10 +552,10 @@ func buildPageQuery(pm auth.Page) (string, error) { query = append(query, fmt.Sprintf("d.id IN ('%s')", strings.Join(pm.IDs, "','"))) } - if pm.Status != auth.AllStatus { + if (pm.Status >= auth.EnabledStatus) && (pm.Status <= auth.AllStatus) { query = append(query, "d.status = :status") } else { - query = append(query, fmt.Sprintf("d.status < %s", auth.AllStatus)) + query = append(query, fmt.Sprintf("d.status < %d", auth.AllStatus)) } if pm.Name != "" { From 97432fd8353de64bde911605208664374bc9427a Mon Sep 17 00:00:00 2001 From: charlie-jangala <129748315+charlie-jangala@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:40:48 +0000 Subject: [PATCH 06/71] NOISSUE - Increase minimum docker-compose version in README.md (#260) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2aabbefd44..4d2c15a693 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ For more details, check out the [official documentation][docs]. The following are needed to run Magistrala: - [Docker](https://docs.docker.com/install/) (version 20.10) -- [Docker compose](https://docs.docker.com/compose/install/) (version 1.29) +- [Docker compose](https://docs.docker.com/compose/install/) (version 2.20) Developing Magistrala will also require: From 4375254073549a27635c7e025a232a307c642798 Mon Sep 17 00:00:00 2001 From: Nataly Musilah <115026536+Musilah@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:45:54 +0300 Subject: [PATCH 07/71] NOISSUE - Resolve falsely passing CI (#214) Signed-off-by: Musilah Co-authored-by: Musilah --- .github/workflows/build.yml | 1 - .github/workflows/tests.yml | 3 --- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd1622f283..d2ae81b678 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,6 @@ jobs: build-and-push: name: Build and Push runs-on: ubuntu-latest - continue-on-error: true steps: - name: Checkout code diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ba9d02893f..3001f1d3ec 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,9 +44,6 @@ jobs: name: Run tests runs-on: ubuntu-latest needs: lint-and-build - continue-on-error: false - strategy: - fail-fast: true steps: - name: Checkout From 7776e248c8441eadbc16901f3d51218d879e9fb8 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:16:32 +0530 Subject: [PATCH 08/71] MG-1961 - Add Domains OpenAPI Spec (#261) Signed-off-by: Arvindh --- api/openapi/auth.yml | 572 ++++++++++++++++++++++++++++++++++++++++++ api/openapi/users.yml | 32 ++- 2 files changed, 603 insertions(+), 1 deletion(-) diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml index 93b791eadd..9ab9366fa7 100644 --- a/api/openapi/auth.yml +++ b/api/openapi/auth.yml @@ -32,6 +32,291 @@ tags: url: http://docs.mainflux.io/ paths: + /domains: + post: + tags: + - Domains + summary: Adds new domain + description: | + Adds new domain. + requestBody: + $ref: "#/components/requestBodies/DomainCreateReq" + responses: + "201": + $ref: "#/components/responses/DomainCreateRes" + "400": + description: Failed due to malformed JSON. + "401": + description: Missing or invalid access token provided. + "409": + description: Failed due to using an existing alias. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + get: + summary: Retrieves list of domains. + description: | + Retrieves list of domains that the user have access. + parameters: + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/Metadata" + - $ref: "#/components/parameters/Status" + - $ref: "#/components/parameters/DomainName" + - $ref: "#/components/parameters/Permission" + tags: + - Domains + security: + - bearerAuth: [] + responses: + "200": + $ref: "#/components/responses/DomainsPageRes" + "401": + description: Missing or invalid access token provided. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + /domains/{domainID}: + get: + summary: Retrieves domain information + description: | + Retrieves a specific domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + $ref: "#/components/responses/DomainRes" + "401": + description: Missing or invalid access token provided. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + patch: + summary: Updates name, metadata, tags and alias of the domain. + description: | + Updates name, metadata, tags and alias of the domain. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + requestBody: + $ref: "#/components/requestBodies/DomainUpdateReq" + security: + - bearerAuth: [] + responses: + "200": + $ref: "#/components/responses/DomainRes" + "400": + description: Failed due to malformed JSON. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access to domain id. + "404": + description: Failed due to non existing domain. + "415": + description: Missing or invalid content type. + "500": + $ref: "#/components/responses/ServiceError" + delete: + summary: Delete domain for a domain with the given id. + description: | + Delete domain removes a domain with the given id from repo + and removes all the things, channels, assigned users, policies related to this domain. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "204": + description: Domain deleted. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access to domain id. + "500": + $ref: "#/components/responses/ServiceError" + /domains/{domainID}/permissions: + get: + summary: Retrieves user permissions on domain. + description: | + Retrieves user permissions on domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + $ref: "#/components/responses/DomainPermissionRes" + "401": + description: Missing or invalid access token provided. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + /domains/{domainID}/enable: + post: + summary: Enables a domain + description: | + Enables a specific domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + description: Successfully enabled domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + /domains/{domainID}/disable: + post: + summary: Disable a domain + description: | + Disable a specific domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + description: Successfully disabled domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + /domains/{domainID}/freeze: + post: + summary: Freeze a domain + description: | + Freeze a specific domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + description: Successfully freezed domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + /domains/{domainID}/users/assign: + post: + summary: Assign users to domain + description: | + Assign users to domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + requestBody: + $ref: "#/components/requestBodies/AssignUserReq" + security: + - bearerAuth: [] + responses: + "200": + description: Users successfully assigned to domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "409": + description: Conflict of data. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + + /domains/{domainID}/users/unassign: + post: + summary: Unassign users from domain + description: | + Unassign users from domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "#/components/parameters/DomainID" + requestBody: + $ref: "#/components/requestBodies/UnassignUsersReq" + security: + - bearerAuth: [] + responses: + "200": + description: Users successfully unassigned from domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "409": + description: Conflict of data. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" /keys: post: tags: @@ -136,6 +421,39 @@ paths: description: Missing or invalid content type. "500": $ref: "#/components/responses/ServiceError" + /users/{memberID}/domains: + get: + tags: + - Domains + summary: List users in a group + description: | + Retrieves a list of users in a domain. 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: "users.yml#/components/parameters/MemberID" + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/Metadata" + - $ref: "#/components/parameters/Status" + security: + - bearerAuth: [] + responses: + "200": + $ref: "users.yml#/components/responses/UserPageRes" + "400": + description: Failed due to malformed query parameters. + "401": + description: | + Missing or invalid access token provided. + This endpoint is available only for administrators. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" /health: get: @@ -150,6 +468,164 @@ paths: components: schemas: + DomainReqObj: + type: object + properties: + name: + type: string + example: domainName + description: Domain name. + tags: + type: array + minItems: 0 + items: + type: string + example: ["tag1", "tag2"] + description: domain tags. + metadata: + type: object + example: { "domain": "example.com" } + description: Arbitrary, object-encoded domain's data. + alias: + type: string + example: domain alias + description: Domain alias. + required: + - name + - alias + Domain: + type: object + properties: + id: + type: string + format: uuid + example: bb7edb32-2eac-4aad-aebe-ed96fe073879 + description: Domain unique identifier. + name: + type: string + example: domainName + description: Domain name. + tags: + type: array + minItems: 0 + items: + type: string + example: ["tag1", "tag2"] + description: domain tags. + metadata: + type: object + example: { "domain": "example.com" } + description: Arbitrary, object-encoded domain's data. + alias: + type: string + example: domain alias + description: Domain alias. + status: + type: string + description: Domain Status + format: string + example: enabled + created_by: + type: string + format: uuid + example: "0d837f56-3f8a-4e2a-9359-6347d0fc9f06 " + description: User ID of the user who created the domain. + created_at: + type: string + format: date-time + example: "2019-11-26 13:31:52" + description: Time when the domain was created. + updated_by: + type: string + format: uuid + example: "80f66b77-ed74-4e74-9f88-6cce9a0a3049" + description: User ID of the user who last updated the domain. + updated_at: + type: string + format: date-time + example: "2019-11-26 13:31:52" + description: Time when the domain was last updated. + xml: + name: domain + + DomainsPage: + type: object + properties: + domains: + type: array + minItems: 0 + uniqueItems: true + items: + $ref: "#/components/schemas/Domain" + total: + type: integer + example: 1 + description: Total number of items. + offset: + type: integer + description: Number of items to skip during retrieval. + limit: + type: integer + example: 10 + description: Maximum number of items to return in one page. + required: + - domain + - total + - offset + DomainUpdate: + type: object + properties: + name: + type: string + example: domainName + description: Domain name. + tags: + type: array + minItems: 0 + items: + type: string + example: ["tag1", "tag2"] + description: domain tags. + metadata: + type: object + example: { "domain": "example.com" } + description: Arbitrary, object-encoded thing's data. + alias: + type: string + example: domain alias + description: Domain alias. + Permissions: + type: object + properties: + permissions: + type: array + minItems: 0 + items: + type: string + description: Permissions + + UserDomainRelationReq: + type: object + properties: + users_ids: + type: array + minItems: 1 + items: + type: string + description: Users IDs + example: + [ + "5dc1ce4b-7cc9-4f12-98a6-9d74cc4980bb", + "c01ed106-e52d-4aa4-bed3-39f360177cfa", + ] + relation: + type: string + enum: ["administrator", "editor","viewer","member"] + example: "administrator" + description: Policy relations. + required: + - users_ids + - relation Key: type: object properties: @@ -206,6 +682,40 @@ components: type: string parameters: + DomainID: + name: domainID + description: Unique domain identifier. + in: path + schema: + type: string + format: uuid + required: true + example: bb7edb32-2eac-4aad-aebe-ed96fe073879 + Status: + name: status + description: Domain status. + in: query + schema: + type: string + default: enabled + required: false + example: enabled + DomainName: + name: name + description: Domain's name. + in: query + schema: + type: string + required: false + example: "domainName" + Permission: + name: permission + description: permission. + in: query + schema: + type: string + required: false + example: "edit" ApiKeyId: name: keyID description: API Key ID. @@ -259,6 +769,36 @@ components: required: false requestBodies: + DomainCreateReq: + description: JSON-formatted document describing the new domain to be registered + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DomainReqObj" + DomainUpdateReq: + description: JSON-formated document describing the name, alias, tags, and metadata of the domain to be updated + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DomainUpdate" + AssignUserReq: + description: JSON-formated document describing the policy related to assigning users to a domain + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UserDomainRelationReq" + + UnassignUsersReq: + description: JSON-formated document describing the policy related to unassigning users to a domain + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UserDomainRelationReq" + KeyRequest: description: JSON-formatted document describing key request. required: true @@ -289,6 +829,38 @@ components: ServiceError: description: Unexpected server-side error occurred. + DomainCreateRes: + description: Create new domain. + headers: + Location: + schema: + type: string + format: url + description: Registered domain relative URL in the format `/domains/` + content: + application/json: + schema: + $ref: "#/components/schemas/Domain" + + DomainRes: + description: Data retrieved. + content: + application/json: + schema: + $ref: "#/components/schemas/Domain" + DomainPermissionRes: + description: Data retrieved. + content: + application/json: + schema: + $ref: "#/components/schemas/Permissions" + DomainsPageRes: + description: Data retrieved. + content: + application/json: + schema: + $ref: "#/components/schemas/DomainsPage" + KeyRes: description: Data retrieved. content: diff --git a/api/openapi/users.yml b/api/openapi/users.yml index d7af7fd09f..8aac147eba 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -819,7 +819,37 @@ paths: description: Group does not exist. "500": $ref: "#/components/responses/ServiceError" - + /domains/{domainID}/users: + get: + summary: List users assigned to domain + description: | + List users assigned to domain that is identified by the domain ID. + tags: + - Domains + parameters: + - $ref: "auth.yml#/components/parameters/DomainID" + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/Metadata" + - $ref: "#/components/parameters/Status" + security: + - bearerAuth: [] + responses: + "200": + $ref: "#/components/responses/UserPageRes" + description: List of users assigned to domain. + "400": + description: Failed due to malformed domain's ID. + "401": + description: Missing or invalid access token provided. + "403": + description: Unauthorized access the domain ID. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" /health: get: summary: Retrieves service health check info. From a10ab9329a71715611dc1c7c4a7398d03ed3c019 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:22:06 +0530 Subject: [PATCH 09/71] MG-1967 - Handle errors in Things gRPC server and client (#257) Signed-off-by: Arvindh --- things/api/grpc/client.go | 35 +++++++++++++++++++++++++++++++++-- things/api/grpc/server.go | 4 +++- things/service.go | 8 ++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/things/api/grpc/client.go b/things/api/grpc/client.go index 78eadf2b81..d0f2b6f309 100644 --- a/things/api/grpc/client.go +++ b/things/api/grpc/client.go @@ -5,12 +5,16 @@ package grpc import ( "context" + "fmt" "time" "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/errors" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const svcName = "magistrala.AuthzService" @@ -44,11 +48,11 @@ func (client grpcClient) Authorize(ctx context.Context, req *magistrala.Authoriz res, err := client.authorize(ctx, req) if err != nil { - return &magistrala.AuthorizeRes{}, err + return &magistrala.AuthorizeRes{}, decodeError(err) } ar := res.(authorizeRes) - return &magistrala.AuthorizeRes{Authorized: ar.authorized, Id: ar.id}, err + return &magistrala.AuthorizeRes{Authorized: ar.authorized, Id: ar.id}, nil } func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { @@ -69,3 +73,30 @@ func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{} Object: req.GetObject(), }, nil } + +func decodeError(err error) error { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.Unauthenticated: + return errors.Wrap(errors.ErrAuthentication, errors.New(st.Message())) + case codes.PermissionDenied: + return errors.Wrap(errors.ErrAuthorization, errors.New(st.Message())) + case codes.InvalidArgument: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.FailedPrecondition: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.NotFound: + return errors.Wrap(errors.ErrNotFound, errors.New(st.Message())) + case codes.AlreadyExists: + return errors.Wrap(errors.ErrConflict, errors.New(st.Message())) + case codes.OK: + if msg := st.Message(); msg != "" { + return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) + } + return nil + default: + return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) + } + } + return err +} diff --git a/things/api/grpc/server.go b/things/api/grpc/server.go index 456ab4bff4..48376d816b 100644 --- a/things/api/grpc/server.go +++ b/things/api/grpc/server.go @@ -10,6 +10,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/things" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc/codes" @@ -69,7 +70,8 @@ func encodeError(err error) error { err == apiutil.ErrMissingEmail, err == apiutil.ErrBearerToken: return status.Error(codes.Unauthenticated, err.Error()) - case errors.Contains(err, errors.ErrAuthorization): + case errors.Contains(err, errors.ErrAuthorization), + errors.Contains(err, svcerr.ErrAuthorization): return status.Error(codes.PermissionDenied, err.Error()) default: return status.Error(codes.Internal, err.Error()) diff --git a/things/service.go b/things/service.go index 1da19c87ec..e8a0284226 100644 --- a/things/service.go +++ b/things/service.go @@ -56,10 +56,10 @@ func (svc service) Authorize(ctx context.Context, req *magistrala.AuthorizeReq) } resp, err := svc.auth.Authorize(ctx, r) if err != nil { - return "", errors.Wrap(errors.ErrAuthorization, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !resp.GetAuthorized() { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return thingID, nil @@ -578,10 +578,10 @@ func (svc service) Identify(ctx context.Context, key string) (string, error) { client, err := svc.clients.RetrieveBySecret(ctx, key) if err != nil { - return "", errors.Wrap(repoerr.ErrNotFound, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if err := svc.clientCache.Save(ctx, key, client.ID); err != nil { - return "", errors.Wrap(repoerr.ErrUpdateEntity, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } return client.ID, nil From 32d43b193e4aae46d2bb0543445de9e317da5ea0 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 10 Jan 2024 22:40:56 +0530 Subject: [PATCH 10/71] NOISSUE - List Permissions when listing Users (#269) Signed-off-by: Arvindh --- auth/api/grpc/server.go | 1 - users/api/clients.go | 5 ++++ users/service.go | 52 +++++++++++++++++++++++++++++++++++++++++ users/service_test.go | 14 ++++++----- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index 04a6185643..e50c032996 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -40,7 +40,6 @@ var ( defDomainsFilterPermissions = []string{ auth.AdminPermission, - auth.DeletePermission, auth.EditPermission, auth.ViewPermission, auth.MembershipPermission, diff --git a/users/api/clients.go b/users/api/clients.go index 32dea1a2b9..a9f11d0ede 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -530,6 +530,10 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err if err != nil { return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } + lp, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) + if err != nil { + return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) + } return mgclients.Page{ Status: st, Offset: o, @@ -540,5 +544,6 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err Tag: t, Owner: oid, Permission: p, + ListPerms: lp, }, nil } diff --git a/users/service.go b/users/service.go index 2db117e7e9..04cfd02dcc 100644 --- a/users/service.go +++ b/users/service.go @@ -15,6 +15,7 @@ import ( repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/users/postgres" + "golang.org/x/sync/errgroup" ) var ( @@ -450,6 +451,10 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client } func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mgclients.Page) (mgclients.MembersPage, error) { + res, err := svc.identify(ctx, token) + if err != nil { + return mgclients.MembersPage{}, err + } var objectType string var authzPerm string switch objectKind { @@ -497,12 +502,51 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID return mgclients.MembersPage{}, errors.Wrap(repoerr.ErrNotFound, err) } + if pm.ListPerms && len(cp.Clients) > 0 { + g, ctx := errgroup.WithContext(ctx) + + for i := range cp.Clients { + // Copying loop variable "i" to avoid "loop variable captured by func literal" + iter := i + g.Go(func() error { + return svc.retrieveObjectUsersPermissions(ctx, res.GetDomainId(), objectType, objectID, &cp.Clients[iter]) + }) + } + + if err := g.Wait(); err != nil { + return mgclients.MembersPage{}, err + } + } + return mgclients.MembersPage{ Page: cp.Page, Members: cp.Clients, }, nil } +func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, client *mgclients.Client) error { + userID := auth.EncodeDomainUserID(domainID, client.ID) + permissions, err := svc.listObjectUserPermission(ctx, userID, objectType, objectID) + if err != nil { + return err + } + client.Permissions = permissions + return nil +} + +func (svc service) listObjectUserPermission(ctx context.Context, userID, objectType, objectID string) ([]string, error) { + lp, err := svc.auth.ListPermissions(ctx, &magistrala.ListPermissionsReq{ + SubjectType: auth.UserType, + Subject: userID, + Object: objectID, + ObjectType: objectType, + }) + if err != nil { + return []string{}, err + } + return lp.GetPermissions(), nil +} + func (svc *service) checkSuperAdmin(ctx context.Context, adminID string) error { if _, err := svc.authorize(ctx, auth.UserType, auth.UsersKind, adminID, auth.AdminPermission, auth.PlatformType, auth.MagistralaObject); err != nil { if err := svc.clients.CheckSuperAdmin(ctx, adminID); err != nil { @@ -514,6 +558,14 @@ func (svc *service) checkSuperAdmin(ctx context.Context, adminID string) error { return nil } +func (svc service) identify(ctx context.Context, token string) (*magistrala.IdentityRes, error) { + res, err := svc.auth.Identify(ctx, &magistrala.IdentityReq{Token: token}) + if err != nil { + return &magistrala.IdentityRes{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + return res, nil +} + func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, perm, objType, obj string) (string, error) { req := &magistrala.AuthorizeReq{ SubjectType: subjType, diff --git a/users/service_test.go b/users/service_test.go index 0fd93e3d2d..5c52482987 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -1298,7 +1298,7 @@ func TestListMembers(t *testing.T) { Limit: 0, }, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthentication, }, { desc: "list clients for an owner", @@ -1318,23 +1318,25 @@ func TestListMembers(t *testing.T) { } for _, tc := range cases { - repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true, Id: validID}, nil) + repoCall := auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, nil) if tc.token == inValidToken { - repoCall = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) } + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true, Id: validID}, nil) - repoCall1 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&magistrala.ListSubjectsRes{Policies: prefixClientUUIDSWithDomain(tc.response.Members)}, nil) - repoCall2 := cRepo.On("RetrieveAll", context.Background(), tc.page).Return(mgclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) + repoCall2 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&magistrala.ListSubjectsRes{Policies: prefixClientUUIDSWithDomain(tc.response.Members)}, nil) + repoCall3 := cRepo.On("RetrieveAll", context.Background(), tc.page).Return(mgclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) page, err := svc.ListMembers(context.Background(), tc.token, "groups", tc.groupID, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "RetrieveAll", context.Background(), tc.page) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveAll", context.Background(), tc.page) assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() + repoCall3.Unset() } } From d16e9ade1e78530f49f8204efcb05b09f0c0477e Mon Sep 17 00:00:00 2001 From: Ian Ngethe Muchiri <100555904+ianmuchyri@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:54:51 +0300 Subject: [PATCH 11/71] NOISSUE - Add pagemetadata to ReadMessage function in sdk (#2055) Signed-off-by: ianmuchyri --- cli/message.go | 15 ++++++++++++--- pkg/sdk/go/message.go | 14 +++++++++----- pkg/sdk/go/sdk.go | 8 ++++++-- pkg/sdk/mocks/sdk.go | 18 +++++++++--------- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/cli/message.go b/cli/message.go index 61058bb3b6..79f86571fb 100644 --- a/cli/message.go +++ b/cli/message.go @@ -3,7 +3,10 @@ package cli -import "github.com/spf13/cobra" +import ( + mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" + "github.com/spf13/cobra" +) var cmdMessages = []cobra.Command{ { @@ -27,14 +30,20 @@ var cmdMessages = []cobra.Command{ { Use: "read ", Short: "Read messages", - Long: `Reads all channel messages`, + Long: "Reads all channel messages\n" + + "Usage:\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 { logUsage(cmd.Use) return } + pageMetadata := mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } - m, err := sdk.ReadMessages(args[0], args[1]) + m, err := sdk.ReadMessages(pageMetadata, args[0], args[1]) if err != nil { logError(err) return diff --git a/pkg/sdk/go/message.go b/pkg/sdk/go/message.go index 5ad50af9fd..591436ef51 100644 --- a/pkg/sdk/go/message.go +++ b/pkg/sdk/go/message.go @@ -30,7 +30,7 @@ func (sdk mgSDK) SendMessage(chanName, msg, key string) errors.SDKError { return err } -func (sdk mgSDK) ReadMessages(chanName, token string) (MessagesPage, errors.SDKError) { +func (sdk mgSDK) ReadMessages(pm PageMetadata, chanName, token string) (MessagesPage, errors.SDKError) { chanNameParts := strings.SplitN(chanName, ".", channelParts) chanID := chanNameParts[0] subtopicPart := "" @@ -38,14 +38,18 @@ func (sdk mgSDK) ReadMessages(chanName, token string) (MessagesPage, errors.SDKE subtopicPart = fmt.Sprintf("?subtopic=%s", chanNameParts[1]) } - url := fmt.Sprintf("%s/channels/%s/messages%s", sdk.readerURL, chanID, subtopicPart) + readMessagesEndpoint := fmt.Sprintf("channels/%s/messages%s", chanID, subtopicPart) + url, err := sdk.withQueryParams(sdk.readerURL, readMessagesEndpoint, pm) + if err != nil { + return MessagesPage{}, errors.NewSDKError(err) + } header := make(map[string]string) header["Content-Type"] = string(sdk.msgContentType) - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, header, http.StatusOK) - if err != nil { - return MessagesPage{}, err + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, header, http.StatusOK) + if sdkerr != nil { + return MessagesPage{}, sdkerr } var mp MessagesPage diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 219e84a953..98e3e6aab5 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -828,9 +828,13 @@ type SDK interface { // ReadMessages read messages of specified channel. // // example: - // msgs, _ := sdk.ReadMessages("channelID", "token") + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // } + // msgs, _ := sdk.ReadMessages(pm,"channelID", "token") // fmt.Println(msgs) - ReadMessages(chanID, token string) (MessagesPage, errors.SDKError) + ReadMessages(pm PageMetadata, chanID, 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 8c58eac30c..e8eb18f56b 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -1804,9 +1804,9 @@ func (_m *SDK) Parents(id string, pm sdk.PageMetadata, token string) (sdk.Groups return r0, r1 } -// ReadMessages provides a mock function with given fields: chanID, token -func (_m *SDK) ReadMessages(chanID string, token string) (sdk.MessagesPage, errors.SDKError) { - ret := _m.Called(chanID, token) +// ReadMessages provides a mock function with given fields: pm, chanID, token +func (_m *SDK) ReadMessages(pm sdk.PageMetadata, chanID string, token string) (sdk.MessagesPage, errors.SDKError) { + ret := _m.Called(pm, chanID, token) if len(ret) == 0 { panic("no return value specified for ReadMessages") @@ -1814,17 +1814,17 @@ func (_m *SDK) ReadMessages(chanID string, token string) (sdk.MessagesPage, erro var r0 sdk.MessagesPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.MessagesPage, errors.SDKError)); ok { - return rf(chanID, token) + if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.MessagesPage, errors.SDKError)); ok { + return rf(pm, chanID, token) } - if rf, ok := ret.Get(0).(func(string, string) sdk.MessagesPage); ok { - r0 = rf(chanID, token) + if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.MessagesPage); ok { + r0 = rf(pm, chanID, token) } else { r0 = ret.Get(0).(sdk.MessagesPage) } - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(chanID, token) + if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string, string) errors.SDKError); ok { + r1 = rf(pm, chanID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) From 52b650f17393b3f6d83274b8394912372e852672 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:03:55 +0530 Subject: [PATCH 12/71] NOISSUE - Fix Domain listing and updating (#271) Signed-off-by: Arvindh --- auth/postgres/domains.go | 2 +- auth/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index e6d8d8a6b1..b0e5556525 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -552,7 +552,7 @@ func buildPageQuery(pm auth.Page) (string, error) { query = append(query, fmt.Sprintf("d.id IN ('%s')", strings.Join(pm.IDs, "','"))) } - if (pm.Status >= auth.EnabledStatus) && (pm.Status <= auth.AllStatus) { + if (pm.Status >= auth.EnabledStatus) && (pm.Status < auth.AllStatus) { query = append(query, "d.status = :status") } else { query = append(query, fmt.Sprintf("d.status < %d", auth.AllStatus)) diff --git a/auth/service.go b/auth/service.go index 3938a05acd..a277a0ebea 100644 --- a/auth/service.go +++ b/auth/service.go @@ -629,7 +629,7 @@ func (svc service) UpdateDomain(ctx context.Context, token, id string, d DomainR return Domain{}, errors.Wrap(svcerr.ErrAuthorization, err) } - dom, err := svc.domains.RetrieveByID(ctx, id) + dom, err := svc.domains.Update(ctx, id, key.User, d) if err != nil { return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } From 53d5e8b879dd0f622ff607491c8c0c169463e99e Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:41:09 +0300 Subject: [PATCH 13/71] NOISSUE - Add group API tests (#243) Signed-off-by: rodneyosodo --- .github/workflows/check-generated-files.yml | 5 + internal/groups/api/decode.go | 17 +- internal/groups/api/decode_test.go | 794 +++++++++++++++++ internal/groups/api/endpoint_test.go | 921 ++++++++++++++++++++ internal/groups/api/endpoints.go | 50 +- internal/groups/api/requests.go | 9 +- internal/groups/api/requests_test.go | 516 +++++++++++ internal/groups/api/responses.go | 65 +- pkg/groups/groups.go | 8 +- {internal => pkg}/groups/mocks/doc.go | 0 10 files changed, 2299 insertions(+), 86 deletions(-) create mode 100644 internal/groups/api/decode_test.go create mode 100644 internal/groups/api/endpoint_test.go create mode 100644 internal/groups/api/requests_test.go rename {internal => pkg}/groups/mocks/doc.go (100%) diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index 49c4c00d95..d55e2d3a6b 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -51,6 +51,7 @@ jobs: - "auth/policies.go" - "pkg/events/events.go" - "provision/service.go" + - "pkg/groups/groups.go" - name: Set up protoc if: steps.changes.outputs.proto == 'true' @@ -111,6 +112,8 @@ jobs: mv ./pkg/events/mocks/publisher.go ./pkg/events/mocks/publisher.go.tmp mv ./pkg/events/mocks/subscriber.go ./pkg/events/mocks/subscriber.go.tmp mv ./provision/mocks/service.go ./provision/mocks/service.go.tmp + mv ./pkg/groups/mocks/repository.go ./pkg/groups/mocks/repository.go.tmp + mv ./pkg/groups/mocks/service.go ./pkg/groups/mocks/service.go.tmp make mocks @@ -140,3 +143,5 @@ jobs: check_mock_changes ./pkg/events/mocks/publisher.go "ES Publisher ./pkg/events/mocks/publisher.go" check_mock_changes ./pkg/events/mocks/subscriber.go "EE Subscriber ./pkg/events/mocks/subscriber.go" check_mock_changes ./provision/mocks/service.go "Provision Service ./provision/mocks/service.go" + check_mock_changes ./pkg/groups/mocks/repository.go "Groups Repository ./pkg/groups/mocks/repository.go" + check_mock_changes ./pkg/groups/mocks/service.go "Groups Service ./pkg/groups/mocks/service.go" diff --git a/internal/groups/api/decode.go b/internal/groups/api/decode.go index 059be6ced4..97377ebd6a 100644 --- a/internal/groups/api/decode.go +++ b/internal/groups/api/decode.go @@ -17,11 +17,6 @@ import ( "github.com/go-chi/chi/v5" ) -const ( - defRelation = "viewer" - defPermission = "view" -) - func DecodeListGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { pm, err := decodePageMeta(r) if err != nil { @@ -211,6 +206,9 @@ func DecodeChangeGroupStatus(_ context.Context, r *http.Request) (interface{}, e } func DecodeAssignMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } req := assignReq{ token: apiutil.ExtractBearerToken(r), groupID: chi.URLParam(r, "groupID"), @@ -222,6 +220,9 @@ func DecodeAssignMembersRequest(_ context.Context, r *http.Request) (interface{} } func DecodeUnassignMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } req := unassignReq{ token: apiutil.ExtractBearerToken(r), groupID: chi.URLParam(r, "groupID"), @@ -235,11 +236,11 @@ func DecodeUnassignMembersRequest(_ context.Context, r *http.Request) (interface func DecodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { memberKind, err := apiutil.ReadStringQuery(r, api.MemberKindKey, "") if err != nil { - return nil, apiutil.ErrInvalidQueryParams + return nil, errors.Wrap(apiutil.ErrValidation, err) } - permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, defPermission) + permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) if err != nil { - return nil, apiutil.ErrInvalidQueryParams + return nil, errors.Wrap(apiutil.ErrValidation, err) } req := listMembersReq{ token: apiutil.ExtractBearerToken(r), diff --git a/internal/groups/api/decode_test.go b/internal/groups/api/decode_test.go new file mode 100644 index 0000000000..b8ec6e7cd8 --- /dev/null +++ b/internal/groups/api/decode_test.go @@ -0,0 +1,794 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/groups" + "github.com/stretchr/testify/assert" +) + +func TestDecodeListGroupsRequest(t *testing.T) { + cases := []struct { + desc string + url string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request with no parameters", + url: "http://localhost:8080", + header: map[string][]string{}, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + Permission: api.DefPermission, + Direction: -1, + }, + }, + err: nil, + }, + { + desc: "valid request with all parameters", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + OwnerID: "random", + Name: "random", + Metadata: clients.Metadata{ + "test": "test", + }, + }, + Level: 2, + ID: "random", + Permission: "random", + Direction: -1, + ListPerms: true, + }, + token: "123", + tree: true, + memberKind: "random", + }, + err: nil, + }, + { + desc: "valid request with invalid page metadata", + url: "http://localhost:8080?metadata=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid level", + url: "http://localhost:8080?level=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid parent", + url: "http://localhost:8080?parent_id=random&parent_id=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid tree", + url: "http://localhost:8080?tree=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid dir", + url: "http://localhost:8080?dir=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid member kind", + url: "http://localhost:8080?member_kind=random&member_kind=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid permission", + url: "http://localhost:8080?permission=random&permission=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid list permission", + url: "http://localhost:8080?&list_perms=random", + resp: nil, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + parsedURL, err := url.Parse(tc.url) + assert.NoError(t, err) + + req := &http.Request{ + URL: parsedURL, + Header: tc.header, + } + resp, err := DecodeListGroupsRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeListParentsRequest(t *testing.T) { + cases := []struct { + desc string + url string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request with no parameters", + url: "http://localhost:8080", + header: map[string][]string{}, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + Permission: api.DefPermission, + Direction: +1, + }, + }, + err: nil, + }, + { + desc: "valid request with all parameters", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + OwnerID: "random", + Name: "random", + Metadata: clients.Metadata{ + "test": "test", + }, + }, + Level: 2, + Permission: "random", + Direction: +1, + ListPerms: true, + }, + token: "123", + tree: true, + }, + err: nil, + }, + { + desc: "valid request with invalid page metadata", + url: "http://localhost:8080?metadata=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid level", + url: "http://localhost:8080?level=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid tree", + url: "http://localhost:8080?tree=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid permission", + url: "http://localhost:8080?permission=random&permission=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid list permission", + url: "http://localhost:8080?&list_perms=random", + resp: nil, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + parsedURL, err := url.Parse(tc.url) + assert.NoError(t, err) + + req := &http.Request{ + URL: parsedURL, + Header: tc.header, + } + resp, err := DecodeListParentsRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeListChildrenRequest(t *testing.T) { + cases := []struct { + desc string + url string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request with no parameters", + url: "http://localhost:8080", + header: map[string][]string{}, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + Permission: api.DefPermission, + Direction: -1, + }, + }, + err: nil, + }, + { + desc: "valid request with all parameters", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + OwnerID: "random", + Name: "random", + Metadata: clients.Metadata{ + "test": "test", + }, + }, + Level: 2, + Permission: "random", + Direction: -1, + ListPerms: true, + }, + token: "123", + tree: true, + }, + err: nil, + }, + { + desc: "valid request with invalid page metadata", + url: "http://localhost:8080?metadata=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid level", + url: "http://localhost:8080?level=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid tree", + url: "http://localhost:8080?tree=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid permission", + url: "http://localhost:8080?permission=random&permission=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid list permission", + url: "http://localhost:8080?&list_perms=random", + resp: nil, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + parsedURL, err := url.Parse(tc.url) + assert.NoError(t, err) + + req := &http.Request{ + URL: parsedURL, + Header: tc.header, + } + resp, err := DecodeListChildrenRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeListMembersRequest(t *testing.T) { + cases := []struct { + desc string + url string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request with no parameters", + url: "http://localhost:8080", + header: map[string][]string{}, + resp: listMembersReq{ + permission: api.DefPermission, + }, + err: nil, + }, + { + desc: "valid request with all parameters", + url: "http://localhost:8080?member_kind=random&permission=random", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: listMembersReq{ + token: "123", + memberKind: "random", + permission: "random", + }, + err: nil, + }, + { + desc: "valid request with invalid permission", + url: "http://localhost:8080?permission=random&permission=random", + resp: nil, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid member kind", + url: "http://localhost:8080?member_kind=random&member_kind=random", + resp: nil, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + parsedURL, err := url.Parse(tc.url) + assert.NoError(t, err) + + req := &http.Request{ + URL: parsedURL, + Header: tc.header, + } + resp, err := DecodeListMembersRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodePageMeta(t *testing.T) { + cases := []struct { + desc string + url string + resp groups.PageMeta + err error + }{ + { + desc: "valid request with no parameters", + url: "http://localhost:8080", + resp: groups.PageMeta{ + Limit: 10, + }, + err: nil, + }, + { + desc: "valid request with all parameters", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}", + resp: groups.PageMeta{ + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + OwnerID: "random", + Name: "random", + Metadata: clients.Metadata{ + "test": "test", + }, + }, + err: nil, + }, + { + desc: "valid request with invalid status", + url: "http://localhost:8080?status=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid status duplicated", + url: "http://localhost:8080?status=random&status=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid offset", + url: "http://localhost:8080?offset=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid limit", + url: "http://localhost:8080?limit=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid owner_id", + url: "http://localhost:8080?owner_id=random&owner_id=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid name", + url: "http://localhost:8080?name=random&name=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + { + desc: "valid request with invalid page metadata", + url: "http://localhost:8080?metadata=random", + resp: groups.PageMeta{}, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + parsedURL, err := url.Parse(tc.url) + assert.NoError(t, err) + + req := &http.Request{URL: parsedURL} + resp, err := decodePageMeta(req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeGroupCreate(t *testing.T) { + cases := []struct { + desc string + body string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + body: `{"name": "random", "description": "random"}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: createGroupReq{ + Group: groups.Group{ + Name: "random", + Description: "random", + }, + token: "123", + }, + err: nil, + }, + { + desc: "invalid content type", + body: `{"name": "random", "description": "random"}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {"text/plain"}, + }, + resp: nil, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "invalid request body", + body: `data`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: nil, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodPost, "http://localhost:8080", strings.NewReader(tc.body)) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeGroupCreate(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeGroupUpdate(t *testing.T) { + cases := []struct { + desc string + body string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + body: `{"name": "random", "description": "random"}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: updateGroupReq{ + Name: "random", + Description: "random", + token: "123", + }, + err: nil, + }, + { + desc: "invalid content type", + body: `{"name": "random", "description": "random"}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {"text/plain"}, + }, + resp: nil, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "invalid request body", + body: `data`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: nil, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodPut, "http://localhost:8080", strings.NewReader(tc.body)) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeGroupUpdate(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeGroupRequest(t *testing.T) { + cases := []struct { + desc string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: groupReq{ + token: "123", + }, + err: nil, + }, + { + desc: "empty token", + resp: groupReq{}, + err: nil, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", http.NoBody) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeGroupRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeGroupPermsRequest(t *testing.T) { + cases := []struct { + desc string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: groupPermsReq{ + token: "123", + }, + err: nil, + }, + { + desc: "empty token", + resp: groupPermsReq{}, + err: nil, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", http.NoBody) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeGroupPermsRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeChangeGroupStatus(t *testing.T) { + cases := []struct { + desc string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + }, + resp: changeGroupStatusReq{ + token: "123", + }, + err: nil, + }, + { + desc: "empty token", + resp: changeGroupStatusReq{}, + err: nil, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", http.NoBody) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeChangeGroupStatus(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeAssignMembersRequest(t *testing.T) { + cases := []struct { + desc string + body string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + body: `{"member_kind": "random", "members": ["random"]}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: assignReq{ + MemberKind: "random", + Members: []string{"random"}, + token: "123", + }, + err: nil, + }, + { + desc: "invalid content type", + body: `{"member_kind": "random", "members": ["random"]}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {"text/plain"}, + }, + resp: nil, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "invalid request body", + body: `data`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: nil, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodPost, "http://localhost:8080", strings.NewReader(tc.body)) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeAssignMembersRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} + +func TestDecodeUnassignMembersRequest(t *testing.T) { + cases := []struct { + desc string + body string + header map[string][]string + resp interface{} + err error + }{ + { + desc: "valid request", + body: `{"member_kind": "random", "members": ["random"]}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: unassignReq{ + MemberKind: "random", + Members: []string{"random"}, + token: "123", + }, + err: nil, + }, + { + desc: "invalid content type", + body: `{"member_kind": "random", "members": ["random"]}`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {"text/plain"}, + }, + resp: nil, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "invalid request body", + body: `data`, + header: map[string][]string{ + "Authorization": {"Bearer 123"}, + "Content-Type": {api.ContentType}, + }, + resp: nil, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + req, err := http.NewRequest(http.MethodPost, "http://localhost:8080", strings.NewReader(tc.body)) + assert.NoError(t, err) + req.Header = tc.header + resp, err := DecodeUnassignMembersRequest(context.Background(), req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + } +} diff --git a/internal/groups/api/endpoint_test.go b/internal/groups/api/endpoint_test.go new file mode 100644 index 0000000000..6444d16f97 --- /dev/null +++ b/internal/groups/api/endpoint_test.go @@ -0,0 +1,921 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/groups/mocks" + "github.com/stretchr/testify/assert" +) + +var validGroupResp = groups.Group{ + ID: testsutil.GenerateUUID(&testing.T{}), + Name: valid, + Description: valid, + Owner: testsutil.GenerateUUID(&testing.T{}), + Parent: testsutil.GenerateUUID(&testing.T{}), + Metadata: clients.Metadata{ + "name": "test", + }, + Children: []*groups.Group{}, + CreatedAt: time.Now().Add(-1 * time.Second), + UpdatedAt: time.Now(), + UpdatedBy: testsutil.GenerateUUID(&testing.T{}), + Status: clients.EnabledStatus, +} + +func TestCreateGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + kind string + req createGroupReq + svcResp groups.Group + svcErr error + resp createGroupRes + err error + }{ + { + desc: "successfully with groups kind", + kind: auth.NewGroupKind, + req: createGroupReq{ + token: valid, + Group: groups.Group{ + Name: valid, + }, + }, + svcResp: validGroupResp, + svcErr: nil, + resp: createGroupRes{created: true, Group: validGroupResp}, + err: nil, + }, + { + desc: "successfully with channels kind", + kind: auth.NewChannelKind, + req: createGroupReq{ + token: valid, + Group: groups.Group{ + Name: valid, + }, + }, + svcResp: validGroupResp, + svcErr: nil, + resp: createGroupRes{created: true, Group: validGroupResp}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + kind: auth.NewGroupKind, + req: createGroupReq{ + Group: groups.Group{ + Name: valid, + }, + }, + resp: createGroupRes{created: false}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + kind: auth.NewGroupKind, + req: createGroupReq{ + token: valid, + Group: groups.Group{ + Name: valid, + }, + }, + svcResp: groups.Group{}, + svcErr: errors.ErrAuthorization, + resp: createGroupRes{created: false}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("CreateGroup", context.Background(), tc.req.token, tc.kind, tc.req.Group).Return(tc.svcResp, tc.svcErr) + resp, err := CreateGroupEndpoint(svc, tc.kind)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(createGroupRes) + switch err { + case nil: + assert.Equal(t, response.Code(), http.StatusCreated) + assert.Equal(t, response.Headers()["Location"], fmt.Sprintf("/groups/%s", response.ID)) + default: + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + } + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestViewGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req groupReq + svcResp groups.Group + svcErr error + resp viewGroupRes + err error + }{ + { + desc: "successfully", + req: groupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: validGroupResp, + svcErr: nil, + resp: viewGroupRes{Group: validGroupResp}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: groupReq{ + id: testsutil.GenerateUUID(t), + }, + resp: viewGroupRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: groupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: groups.Group{}, + svcErr: errors.ErrAuthorization, + resp: viewGroupRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("ViewGroup", context.Background(), tc.req.token, tc.req.id).Return(tc.svcResp, tc.svcErr) + resp, err := ViewGroupEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(viewGroupRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestViewGroupPermsEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req groupPermsReq + svcResp []string + svcErr error + resp viewGroupPermsRes + err error + }{ + { + desc: "successfully", + req: groupPermsReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: []string{ + valid, + }, + svcErr: nil, + resp: viewGroupPermsRes{Permissions: []string{valid}}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: groupPermsReq{ + id: testsutil.GenerateUUID(t), + }, + resp: viewGroupPermsRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: groupPermsReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: []string{}, + svcErr: errors.ErrAuthorization, + resp: viewGroupPermsRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("ViewGroupPerms", context.Background(), tc.req.token, tc.req.id).Return(tc.svcResp, tc.svcErr) + resp, err := ViewGroupPermsEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(viewGroupPermsRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestEnableGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req changeGroupStatusReq + svcResp groups.Group + svcErr error + resp changeStatusRes + err error + }{ + { + desc: "successfully", + req: changeGroupStatusReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: validGroupResp, + svcErr: nil, + resp: changeStatusRes{Group: validGroupResp}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: changeGroupStatusReq{ + id: testsutil.GenerateUUID(t), + }, + resp: changeStatusRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: changeGroupStatusReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: groups.Group{}, + svcErr: errors.ErrAuthorization, + resp: changeStatusRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("EnableGroup", context.Background(), tc.req.token, tc.req.id).Return(tc.svcResp, tc.svcErr) + resp, err := EnableGroupEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(changeStatusRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestDisableGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req changeGroupStatusReq + svcResp groups.Group + svcErr error + resp changeStatusRes + err error + }{ + { + desc: "successfully", + req: changeGroupStatusReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: validGroupResp, + svcErr: nil, + resp: changeStatusRes{Group: validGroupResp}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: changeGroupStatusReq{ + id: testsutil.GenerateUUID(t), + }, + resp: changeStatusRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: changeGroupStatusReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcResp: groups.Group{}, + svcErr: errors.ErrAuthorization, + resp: changeStatusRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("DisableGroup", context.Background(), tc.req.token, tc.req.id).Return(tc.svcResp, tc.svcErr) + resp, err := DisableGroupEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(changeStatusRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestDeleteGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req groupReq + svcErr error + resp deleteGroupRes + err error + }{ + { + desc: "successfully", + req: groupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcErr: nil, + resp: deleteGroupRes{deleted: true}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: groupReq{ + id: testsutil.GenerateUUID(t), + }, + resp: deleteGroupRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: groupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + }, + svcErr: errors.ErrAuthorization, + resp: deleteGroupRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := svc.On("DeleteGroup", context.Background(), tc.req.token, tc.req.id).Return(tc.svcErr) + resp, err := DeleteGroupEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(deleteGroupRes) + switch err { + case nil: + assert.Equal(t, response.Code(), http.StatusNoContent) + default: + assert.Equal(t, response.Code(), http.StatusBadRequest) + } + assert.Empty(t, response.Headers()) + assert.True(t, response.Empty()) + repoCall.Unset() + } +} + +func TestUpdateGroupEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + req updateGroupReq + svcResp groups.Group + svcErr error + resp updateGroupRes + err error + }{ + { + desc: "successfully", + req: updateGroupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + Name: valid, + }, + svcResp: validGroupResp, + svcErr: nil, + resp: updateGroupRes{Group: validGroupResp}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + req: updateGroupReq{ + id: testsutil.GenerateUUID(t), + Name: valid, + }, + resp: updateGroupRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + req: updateGroupReq{ + token: valid, + id: testsutil.GenerateUUID(t), + Name: valid, + }, + svcResp: groups.Group{}, + svcErr: errors.ErrAuthorization, + resp: updateGroupRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + group := groups.Group{ + ID: tc.req.id, + Name: tc.req.Name, + Description: tc.req.Description, + Metadata: tc.req.Metadata, + } + repoCall := svc.On("UpdateGroup", context.Background(), tc.req.token, group).Return(tc.svcResp, tc.svcErr) + resp, err := UpdateGroupEndpoint(svc)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(updateGroupRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestListGroupsEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + memberKind string + req listGroupsReq + svcResp groups.Page + svcErr error + resp groupPageRes + err error + }{ + { + desc: "successfully", + memberKind: auth.ThingsKind, + req: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + token: valid, + memberKind: auth.ThingsKind, + memberID: testsutil.GenerateUUID(t), + }, + svcResp: groups.Page{ + Groups: []groups.Group{validGroupResp}, + }, + svcErr: nil, + resp: groupPageRes{ + Groups: []viewGroupRes{ + { + Group: validGroupResp, + }, + }, + }, + err: nil, + }, + { + desc: "successfully with empty member kind", + req: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + token: valid, + memberKind: auth.ThingsKind, + memberID: testsutil.GenerateUUID(t), + }, + svcResp: groups.Page{ + Groups: []groups.Group{validGroupResp}, + }, + svcErr: nil, + resp: groupPageRes{ + Groups: []viewGroupRes{ + { + Group: validGroupResp, + }, + }, + }, + err: nil, + }, + { + desc: "successfully with tree", + memberKind: auth.ThingsKind, + req: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + tree: true, + token: valid, + memberKind: auth.ThingsKind, + memberID: testsutil.GenerateUUID(t), + }, + svcResp: groups.Page{ + Groups: []groups.Group{validGroupResp}, + }, + svcErr: nil, + resp: groupPageRes{ + Groups: []viewGroupRes{ + { + Group: validGroupResp, + }, + }, + }, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + memberKind: auth.ThingsKind, + req: listGroupsReq{}, + resp: groupPageRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + memberKind: auth.ThingsKind, + req: listGroupsReq{ + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + token: valid, + memberKind: auth.ThingsKind, + memberID: testsutil.GenerateUUID(t), + }, + svcResp: groups.Page{}, + svcErr: errors.ErrAuthorization, + resp: groupPageRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + if tc.memberKind != "" { + tc.req.memberKind = tc.memberKind + } + repoCall := svc.On("ListGroups", context.Background(), tc.req.token, tc.req.memberKind, tc.req.memberID, tc.req.Page).Return(tc.svcResp, tc.svcErr) + resp, err := ListGroupsEndpoint(svc, tc.memberKind)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(groupPageRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestListMembersEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + memberKind string + req listMembersReq + svcResp groups.MembersPage + svcErr error + resp listMembersRes + err error + }{ + { + desc: "successfully", + memberKind: auth.ThingsKind, + req: listMembersReq{ + token: valid, + memberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + }, + svcResp: groups.MembersPage{ + Members: []groups.Member{ + { + ID: valid, + Type: valid, + }, + }, + }, + svcErr: nil, + resp: listMembersRes{ + Members: []groups.Member{ + { + ID: valid, + Type: valid, + }, + }, + }, + err: nil, + }, + { + desc: "successfully with empty member kind", + req: listMembersReq{ + token: valid, + memberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + }, + svcResp: groups.MembersPage{ + Members: []groups.Member{ + { + ID: valid, + Type: valid, + }, + }, + }, + svcErr: nil, + resp: listMembersRes{ + Members: []groups.Member{ + { + ID: valid, + Type: valid, + }, + }, + }, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + memberKind: auth.ThingsKind, + req: listMembersReq{}, + resp: listMembersRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + memberKind: auth.ThingsKind, + req: listMembersReq{ + token: valid, + memberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + }, + svcResp: groups.MembersPage{}, + svcErr: errors.ErrAuthorization, + resp: listMembersRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + if tc.memberKind != "" { + tc.req.memberKind = tc.memberKind + } + repoCall := svc.On("ListMembers", context.Background(), tc.req.token, tc.req.groupID, tc.req.permission, tc.req.memberKind).Return(tc.svcResp, tc.svcErr) + resp, err := ListMembersEndpoint(svc, tc.memberKind)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(listMembersRes) + assert.Equal(t, response.Code(), http.StatusOK) + assert.Empty(t, response.Headers()) + assert.False(t, response.Empty()) + repoCall.Unset() + } +} + +func TestAssignMembersEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + relation string + memberKind string + req assignReq + svcErr error + resp assignRes + err error + }{ + { + desc: "successfully", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: assignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: assignRes{assigned: true}, + err: nil, + }, + { + desc: "successfully with empty member kind", + relation: auth.ViewerRelation, + req: assignReq{ + token: valid, + groupID: testsutil.GenerateUUID(t), + MemberKind: auth.ThingsKind, + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: assignRes{assigned: true}, + err: nil, + }, + { + desc: "successfully with empty relation", + memberKind: auth.ThingsKind, + req: assignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: assignRes{assigned: true}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: assignReq{}, + resp: assignRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: assignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: errors.ErrAuthorization, + resp: assignRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + if tc.memberKind != "" { + tc.req.MemberKind = tc.memberKind + } + if tc.relation != "" { + tc.req.Relation = tc.relation + } + repoCall := svc.On("Assign", context.Background(), tc.req.token, tc.req.groupID, tc.req.Relation, tc.req.MemberKind, tc.req.Members).Return(tc.svcErr) + resp, err := AssignMembersEndpoint(svc, tc.relation, tc.memberKind)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(assignRes) + switch err { + case nil: + assert.Equal(t, response.Code(), http.StatusCreated) + default: + assert.Equal(t, response.Code(), http.StatusBadRequest) + } + assert.Empty(t, response.Headers()) + assert.True(t, response.Empty()) + repoCall.Unset() + } +} + +func TestUnassignMembersEndpoint(t *testing.T) { + svc := new(mocks.Service) + cases := []struct { + desc string + relation string + memberKind string + req unassignReq + svcErr error + resp unassignRes + err error + }{ + { + desc: "successfully", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: unassignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: unassignRes{unassigned: true}, + err: nil, + }, + { + desc: "successfully with empty member kind", + relation: auth.ViewerRelation, + req: unassignReq{ + token: valid, + groupID: testsutil.GenerateUUID(t), + MemberKind: auth.ThingsKind, + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: unassignRes{unassigned: true}, + err: nil, + }, + { + desc: "successfully with empty relation", + memberKind: auth.ThingsKind, + req: unassignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: nil, + resp: unassignRes{unassigned: true}, + err: nil, + }, + { + desc: "unsuccessfully with invalid request", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: unassignReq{}, + resp: unassignRes{}, + err: apiutil.ErrValidation, + }, + { + desc: "unsuccessfully with repo error", + relation: auth.ViewerRelation, + memberKind: auth.ThingsKind, + req: unassignReq{ + token: valid, + MemberKind: auth.ThingsKind, + groupID: testsutil.GenerateUUID(t), + Members: []string{ + testsutil.GenerateUUID(t), + testsutil.GenerateUUID(t), + }, + }, + svcErr: errors.ErrAuthorization, + resp: unassignRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + if tc.memberKind != "" { + tc.req.MemberKind = tc.memberKind + } + if tc.relation != "" { + tc.req.Relation = tc.relation + } + repoCall := svc.On("Unassign", context.Background(), tc.req.token, tc.req.groupID, tc.req.Relation, tc.req.MemberKind, tc.req.Members).Return(tc.svcErr) + resp, err := UnassignMembersEndpoint(svc, tc.relation, tc.memberKind)(context.Background(), tc.req) + assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) + response := resp.(unassignRes) + switch err { + case nil: + assert.Equal(t, response.Code(), http.StatusCreated) + default: + assert.Equal(t, response.Code(), http.StatusBadRequest) + } + assert.Empty(t, response.Headers()) + assert.True(t, response.Empty()) + repoCall.Unset() + } +} diff --git a/internal/groups/api/endpoints.go b/internal/groups/api/endpoints.go index 8a3e60ca2d..be436343e2 100644 --- a/internal/groups/api/endpoints.go +++ b/internal/groups/api/endpoints.go @@ -16,12 +16,12 @@ func CreateGroupEndpoint(svc groups.Service, kind string) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(createGroupReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return createGroupRes{created: false}, errors.Wrap(apiutil.ErrValidation, err) } group, err := svc.CreateGroup(ctx, req.token, kind, req.Group) if err != nil { - return nil, err + return createGroupRes{created: false}, err } return createGroupRes{created: true, Group: group}, nil @@ -32,12 +32,12 @@ func ViewGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(groupReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return viewGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } group, err := svc.ViewGroup(ctx, req.token, req.id) if err != nil { - return nil, err + return viewGroupRes{}, err } return viewGroupRes{Group: group}, nil @@ -48,12 +48,12 @@ func ViewGroupPermsEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(groupPermsReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return viewGroupPermsRes{}, errors.Wrap(apiutil.ErrValidation, err) } p, err := svc.ViewGroupPerms(ctx, req.token, req.id) if err != nil { - return nil, err + return viewGroupPermsRes{}, err } return viewGroupPermsRes{Permissions: p}, nil @@ -64,7 +64,7 @@ func UpdateGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(updateGroupReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return updateGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } group := groups.Group{ @@ -76,7 +76,7 @@ func UpdateGroupEndpoint(svc groups.Service) endpoint.Endpoint { group, err := svc.UpdateGroup(ctx, req.token, group) if err != nil { - return nil, err + return updateGroupRes{}, err } return updateGroupRes{Group: group}, nil @@ -87,11 +87,11 @@ func EnableGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(changeGroupStatusReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err) } group, err := svc.EnableGroup(ctx, req.token, req.id) if err != nil { - return nil, err + return changeStatusRes{}, err } return changeStatusRes{Group: group}, nil } @@ -101,11 +101,11 @@ func DisableGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(changeGroupStatusReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err) } group, err := svc.DisableGroup(ctx, req.token, req.id) if err != nil { - return nil, err + return changeStatusRes{}, err } return changeStatusRes{Group: group}, nil } @@ -118,11 +118,11 @@ func ListGroupsEndpoint(svc groups.Service, memberKind string) endpoint.Endpoint req.memberKind = memberKind } if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return groupPageRes{}, errors.Wrap(apiutil.ErrValidation, err) } page, err := svc.ListGroups(ctx, req.token, req.memberKind, req.memberID, req.Page) if err != nil { - return nil, err + return groupPageRes{}, err } if req.tree { @@ -140,12 +140,12 @@ func ListMembersEndpoint(svc groups.Service, memberKind string) endpoint.Endpoin req.memberKind = memberKind } if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return listMembersRes{}, errors.Wrap(apiutil.ErrValidation, err) } page, err := svc.ListMembers(ctx, req.token, req.groupID, req.permission, req.memberKind) if err != nil { - return nil, err + return listMembersRes{}, err } return listMembersRes{ @@ -169,12 +169,12 @@ func AssignMembersEndpoint(svc groups.Service, relation, memberKind string) endp req.MemberKind = memberKind } if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return assignRes{}, errors.Wrap(apiutil.ErrValidation, err) } if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return nil, err + return assignRes{}, err } - return assignRes{}, nil + return assignRes{assigned: true}, nil } } @@ -188,13 +188,13 @@ func UnassignMembersEndpoint(svc groups.Service, relation, memberKind string) en req.MemberKind = memberKind } if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return unassignRes{}, errors.Wrap(apiutil.ErrValidation, err) } if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return nil, err + return unassignRes{}, err } - return unassignRes{}, nil + return unassignRes{unassigned: true}, nil } } @@ -202,12 +202,12 @@ func DeleteGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(groupReq) if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return deleteGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } if err := svc.DeleteGroup(ctx, req.token, req.id); err != nil { - return nil, err + return deleteGroupRes{}, err } - return deleteGroupRes{}, nil + return deleteGroupRes{deleted: true}, nil } } diff --git a/internal/groups/api/requests.go b/internal/groups/api/requests.go index 246eaa0e8b..450cd2d1b9 100644 --- a/internal/groups/api/requests.go +++ b/internal/groups/api/requests.go @@ -4,15 +4,12 @@ package api import ( + "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" mggroups "github.com/absmach/magistrala/pkg/groups" ) -const ( - thingsKind = "things" -) - type createGroupReq struct { mggroups.Group token string @@ -67,10 +64,10 @@ func (req listGroupsReq) validate() error { if req.memberKind == "" { return apiutil.ErrMissingMemberKind } - if req.memberKind == thingsKind && req.memberID == "" { + if req.memberKind == auth.ThingsKind && req.memberID == "" { return apiutil.ErrMissingID } - if req.Level < mggroups.MinLevel || req.Level > mggroups.MaxLevel { + if req.Level > mggroups.MaxLevel { return apiutil.ErrInvalidLevel } if req.Limit > api.MaxLimitSize || req.Limit < 1 { diff --git a/internal/groups/api/requests_test.go b/internal/groups/api/requests_test.go new file mode 100644 index 0000000000..8dc5aef8dc --- /dev/null +++ b/internal/groups/api/requests_test.go @@ -0,0 +1,516 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "strings" + "testing" + + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/pkg/groups" + "github.com/stretchr/testify/assert" +) + +var valid = "valid" + +func TestCreateGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req createGroupReq + err error + }{ + { + desc: "valid request", + req: createGroupReq{ + token: valid, + Group: groups.Group{ + Name: valid, + }, + }, + err: nil, + }, + { + desc: "empty token", + req: createGroupReq{ + Group: groups.Group{ + Name: valid, + }, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "long name", + req: createGroupReq{ + token: valid, + Group: groups.Group{ + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + }, + err: apiutil.ErrNameSize, + }, + { + desc: "empty name", + req: createGroupReq{ + token: valid, + Group: groups.Group{}, + }, + err: apiutil.ErrNameSize, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestUpdateGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req updateGroupReq + err error + }{ + { + desc: "valid request", + req: updateGroupReq{ + token: valid, + id: valid, + Name: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: updateGroupReq{ + id: valid, + Name: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "long name", + req: updateGroupReq{ + token: valid, + id: valid, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + err: apiutil.ErrNameSize, + }, + { + desc: "empty id", + req: updateGroupReq{ + token: valid, + Name: valid, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestListGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req listGroupsReq + err error + }{ + { + desc: "valid request", + req: listGroupsReq{ + token: valid, + memberKind: auth.ThingsKind, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + }, + err: nil, + }, + { + desc: "empty token", + req: listGroupsReq{ + memberKind: auth.ThingsKind, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty memberkind", + req: listGroupsReq{ + token: valid, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + }, + err: apiutil.ErrMissingMemberKind, + }, + { + desc: "empty member id", + req: listGroupsReq{ + token: valid, + memberKind: auth.ThingsKind, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "invalid upper level", + req: listGroupsReq{ + token: valid, + memberKind: auth.ThingsKind, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + Level: groups.MaxLevel + 1, + }, + }, + err: apiutil.ErrInvalidLevel, + }, + { + desc: "invalid lower limit", + req: listGroupsReq{ + token: valid, + memberKind: auth.ThingsKind, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: 0, + }, + }, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "invalid upper limit", + req: listGroupsReq{ + token: valid, + memberKind: auth.ThingsKind, + memberID: valid, + Page: groups.Page{ + PageMeta: groups.PageMeta{ + Limit: api.MaxLimitSize + 1, + }, + }, + }, + err: apiutil.ErrLimitSize, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req groupReq + err error + }{ + { + desc: "valid request", + req: groupReq{ + token: valid, + id: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: groupReq{ + id: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: groupReq{ + token: valid, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestGroupPermsReqValidation(t *testing.T) { + cases := []struct { + desc string + req groupPermsReq + err error + }{ + { + desc: "valid request", + req: groupPermsReq{ + token: valid, + id: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: groupPermsReq{ + id: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: groupPermsReq{ + token: valid, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestChangeGroupStatusReqValidation(t *testing.T) { + cases := []struct { + desc string + req changeGroupStatusReq + err error + }{ + { + desc: "valid request", + req: changeGroupStatusReq{ + token: valid, + id: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: changeGroupStatusReq{ + id: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: changeGroupStatusReq{ + token: valid, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestAssignReqValidation(t *testing.T) { + cases := []struct { + desc string + req assignReq + err error + }{ + { + desc: "valid request", + req: assignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: nil, + }, + { + desc: "empty token", + req: assignReq{ + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty member kind", + req: assignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + Members: []string{valid}, + }, + err: apiutil.ErrMissingMemberKind, + }, + { + desc: "empty groupID", + req: assignReq{ + token: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty Members", + req: assignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + }, + err: apiutil.ErrEmptyList, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestUnAssignReqValidation(t *testing.T) { + cases := []struct { + desc string + req unassignReq + err error + }{ + { + desc: "valid request", + req: unassignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: nil, + }, + { + desc: "empty token", + req: unassignReq{ + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty member kind", + req: unassignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + Members: []string{valid}, + }, + err: apiutil.ErrMissingMemberKind, + }, + { + desc: "empty groupID", + req: unassignReq{ + token: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + Members: []string{valid}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty Members", + req: unassignReq{ + token: valid, + groupID: valid, + Relation: auth.ViewerRelation, + MemberKind: auth.ThingsKind, + }, + err: apiutil.ErrEmptyList, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestListMembersReqValidation(t *testing.T) { + cases := []struct { + desc string + req listMembersReq + err error + }{ + { + desc: "valid request", + req: listMembersReq{ + token: valid, + groupID: valid, + permission: auth.ViewPermission, + memberKind: auth.ThingsKind, + }, + err: nil, + }, + { + desc: "empty token", + req: listMembersReq{ + groupID: valid, + permission: auth.ViewPermission, + memberKind: auth.ThingsKind, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty member kind", + req: listMembersReq{ + token: valid, + groupID: valid, + permission: auth.ViewPermission, + }, + err: apiutil.ErrMissingMemberKind, + }, + { + desc: "empty groupID", + req: listMembersReq{ + token: valid, + permission: auth.ViewPermission, + memberKind: auth.ThingsKind, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} diff --git a/internal/groups/api/responses.go b/internal/groups/api/responses.go index 11651448ea..08b4b314ce 100644 --- a/internal/groups/api/responses.go +++ b/internal/groups/api/responses.go @@ -12,8 +12,6 @@ import ( ) var ( - _ magistrala.Response = (*viewMembershipRes)(nil) - _ magistrala.Response = (*membershipPageRes)(nil) _ magistrala.Response = (*createGroupRes)(nil) _ magistrala.Response = (*groupPageRes)(nil) _ magistrala.Response = (*changeStatusRes)(nil) @@ -23,39 +21,6 @@ var ( _ magistrala.Response = (*unassignRes)(nil) ) -type viewMembershipRes struct { - groups.Group `json:",inline"` -} - -func (res viewMembershipRes) Code() int { - return http.StatusOK -} - -func (res viewMembershipRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewMembershipRes) Empty() bool { - return false -} - -type membershipPageRes struct { - pageRes - Members []groups.Member `json:"members"` -} - -func (res membershipPageRes) Code() int { - return http.StatusOK -} - -func (res membershipPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res membershipPageRes) Empty() bool { - return false -} - type viewGroupRes struct { groups.Group `json:",inline"` } @@ -171,10 +136,16 @@ func (res changeStatusRes) Empty() bool { return false } -type assignRes struct{} +type assignRes struct { + assigned bool +} func (res assignRes) Code() int { - return http.StatusCreated + if res.assigned { + return http.StatusCreated + } + + return http.StatusBadRequest } func (res assignRes) Headers() map[string]string { @@ -185,10 +156,16 @@ func (res assignRes) Empty() bool { return true } -type unassignRes struct{} +type unassignRes struct { + unassigned bool +} func (res unassignRes) Code() int { - return http.StatusNoContent + if res.unassigned { + return http.StatusCreated + } + + return http.StatusBadRequest } func (res unassignRes) Headers() map[string]string { @@ -216,10 +193,16 @@ func (res listMembersRes) Empty() bool { return false } -type deleteGroupRes struct{} +type deleteGroupRes struct { + deleted bool +} func (res deleteGroupRes) Code() int { - return http.StatusNoContent + if res.deleted { + return http.StatusNoContent + } + + return http.StatusBadRequest } func (res deleteGroupRes) Headers() map[string]string { diff --git a/pkg/groups/groups.go b/pkg/groups/groups.go index 9994f2102f..aa24ad5d63 100644 --- a/pkg/groups/groups.go +++ b/pkg/groups/groups.go @@ -10,12 +10,8 @@ import ( "github.com/absmach/magistrala/pkg/clients" ) -const ( - // MaxLevel represents the maximum group hierarchy level. - MaxLevel = uint64(5) - // MinLevel represents the minimum group hierarchy level. - MinLevel = uint64(0) -) +// MaxLevel represents the maximum group hierarchy level. +const MaxLevel = uint64(5) // Group represents the group of Clients. // Indicates a level in tree hierarchy. Root node is level 1. diff --git a/internal/groups/mocks/doc.go b/pkg/groups/mocks/doc.go similarity index 100% rename from internal/groups/mocks/doc.go rename to pkg/groups/mocks/doc.go From 823731233b023aa09e0a0051741527220f850d87 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:25:52 +0300 Subject: [PATCH 14/71] NOISSUE - Add tests for pkg/clients (#258) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- pkg/clients/clients_test.go | 157 ++ pkg/clients/page.go | 2 +- pkg/clients/postgres/clients.go | 109 +- pkg/clients/postgres/clients_test.go | 2329 +++++++++++++++++--------- pkg/clients/roles.go | 13 +- pkg/clients/roles_test.go | 175 ++ pkg/clients/status_test.go | 217 +++ things/postgres/clients.go | 8 +- users/postgres/clients.go | 8 +- users/service.go | 4 +- 10 files changed, 2137 insertions(+), 885 deletions(-) create mode 100644 pkg/clients/clients_test.go create mode 100644 pkg/clients/roles_test.go create mode 100644 pkg/clients/status_test.go diff --git a/pkg/clients/clients_test.go b/pkg/clients/clients_test.go new file mode 100644 index 0000000000..823aa710d1 --- /dev/null +++ b/pkg/clients/clients_test.go @@ -0,0 +1,157 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients + +import ( + "testing" + + "github.com/absmach/magistrala/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestValidateClient(t *testing.T) { + cases := []struct { + desc string + identity string + err error + }{ + { + desc: "valid identity", + identity: "user@example.com", + err: nil, + }, + { + desc: "invalid identity", + identity: "user@example", + err: errors.ErrMalformedEntity, + }, + { + desc: "empty identity", + err: errors.ErrMalformedEntity, + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + client := Client{ + Credentials: Credentials{ + Identity: c.identity, + }, + } + err := client.Validate() + assert.Equal(t, c.err, err, "ValidateClient() error = %v, expected %v", err, c.err) + }) + } +} + +func TestIsEmail(t *testing.T) { + cases := []struct { + email string + expected bool + }{ + { + email: "test@example.com", + expected: true, + }, + { + email: "test-test@example.com", + expected: true, + }, + { + email: "test.test@example.com", + expected: true, + }, + { + email: "test_test@example.com", + expected: true, + }, + { + email: "test@", + expected: false, + }, + { + email: "@", + expected: false, + }, + { + email: "test.example.com", + expected: false, + }, + { + email: "@example.com", + expected: false, + }, + { + email: "test@example", + expected: false, + }, + { + email: "test@example.", + expected: false, + }, + { + email: "test@.com", + expected: false, + }, + { + email: "test@.example.com", + expected: false, + }, + { + email: "test@example.com.", + expected: false, + }, + { + email: "test@example.", + expected: false, + }, + { + email: "test@subdomain.example.com", + expected: true, + }, + { + email: "test@subdomain-example.com", + expected: true, + }, + { + email: "test@subdomain_example.com", + expected: true, + }, + { + email: "@subdomain.example.com", + expected: false, + }, + { + email: "test@subdomain.subdomain.example.com", + expected: true, + }, + { + email: "test@subdomain..com", + expected: false, + }, + { + email: "test@subdomain..example.com", + expected: false, + }, + { + email: "test@subdomain.example..com", + expected: false, + }, + { + email: "test@subdomain.example.com.", + expected: false, + }, + { + email: "test@subdomain.example.com..", + expected: false, + }, + } + + for _, c := range cases { + isValid := isEmail(c.email) + if isValid != c.expected { + t.Errorf("Expected isEmail(%s) to be %v, but got %v", c.email, c.expected, isValid) + } + } +} diff --git a/pkg/clients/page.go b/pkg/clients/page.go index 0bf4ccc823..d0321f0a88 100644 --- a/pkg/clients/page.go +++ b/pkg/clients/page.go @@ -18,6 +18,6 @@ type Page struct { Status Status `json:"status,omitempty"` IDs []string `json:"ids,omitempty"` Identity string `json:"identity,omitempty"` - Role *Role `json:"-"` + Role Role `json:"-"` ListPerms bool `json:"-"` } diff --git a/pkg/clients/postgres/clients.go b/pkg/clients/postgres/clients.go index 057b79e049..59144c0e10 100644 --- a/pkg/clients/postgres/clients.go +++ b/pkg/clients/postgres/clients.go @@ -20,11 +20,11 @@ import ( "github.com/jackc/pgtype" ) -type ClientRepository struct { +type Repository struct { DB postgres.Database } -func (repo ClientRepository) Update(ctx context.Context, client clients.Client) (clients.Client, error) { +func (repo *Repository) Update(ctx context.Context, client clients.Client) (clients.Client, error) { var query []string var upq string if client.Name != "" { @@ -36,64 +36,64 @@ func (repo ClientRepository) Update(ctx context.Context, client clients.Client) if len(query) > 0 { upq = strings.Join(query, " ") } - client.Status = clients.EnabledStatus + q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`, upq) - + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { - client.Status = clients.EnabledStatus +func (repo *Repository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { +func (repo *Repository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { +func (repo *Repository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { +func (repo *Repository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET owner_id = :owner_id, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) { +func (repo *Repository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET role = :role, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - + RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, role, created_at, updated_at, updated_by` + client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } -func (repo ClientRepository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { - q := `UPDATE clients SET status = :status WHERE id = :id +func (repo *Repository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { + q := `UPDATE clients SET status = :status, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` return repo.update(ctx, client, q) } -func (repo ClientRepository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { +func (repo *Repository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients WHERE id = :id` @@ -103,23 +103,23 @@ func (repo ClientRepository) RetrieveByID(ctx context.Context, id string) (clien row, err := repo.DB.NamedQueryContext(ctx, q, dbc) if err != nil { - if err == sql.ErrNoRows { - return clients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) - } return clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) } - defer row.Close() - row.Next() + dbc = DBClient{} - if err := row.StructScan(&dbc); err != nil { - return clients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + return ToClient(dbc) } - return ToClient(dbc) + return clients.Client{}, repoerr.ErrNotFound } -func (repo ClientRepository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) { +func (repo *Repository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) { q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients WHERE identity = :identity AND status = :status` @@ -130,23 +130,23 @@ func (repo ClientRepository) RetrieveByIdentity(ctx context.Context, identity st row, err := repo.DB.NamedQueryContext(ctx, q, dbc) if err != nil { - if err == sql.ErrNoRows { - return clients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) - } return clients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) } - defer row.Close() - row.Next() + dbc = DBClient{} - if err := row.StructScan(&dbc); err != nil { - return clients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + return ToClient(dbc) } - return ToClient(dbc) + return clients.Client{}, repoerr.ErrNotFound } -func (repo ClientRepository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { +func (repo *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { query, err := PageQuery(pm) if err != nil { return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) @@ -198,7 +198,7 @@ func (repo ClientRepository) RetrieveAll(ctx context.Context, pm clients.Page) ( return page, nil } -func (repo ClientRepository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { +func (repo *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { sq, tq := constructSearchQuery(pm) q := fmt.Sprintf(`SELECT c.id, c.name, c.created_at, c.updated_at FROM clients c %s LIMIT :limit OFFSET :offset;`, sq) @@ -247,7 +247,7 @@ func (repo ClientRepository) RetrieveAllBasicInfo(ctx context.Context, pm client return page, nil } -func (repo ClientRepository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { +func (repo *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { if (len(pm.IDs) <= 0) && (pm.Owner == "") { return clients.ClientsPage{ Page: clients.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit}, @@ -304,8 +304,7 @@ func (repo ClientRepository) RetrieveAllByIDs(ctx context.Context, pm clients.Pa return page, nil } -// generic update function. -func (repo ClientRepository) update(ctx context.Context, client clients.Client, query string) (clients.Client, error) { +func (repo *Repository) update(ctx context.Context, client clients.Client, query string) (clients.Client, error) { dbc, err := ToDBClient(client) if err != nil { return clients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) @@ -315,17 +314,18 @@ func (repo ClientRepository) update(ctx context.Context, client clients.Client, if err != nil { return clients.Client{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) } - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } + dbc = DBClient{} - if err := row.StructScan(&dbc); err != nil { - return clients.Client{}, err + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + return ToClient(dbc) } - return ToClient(dbc) + return clients.Client{}, repoerr.ErrNotFound } type DBClient struct { @@ -436,10 +436,6 @@ func ToDBClientsPage(pm clients.Page) (dbClientsPage, error) { if err != nil { return dbClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } - var role clients.Role - if pm.Role != nil { - role = *pm.Role - } return dbClientsPage{ Name: pm.Name, Identity: pm.Identity, @@ -450,7 +446,7 @@ func ToDBClientsPage(pm clients.Page) (dbClientsPage, error) { Limit: pm.Limit, Status: pm.Status, Tag: pm.Tag, - Role: uint8(role), + Role: pm.Role, }, nil } @@ -465,13 +461,13 @@ type dbClientsPage struct { Tag string `db:"tag"` Status clients.Status `db:"status"` GroupID string `db:"group_id"` - Role uint8 `db:"role"` + Role clients.Role `db:"role"` } func PageQuery(pm clients.Page) (string, error) { mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) if err != nil { - return "", errors.Wrap(repoerr.ErrViewEntity, err) + return "", errors.Wrap(errors.ErrMalformedEntity, err) } var query []string var emq string @@ -493,12 +489,11 @@ func PageQuery(pm clients.Page) (string, error) { if pm.Status != clients.AllStatus { query = append(query, "c.status = :status") } - // For listing clients that the specified client owns but not sharedby if pm.Owner != "" { query = append(query, "c.owner_id = :owner_id") } - if pm.Role != nil { + if pm.Role != clients.AllRole { query = append(query, "c.role = :role") } if len(query) > 0 { diff --git a/pkg/clients/postgres/clients_test.go b/pkg/clients/postgres/clients_test.go index a0f326da6a..4ec8ab86b5 100644 --- a/pkg/clients/postgres/clients_test.go +++ b/pkg/clients/postgres/clients_test.go @@ -6,398 +6,1317 @@ package postgres_test import ( "context" "fmt" + "strconv" "strings" "testing" + "time" "github.com/0x6flab/namegenerator" + ipostgres "github.com/absmach/magistrala/internal/postgres" "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/clients" mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/clients/postgres" + pgclients "github.com/absmach/magistrala/pkg/clients/postgres" "github.com/absmach/magistrala/pkg/errors" - cpostgres "github.com/absmach/magistrala/users/postgres" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( - password = "$tr0ngPassw0rd" - clientIdentity = "client-identity@example.com" - clientName = "client name" - wrongName = "wrong-name" - wrongID = "wrong-id" - namesgen = namegenerator.NewNameGenerator() + password = "$tr0ngPassw0rd" + emailSuffix = "@example.com" + namegen = namegenerator.NewNameGenerator() ) -func TestClientsRetrieveByID(t *testing.T) { +func TestRetrieveByID(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) + repo := &postgres.Repository{database} - client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: clientName, - Credentials: mgclients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Status: mgclients.EnabledStatus, - } + client := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) - clients, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - client = clients - - cases := map[string]struct { - ID string - err error + cases := []struct { + desc string + id string + response mgclients.Client + err error }{ - "retrieve existing client": {client.ID, nil}, - "retrieve non-existing client": {wrongID, errors.ErrNotFound}, + { + desc: "successfully", + id: client.ID, + response: client, + err: nil, + }, + { + desc: "with invalid user id", + id: testsutil.GenerateUUID(t), + response: mgclients.Client{}, + err: repoerr.ErrNotFound, + }, + { + desc: "with empty user id", + id: "", + response: mgclients.Client{}, + err: repoerr.ErrNotFound, + }, } - - for desc, tc := range cases { - cli, err := repo.RetrieveByID(context.Background(), tc.ID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - if err == nil { - assert.Equal(t, client.ID, cli.ID, fmt.Sprintf("retrieve client by ID : client ID : expected %s got %s\n", client.ID, cli.ID)) - assert.Equal(t, client.Name, cli.Name, fmt.Sprintf("retrieve client by ID : client Name : expected %s got %s\n", client.Name, cli.Name)) - assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity, fmt.Sprintf("retrieve client by ID : client Identity : expected %s got %s\n", client.Credentials.Identity, cli.Credentials.Identity)) - assert.Equal(t, client.Status, cli.Status, fmt.Sprintf("retrieve client by ID : client Status : expected %d got %d\n", client.Status, cli.Status)) - } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + cli, err := repo.RetrieveByID(context.Background(), c.id) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s got %s\n", c.err, err)) + if err == nil { + assert.Equal(t, client.ID, cli.ID) + assert.Equal(t, client.Name, cli.Name) + assert.Equal(t, client.Metadata, cli.Metadata) + assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity) + assert.Equal(t, client.Credentials.Secret, cli.Credentials.Secret) + assert.Equal(t, client.Status, cli.Status) + } + }) } } -func TestClientsRetrieveByIdentity(t *testing.T) { +func TestRetrieveByIdentity(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) - - client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: clientName, - Credentials: mgclients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Status: mgclients.EnabledStatus, - } + repo := &postgres.Repository{database} - _, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + client := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) - cases := map[string]struct { + cases := []struct { + desc string identity string + response mgclients.Client err error }{ - "retrieve existing client": {clientIdentity, nil}, - "retrieve non-existing client": {wrongID, errors.ErrNotFound}, + { + desc: "successfully", + identity: client.Credentials.Identity, + response: client, + err: nil, + }, + { + desc: "with invalid user id", + identity: testsutil.GenerateUUID(t), + response: mgclients.Client{}, + err: repoerr.ErrNotFound, + }, + { + desc: "with empty user id", + identity: "", + response: mgclients.Client{}, + err: repoerr.ErrNotFound, + }, } - - for desc, tc := range cases { - _, err := repo.RetrieveByIdentity(context.Background(), tc.identity) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + cli, err := repo.RetrieveByIdentity(context.Background(), c.identity) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s got %s\n", c.err, err)) + if err == nil { + assert.Equal(t, client.ID, cli.ID) + assert.Equal(t, client.Name, cli.Name) + assert.Equal(t, client.Metadata, cli.Metadata) + assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity) + assert.Equal(t, client.Credentials.Secret, cli.Credentials.Secret) + assert.Equal(t, client.Status, cli.Status) + } + }) } } -func TestClientsRetrieveAll(t *testing.T) { +func TestRetrieveAll(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) + repo := &postgres.Repository{database} nClients := uint64(200) - ownerID := testsutil.GenerateUUID(t) - meta := mgclients.Metadata{ - "admin": true, - } - wrongMeta := mgclients.Metadata{ - "admin": false, - } expectedClients := []mgclients.Client{} - + disabledClients := []mgclients.Client{} for i := uint64(0); i < nClients; i++ { - identity := fmt.Sprintf("TestRetrieveAll%d@example.com", i) client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: identity, + ID: testsutil.GenerateUUID(t), + Owner: testsutil.GenerateUUID(t), + Name: namegen.Generate(), Credentials: mgclients.Credentials{ - Identity: identity, + Identity: namegen.Generate() + emailSuffix, Secret: password, }, - Metadata: mgclients.Metadata{}, - Status: mgclients.EnabledStatus, - } - if i%10 == 0 { - client.Owner = ownerID - client.Metadata = meta - client.Tags = []string{"Test"} + Tags: namegen.GenerateNames(5), + Metadata: mgclients.Metadata{ + "department": namegen.Generate(), + }, + Status: mgclients.EnabledStatus, + CreatedAt: time.Now().UTC().Truncate(time.Millisecond), + Role: mgclients.UserRole, } if i%50 == 0 { client.Status = mgclients.DisabledStatus + client.Role = mgclients.AdminRole } - client, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + client, err := save(context.Background(), repo, client) + require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err)) expectedClients = append(expectedClients, client) + if client.Status == mgclients.DisabledStatus { + disabledClients = append(disabledClients, client) + } } - cases := map[string]struct { - size uint64 + cases := []struct { + desc string pm mgclients.Page - response []mgclients.Client + response mgclients.ClientsPage + err error }{ - "retrieve all clients empty page": { - pm: mgclients.Page{}, - response: []mgclients.Client{}, - size: 0, + { + desc: "with empty page", + pm: mgclients.Page{}, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 196, + Offset: 0, + Limit: 0, + }, + Clients: []mgclients.Client(nil), + }, }, - "retrieve all clients": { + { + desc: "with offset only", pm: mgclients.Page{ - Offset: 0, - Limit: nClients, + Offset: 50, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 50, + Limit: 0, + }, + Clients: []mgclients.Client(nil), }, - response: expectedClients, - size: 200, }, - "retrieve all clients with limit": { + { + desc: "with limit only", pm: mgclients.Page{ - Offset: 0, Limit: 50, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: 50, + }, + Clients: expectedClients[0:50], }, - response: expectedClients[0:50], - size: 50, }, - "retrieve all clients with offset": { + { + desc: "retrieve all clients", pm: mgclients.Page{ - Offset: 50, + Offset: 0, Limit: nClients, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: nClients, + }, + Clients: expectedClients, }, - response: expectedClients[50:200], - size: 150, }, - "retrieve all clients with limit and offset": { + { + desc: "with offset and limit", pm: mgclients.Page{ Offset: 50, Limit: 50, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 50, + Limit: 50, + }, + Clients: expectedClients[50:100], + }, + }, + { + desc: "with offset out of range and limit", + pm: mgclients.Page{ + Offset: 1000, + Limit: 50, + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 1000, + Limit: 50, + }, + Clients: []mgclients.Client(nil), }, - response: expectedClients[50:100], - size: 50, }, - "retrieve all clients with limit and offset not full": { + { + desc: "with offset and limit out of range", pm: mgclients.Page{ Offset: 170, Limit: 50, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 170, + Limit: 50, + }, + Clients: expectedClients[170:200], }, - response: expectedClients[170:200], - size: 30, }, - "retrieve all clients by metadata": { + { + desc: "with metadata", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Metadata: meta, + Metadata: expectedClients[0].Metadata, Status: mgclients.AllStatus, + Role: mgclients.AllRole, }, - response: []mgclients.Client{ - expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client{expectedClients[0]}, }, - size: 20, }, - "retrieve clients by wrong metadata": { + { + desc: "with wrong metadata", pm: mgclients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Metadata: wrongMeta, - Status: mgclients.AllStatus, + Offset: 0, + Limit: nClients, + Metadata: mgclients.Metadata{ + "faculty": namegen.Generate(), + }, + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), }, - response: []mgclients.Client{}, - size: 0, }, - "retrieve all clients by name": { + { + desc: "with invalid metadata", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Name: "TestRetrieveAll3@example.com", + Metadata: mgclients.Metadata{ + "faculty": make(chan int), + }, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: uint64(nClients), + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), }, - response: []mgclients.Client{expectedClients[3]}, - size: 1, + err: repoerr.ErrViewEntity, }, - "retrieve clients by wrong name": { + { + desc: "with name", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Name: wrongName, + Name: expectedClients[0].Name, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client{expectedClients[0]}, }, - response: []mgclients.Client{}, - size: 0, }, - "retrieve all clients by owner": { + { + desc: "with wrong name", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Owner: ownerID, + Name: namegen.Generate(), Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), + }, + }, + { + desc: "with identity", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Identity: expectedClients[0].Credentials.Identity, + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client{expectedClients[0]}, + }, + }, + { + desc: "with wrong identity", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Identity: namegen.Generate(), + Status: mgclients.AllStatus, + Role: mgclients.AllRole, }, - response: []mgclients.Client{ - expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), }, - size: 20, }, - "retrieve clients by wrong owner": { + { + desc: "with owner", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Owner: wrongID, + Owner: expectedClients[0].Owner, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client{expectedClients[0]}, }, - response: []mgclients.Client{}, - size: 0, }, - "retrieve all clients shared by and owned by": { + { + desc: "with wrong owner", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Owner: ownerID, + Owner: testsutil.GenerateUUID(t), Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), + }, + }, + { + desc: "with enabled status", + pm: mgclients.Page{ + Offset: 0, + Limit: 10, + Status: mgclients.EnabledStatus, + Role: mgclients.AllRole, }, - response: []mgclients.Client{ - expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 196, + Offset: 0, + Limit: 10, + }, + Clients: expectedClients[1:11], }, - size: 20, }, - "retrieve all clients by disabled status": { + { + desc: "with disabled status", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, Status: mgclients.DisabledStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: nClients, + }, + Clients: disabledClients, }, - response: []mgclients.Client{expectedClients[0], expectedClients[50], expectedClients[100], expectedClients[150]}, - size: 4, }, - "retrieve all clients by combined status": { + { + desc: "with combined status", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: nClients, + }, + Clients: expectedClients, }, - response: expectedClients, - size: 200, }, - "retrieve clients by the wrong status": { + { + desc: "with the wrong status", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, Status: 10, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), }, - response: []mgclients.Client{}, - size: 0, }, - "retrieve all clients by tags": { + { + desc: "with user role", pm: mgclients.Page{ Offset: 0, - Limit: nClients, - Total: nClients, - Tag: "Test", + Limit: 10, + Role: mgclients.UserRole, Status: mgclients.AllStatus, }, - response: []mgclients.Client{ - expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 196, + Offset: 0, + Limit: 10, + }, + Clients: expectedClients[1:11], }, - size: 20, }, - "retrieve clients by wrong tags": { + { + desc: "with admin role", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Total: nClients, - Tag: "wrongTags", + Role: mgclients.AdminRole, Status: mgclients.AllStatus, }, - response: []mgclients.Client{}, - size: 0, - }, - } - for desc, tc := range cases { - page, err := repo.RetrieveAll(context.Background(), tc.pm) - size := uint64(len(page.Clients)) - assert.ElementsMatch(t, page.Clients, tc.response, fmt.Sprintf("%s: expected %v got %v\n", desc, tc.response, page.Clients)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size)) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err)) - } -} - -func TestClientsUpdateMetadata(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM clients") - require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client", - Credentials: mgclients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: nClients, + }, + Clients: disabledClients, + }, }, - Metadata: mgclients.Metadata{ - "name": "enabled-client", + { + desc: "with combined role", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: nClients, + }, + Clients: expectedClients, + }, }, - Tags: []string{"enabled", "tag1"}, - Status: mgclients.EnabledStatus, - } + { + desc: "with the wrong role", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Role: 10, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), + }, + }, + { + desc: "with tag", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Tag: expectedClients[0].Tags[0], + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: uint64(nClients), + }, + Clients: []mgclients.Client{expectedClients[0]}, + }, + }, + { + desc: "with wrong tags", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Tag: namegen.Generate(), + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client(nil), + }, + }, + { + desc: "with multiple parameters", + pm: mgclients.Page{ + Offset: 0, + Limit: nClients, + Metadata: expectedClients[0].Metadata, + Name: expectedClients[0].Name, + Tag: expectedClients[0].Tags[0], + Identity: expectedClients[0].Credentials.Identity, + Owner: expectedClients[0].Owner, + Status: mgclients.AllStatus, + Role: mgclients.AllRole, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: nClients, + }, + Clients: []mgclients.Client{expectedClients[0]}, + }, + }, + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + page, err := repo.RetrieveAll(context.Background(), c.pm) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.response.Total, page.Total) + assert.Equal(t, c.response.Offset, page.Offset) + assert.Equal(t, c.response.Limit, page.Limit) + assert.ElementsMatch(t, page.Clients, c.response.Clients) + } + }) + } +} - client2 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "disabled-client", - Credentials: mgclients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, +func TestRetrieveByIDs(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} + + num := 200 + + var items []mgclients.Client + for i := 0; i < num; i++ { + name := namegen.Generate() + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Owner: testsutil.GenerateUUID(t), + Name: name, + Credentials: mgclients.Credentials{ + Identity: name + emailSuffix, + Secret: password, + }, + Tags: namegen.GenerateNames(5), + Metadata: map[string]interface{}{"name": name}, + CreatedAt: time.Now().UTC().Truncate(time.Millisecond), + Status: clients.EnabledStatus, + } + client, err := save(context.Background(), repo, client) + require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err)) + items = append(items, client) + } + + page, err := repo.RetrieveAll(context.Background(), mgclients.Page{Offset: 0, Limit: uint64(num)}) + require.Nil(t, err, fmt.Sprintf("retrieve all clients unexpected error: %s", err)) + assert.Equal(t, uint64(num), page.Total) + + cases := []struct { + desc string + page mgclients.Page + response mgclients.ClientsPage + err error + }{ + { + desc: "successfully", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + IDs: getIDs(items[0:3]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 3, + Offset: 0, + Limit: 10, + }, + Clients: items[0:3], + }, + err: nil, + }, + { + desc: "with empty ids", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + IDs: []string{}, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client(nil), + }, + err: nil, + }, + { + desc: "with empty ids but with owner", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + Owner: items[0].Owner, + IDs: []string{}, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "with offset only", + page: mgclients.Page{ + Offset: 10, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 20, + Offset: 10, + Limit: 0, + }, + Clients: []mgclients.Client(nil), + }, + err: nil, + }, + { + desc: "with limit only", + page: mgclients.Page{ + Limit: 10, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 20, + Offset: 0, + Limit: 10, + }, + Clients: items[0:10], + }, + err: nil, + }, + { + desc: "with offset out of range", + page: mgclients.Page{ + Offset: 1000, + Limit: 50, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 20, + Offset: 1000, + Limit: 50, + }, + Clients: []mgclients.Client(nil), + }, + err: nil, + }, + { + desc: "with offset and limit out of range", + page: mgclients.Page{ + Offset: 15, + Limit: 10, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 20, + Offset: 15, + Limit: 10, + }, + Clients: items[15:20], + }, + err: nil, + }, + { + desc: "with limit out of range", + page: mgclients.Page{ + Offset: 0, + Limit: 1000, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 20, + Offset: 0, + Limit: 1000, + }, + Clients: items[:20], + }, + err: nil, + }, + { + desc: "with name", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + Name: items[0].Name, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "with owner", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + Owner: items[0].Owner, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "with metadata", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + Metadata: items[0].Metadata, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "with invalid metadata", + page: mgclients.Page{ + Offset: 0, + Limit: 10, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + IDs: getIDs(items[0:20]), + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + Clients: []mgclients.Client(nil), + }, + err: errors.ErrMalformedEntity, + }, + } + + for _, c := range cases { + switch response, err := repo.RetrieveAllByIDs(context.Background(), c.page); { + case err == nil: + assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", c.desc, c.err, err)) + assert.Equal(t, c.response.Total, response.Total) + assert.Equal(t, c.response.Limit, response.Limit) + assert.Equal(t, c.response.Offset, response.Offset) + assert.ElementsMatch(t, response.Clients, c.response.Clients) + default: + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + } + } +} + +func TestRetrieveAllBasicInfo(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} + + name := namegen.Generate() + + nClients := uint64(200) + expectedClients := []mgclients.Client{} + for i := 0; i < int(nClients); i++ { + username := name + strconv.Itoa(i) + emailSuffix + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: username, + Credentials: mgclients.Credentials{ + Identity: username, + Secret: password, + }, + Metadata: mgclients.Metadata{}, + Status: mgclients.EnabledStatus, + CreatedAt: time.Now().UTC().Truncate(time.Millisecond), + } + client, err := save(context.Background(), repo, client) + require.Nil(t, err, fmt.Sprintf("save client unexpected error: %s", err)) + + expectedClients = append(expectedClients, mgclients.Client{ + ID: client.ID, + Name: client.Name, + CreatedAt: client.CreatedAt, + }) + } + + page, err := repo.RetrieveAll(context.Background(), mgclients.Page{Offset: 0, Limit: nClients}) + require.Nil(t, err, fmt.Sprintf("retrieve all clients unexpected error: %s", err)) + assert.Equal(t, nClients, page.Total) + + cases := []struct { + desc string + page mgclients.Page + response mgclients.ClientsPage + err error + }{ + { + desc: "with empty page", + page: mgclients.Page{}, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: 0, + }, + }, + err: nil, + }, + { + desc: "with offset only", + page: mgclients.Page{ + Offset: 50, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: nClients, + Offset: 50, + Limit: 0, + }, + }, + err: nil, + }, + { + desc: "with limit only", + page: mgclients.Page{ + Limit: 10, + Order: "name", + Dir: "asc", + }, + response: mgclients.ClientsPage{ + Clients: expectedClients[0:10], + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "retrieve all clients", + page: mgclients.Page{ + Offset: 0, + Limit: nClients, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: nClients, + }, + Clients: expectedClients, + }, + }, + { + desc: "with offset and limit", + page: mgclients.Page{ + Offset: 10, + Limit: 10, + Order: "name", + Dir: "asc", + }, + response: mgclients.ClientsPage{ + Clients: expectedClients[10:20], + Page: mgclients.Page{ + Total: nClients, + Offset: 10, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with offset out of range and limit", + page: mgclients.Page{ + Offset: 1000, + Limit: 50, + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 1000, + Limit: 50, + }, + Clients: []mgclients.Client(nil), + }, + }, + { + desc: "with offset and limit out of range", + page: mgclients.Page{ + Offset: 190, + Limit: 50, + Order: "name", + Dir: "asc", + }, + response: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients, + Offset: 190, + Limit: 50, + }, + Clients: expectedClients[190:200], + }, + }, + { + desc: "with shorter name", + page: mgclients.Page{ + Name: expectedClients[0].Name[:4], + Offset: 0, + Limit: 10, + Order: "name", + Dir: "asc", + }, + response: mgclients.ClientsPage{ + Clients: findClients(expectedClients, expectedClients[0].Name[:4], 0, 10), + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with longer name", + page: mgclients.Page{ + Name: expectedClients[0].Name, + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client{expectedClients[0]}, + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with name SQL injected", + page: mgclients.Page{ + Name: fmt.Sprintf("%s' OR '1'='1", expectedClients[0].Name[:1]), + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with shorter Identity", + page: mgclients.Page{ + Identity: expectedClients[0].Name[:4], + Offset: 0, + Limit: 10, + Order: "name", + Dir: "asc", + }, + response: mgclients.ClientsPage{ + Clients: findClients(expectedClients, expectedClients[0].Name[:4], 0, 10), + Page: mgclients.Page{ + Total: nClients, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with longer Identity", + page: mgclients.Page{ + Identity: expectedClients[0].Name, + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client{expectedClients[0]}, + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with Identity SQL injected", + page: mgclients.Page{ + Identity: fmt.Sprintf("%s' OR '1'='1", expectedClients[0].Name[:1]), + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with unknown name", + page: mgclients.Page{ + Name: namegen.Generate(), + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with unknown name SQL injected", + page: mgclients.Page{ + Name: fmt.Sprintf("%s' OR '1'='1", namegen.Generate()), + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with unknown identity", + page: mgclients.Page{ + Identity: namegen.Generate(), + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{ + Clients: []mgclients.Client(nil), + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "with name in asc order", + page: mgclients.Page{ + Order: "name", + Dir: "asc", + Name: expectedClients[0].Name[:1], + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{}, + err: nil, }, - Metadata: mgclients.Metadata{ - "name": "disabled-client", + { + desc: "with name in desc order", + page: mgclients.Page{ + Order: "name", + Dir: "desc", + Name: expectedClients[0].Name[:1], + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{}, + err: nil, + }, + { + desc: "with identity in asc order", + page: mgclients.Page{ + Order: "identity", + Dir: "asc", + Identity: expectedClients[0].Name[:1], + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{}, + err: nil, + }, + { + desc: "with identity in desc order", + page: mgclients.Page{ + Order: "identity", + Dir: "desc", + Identity: expectedClients[0].Name[:1], + Offset: 0, + Limit: 10, + }, + response: mgclients.ClientsPage{}, + err: nil, }, - Tags: []string{"disabled", "tag1"}, - Status: mgclients.DisabledStatus, } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + switch response, err := repo.RetrieveAllBasicInfo(context.Background(), c.page); { + case err == nil: + if c.page.Order != "" && c.page.Dir != "" { + c.response = response + } + assert.Nil(t, err) + assert.Equal(t, c.response.Total, response.Total) + assert.Equal(t, c.response.Limit, response.Limit) + assert.Equal(t, c.response.Offset, response.Offset) + assert.ElementsMatch(t, response.Clients, c.response.Clients) + default: + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + } + }) + } +} + +func TestUpdate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} - clients1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with metadata: expected %v got %s\n", nil, err)) - clients2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - client1 = clients1 - client2 = clients2 + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) - ucases := []struct { + cases := []struct { desc string update string client mgclients.Client @@ -409,18 +1328,29 @@ func TestClientsUpdateMetadata(t *testing.T) { client: mgclients.Client{ ID: client1.ID, Metadata: mgclients.Metadata{ - "update": "metadata", + "update": namegen.Generate(), }, }, err: nil, }, + { + desc: "update malformed metadata for enabled client", + update: "metadata", + client: mgclients.Client{ + ID: client1.ID, + Metadata: mgclients.Metadata{ + "update": make(chan int), + }, + }, + err: repoerr.ErrUpdateEntity, + }, { desc: "update metadata for disabled client", update: "metadata", client: mgclients.Client{ ID: client2.ID, Metadata: mgclients.Metadata{ - "update": "metadata", + "update": namegen.Generate(), }, }, err: errors.ErrNotFound, @@ -430,7 +1360,7 @@ func TestClientsUpdateMetadata(t *testing.T) { update: "name", client: mgclients.Client{ ID: client1.ID, - Name: "updated name", + Name: namegen.Generate(), }, err: nil, }, @@ -439,7 +1369,7 @@ func TestClientsUpdateMetadata(t *testing.T) { update: "name", client: mgclients.Client{ ID: client2.ID, - Name: "updated name", + Name: namegen.Generate(), }, err: errors.ErrNotFound, }, @@ -448,9 +1378,9 @@ func TestClientsUpdateMetadata(t *testing.T) { update: "both", client: mgclients.Client{ ID: client1.ID, - Name: "updated name and metadata", + Name: namegen.Generate(), Metadata: mgclients.Metadata{ - "update": "name and metadata", + "update": namegen.Generate(), }, }, err: nil, @@ -460,9 +1390,9 @@ func TestClientsUpdateMetadata(t *testing.T) { update: "both", client: mgclients.Client{ ID: client2.ID, - Name: "updated name and metadata", + Name: namegen.Generate(), Metadata: mgclients.Metadata{ - "update": "name and metadata", + "update": namegen.Generate(), }, }, err: errors.ErrNotFound, @@ -471,9 +1401,9 @@ func TestClientsUpdateMetadata(t *testing.T) { desc: "update metadata for invalid client", update: "metadata", client: mgclients.Client{ - ID: wrongID, + ID: testsutil.GenerateUUID(t), Metadata: mgclients.Metadata{ - "update": "metadata", + "update": namegen.Generate(), }, }, err: errors.ErrNotFound, @@ -482,8 +1412,8 @@ func TestClientsUpdateMetadata(t *testing.T) { desc: "update name for invalid client", update: "name", client: mgclients.Client{ - ID: wrongID, - Name: "updated name", + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), }, err: errors.ErrNotFound, }, @@ -491,749 +1421,516 @@ func TestClientsUpdateMetadata(t *testing.T) { desc: "update name and metadata for invalid client", update: "both", client: mgclients.Client{ - ID: client2.ID, - Name: "updated name and metadata", + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), Metadata: mgclients.Metadata{ - "update": "name and metadata", + "update": namegen.Generate(), }, }, err: errors.ErrNotFound, }, - } - for _, tc := range ucases { - expected, err := repo.Update(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - if tc.client.Name != "" { - assert.Equal(t, expected.Name, tc.client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Name, tc.client.Name)) - } - if tc.client.Metadata != nil { - assert.Equal(t, expected.Metadata, tc.client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Metadata, tc.client.Metadata)) - } - } - } -} - -func TestClientsUpdateTags(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM clients") - require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client-with-tags", - Credentials: mgclients.Credentials{ - Identity: "client1-update-tags@example.com", - Secret: password, - }, - Tags: []string{"test", "enabled"}, - Status: mgclients.EnabledStatus, - } - client2 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "disabled-client-with-tags", - Credentials: mgclients.Credentials{ - Identity: "client2-update-tags@example.com", - Secret: password, - }, - Tags: []string{"test", "disabled"}, - Status: mgclients.DisabledStatus, - } - - clients1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err)) - } - client1 = clients1 - clients2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err)) - } - client2 = clients2 - ucases := []struct { - desc string - client mgclients.Client - err error - }{ { - desc: "update tags for enabled client", + desc: "update metadata for empty client", + update: "metadata", client: mgclients.Client{ - ID: client1.ID, - Tags: []string{"updated"}, + Metadata: mgclients.Metadata{ + "update": namegen.Generate(), + }, }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "update tags for disabled client", + desc: "update name for empty client", + update: "name", client: mgclients.Client{ - ID: client2.ID, - Tags: []string{"updated"}, + Name: namegen.Generate(), }, err: errors.ErrNotFound, }, { - desc: "update tags for invalid client", + desc: "update name and metadata for empty client", + update: "both", client: mgclients.Client{ - ID: wrongID, - Tags: []string{"updated"}, + Name: namegen.Generate(), + Metadata: mgclients.Metadata{ + "update": namegen.Generate(), + }, }, err: errors.ErrNotFound, }, } - for _, tc := range ucases { - expected, err := repo.UpdateTags(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Tags, expected.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Tags, expected.Tags)) - } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.Update(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + switch c.update { + case "metadata": + assert.Equal(t, c.client.Metadata, expected.Metadata) + case "name": + assert.Equal(t, c.client.Name, expected.Name) + case "both": + assert.Equal(t, c.client.Metadata, expected.Metadata) + assert.Equal(t, c.client.Name, expected.Name) + } + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) + } + }) } } -func TestClientsUpdateSecret(t *testing.T) { +func TestUpdateTags(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client", - Credentials: mgclients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: mgclients.EnabledStatus, - } - client2 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "disabled-client", - Credentials: mgclients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, - }, - Status: mgclients.DisabledStatus, - } + repo := &postgres.Repository{database} - rClients1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, rClients1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - } - rClients2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, rClients2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - } + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) - ucases := []struct { + cases := []struct { desc string client mgclients.Client err error }{ { - desc: "update secret for enabled client", + desc: "for enabled client", client: mgclients.Client{ - ID: client1.ID, - Credentials: mgclients.Credentials{ - Identity: "client1-update@example.com", - Secret: "newpassword", - }, + ID: client1.ID, + Tags: namegen.GenerateNames(5), }, err: nil, }, { - desc: "update secret for disabled client", + desc: "for disabled client", client: mgclients.Client{ - ID: client2.ID, - Credentials: mgclients.Credentials{ - Identity: "client2-update@example.com", - Secret: "newpassword", - }, + ID: client2.ID, + Tags: namegen.GenerateNames(5), }, err: errors.ErrNotFound, }, { - desc: "update secret for invalid client", + desc: "for invalid client", client: mgclients.Client{ - ID: wrongID, - Credentials: mgclients.Credentials{ - Identity: "client3-update@example.com", - Secret: "newpassword", - }, + ID: testsutil.GenerateUUID(t), + Tags: namegen.GenerateNames(5), }, err: errors.ErrNotFound, }, + { + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, + }, } - for _, tc := range ucases { - _, err := repo.UpdateSecret(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - c, err := repo.RetrieveByIdentity(context.Background(), tc.client.Credentials.Identity) - require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err)) - assert.Equal(t, tc.client.Credentials.Secret, c.Credentials.Secret, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Secret, c.Credentials.Secret)) - } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.UpdateTags(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.client.Tags, expected.Tags) + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) + } + }) } } -func TestClientsUpdateIdentity(t *testing.T) { +func TestUpdateSecret(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client", - Credentials: mgclients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: mgclients.EnabledStatus, - } - client2 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "disabled-client", - Credentials: mgclients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, - }, - Status: mgclients.DisabledStatus, - } + repo := &postgres.Repository{database} - rClients1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, rClients1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - } - rClients2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, rClients2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - } + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) - ucases := []struct { + cases := []struct { desc string client mgclients.Client err error }{ { - desc: "update identity for enabled client", + desc: "for enabled client", client: mgclients.Client{ ID: client1.ID, Credentials: mgclients.Credentials{ - Identity: "client1-updated@example.com", + Secret: "newpassword", }, }, err: nil, }, { - desc: "update identity for disabled client", + desc: "for disabled client", client: mgclients.Client{ ID: client2.ID, Credentials: mgclients.Credentials{ - Identity: "client2-updated@example.com", + Secret: "newpassword", }, }, err: errors.ErrNotFound, }, { - desc: "update identity for invalid client", + desc: "for invalid client", client: mgclients.Client{ - ID: wrongID, + ID: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{ - Identity: "client3-updated@example.com", + Secret: "newpassword", }, }, err: errors.ErrNotFound, }, - } - for _, tc := range ucases { - expected, err := repo.UpdateIdentity(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Credentials.Identity, expected.Credentials.Identity, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Identity, expected.Credentials.Identity)) - } - } -} - -func TestClientsUpdateOwner(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM clients") - require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client-with-owner", - Credentials: mgclients.Credentials{ - Identity: "client1-update-owner@example.com", - Secret: password, - }, - Owner: testsutil.GenerateUUID(t), - Status: mgclients.EnabledStatus, - } - client2 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "disabled-client-with-owner", - Credentials: mgclients.Credentials{ - Identity: "client2-update-owner@example.com", - Secret: password, - }, - Owner: testsutil.GenerateUUID(t), - Status: mgclients.DisabledStatus, - } - - clients1, err := repo.Save(context.Background(), client1) - client1 = clients1 - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err)) - } - clients2, err := repo.Save(context.Background(), client2) - client2 = clients2 - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err)) - } - ucases := []struct { - desc string - client mgclients.Client - err error - }{ - { - desc: "update owner for enabled client", - client: mgclients.Client{ - ID: client1.ID, - Owner: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "update owner for disabled client", - client: mgclients.Client{ - ID: client2.ID, - Owner: testsutil.GenerateUUID(t), - }, - err: errors.ErrNotFound, - }, { - desc: "update owner for invalid client", - client: mgclients.Client{ - ID: wrongID, - Owner: testsutil.GenerateUUID(t), - }, - err: errors.ErrNotFound, + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, }, } - for _, tc := range ucases { - expected, err := repo.UpdateOwner(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Owner, expected.Owner, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Owner, expected.Owner)) - } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + _, err := repo.UpdateSecret(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + rc, err := repo.RetrieveByID(context.Background(), c.client.ID) + require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err)) + assert.Equal(t, c.client.Credentials.Secret, rc.Credentials.Secret) + assert.Equal(t, c.client.UpdatedAt, rc.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, rc.UpdatedBy) + } + }) } } -func TestClientsChangeStatus(t *testing.T) { +func TestUpdateIdentity(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) - repo := cpostgres.NewRepository(database) - - client1 := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: "enabled-client", - Credentials: mgclients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: mgclients.EnabledStatus, - } + repo := &postgres.Repository{database} - clients1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - client1 = clients1 + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) - ucases := []struct { + cases := []struct { desc string client mgclients.Client err error }{ { - desc: "change client status for an enabled client", - client: mgclients.Client{ - ID: client1.ID, - Status: 0, - }, - err: nil, - }, - { - desc: "change client status for a disabled client", - client: mgclients.Client{ - ID: client1.ID, - Status: 1, - }, - err: nil, - }, - { - desc: "change client status for non-existing client", + desc: "for enabled client", client: mgclients.Client{ - ID: "invalid", - Status: 2, - }, - err: errors.ErrNotFound, - }, - } - - for _, tc := range ucases { - expected, err := repo.ChangeStatus(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.client.Status, expected.Status)) - } - } -} - -func TestClientsRetrieveAllBasicInfo(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM clients") - require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - nusers := 100 - users := make([]mgclients.Client, nusers) - - name := namesgen.Generate() - - for i := 0; i < nusers; i++ { - username := fmt.Sprintf("%s-%d@example.com", name, i) - client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: username, - Credentials: mgclients.Credentials{ - Identity: username, - Secret: password, - }, - Metadata: mgclients.Metadata{}, - Status: mgclients.EnabledStatus, - } - _, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("save client unexpected error: %s", err)) - - users[i] = mgclients.Client{ - ID: client.ID, - Name: client.Name, - } - } - - cases := []struct { - desc string - page mgclients.Page - response mgclients.ClientsPage - err error - }{ - { - desc: "retrieve all clients", - page: mgclients.Page{ - Offset: 0, - Limit: uint64(nusers), - }, - response: mgclients.ClientsPage{ - Clients: users, - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: uint64(nusers), - }, - }, - err: nil, - }, - { - desc: "retrieve all clients with offset", - page: mgclients.Page{ - Offset: 10, - Limit: uint64(nusers), - }, - response: mgclients.ClientsPage{ - Clients: users[10:], - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 10, - Limit: uint64(nusers), + ID: client1.ID, + Credentials: mgclients.Credentials{ + Identity: namegen.Generate() + emailSuffix, }, }, err: nil, }, { - desc: "retrieve all clients with limit", - page: mgclients.Page{ - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: users[:10], - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: 10, + desc: "for disabled client", + client: mgclients.Client{ + ID: client2.ID, + Credentials: mgclients.Credentials{ + Identity: namegen.Generate() + emailSuffix, }, }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with offset and limit", - page: mgclients.Page{ - Offset: 10, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: users[10:20], - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 10, - Limit: 10, + desc: "for invalid client", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Credentials: mgclients.Credentials{ + Identity: namegen.Generate() + emailSuffix, }, }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with name", - page: mgclients.Page{ - Name: users[0].Name[:1], - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: findClients(users, users[0].Name[:1], 0, 10), - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: 10, - }, - }, - err: nil, + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, }, + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.UpdateIdentity(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.client.Credentials.Identity, expected.Credentials.Identity) + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) + } + }) + } +} + +func TestUpdateOwner(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} + + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) + + cases := []struct { + desc string + client mgclients.Client + err error + }{ { - desc: "retrieve all clients with name", - page: mgclients.Page{ - Name: users[0].Name[:4], - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: findClients(users, users[0].Name[:4], 0, 10), - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: 10, - }, + desc: "for enabled client", + client: mgclients.Client{ + ID: client1.ID, + Owner: testsutil.GenerateUUID(t), }, err: nil, }, { - desc: "retrieve all clients with name with SQL injection", - page: mgclients.Page{ - Name: fmt.Sprintf("%s' OR '1'='1", users[0].Name[:1]), - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: []mgclients.Client(nil), - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, + desc: "for disabled client", + client: mgclients.Client{ + ID: client2.ID, + Owner: testsutil.GenerateUUID(t), }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with Identity", - page: mgclients.Page{ - Identity: users[0].Name[:1], - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: findClients(users, users[0].Name[:1], 0, 10), - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: 10, - }, + desc: "for invalid client", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Owner: testsutil.GenerateUUID(t), }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with Identity", - page: mgclients.Page{ - Identity: users[0].Name[:4], - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: findClients(users, users[0].Name[:4], 0, 10), - Page: mgclients.Page{ - Total: uint64(nusers), - Offset: 0, - Limit: 10, - }, - }, - err: nil, + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, }, + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.UpdateOwner(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.client.Owner, expected.Owner) + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) + } + }) + } +} + +func TestChangeStatus(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} + + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) + + cases := []struct { + desc string + client mgclients.Client + err error + }{ { - desc: "retrieve all clients with Identity with SQL injection", - page: mgclients.Page{ - Identity: fmt.Sprintf("%s' OR '1'='1", users[0].Name[:1]), - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: []mgclients.Client(nil), - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, + desc: "for an enabled client", + client: mgclients.Client{ + ID: client1.ID, + Status: mgclients.DisabledStatus, }, err: nil, }, { - desc: "retrieve all clients unknown name", - page: mgclients.Page{ - Name: "unknown", - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: []mgclients.Client(nil), - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, + desc: "for a disabled client", + client: mgclients.Client{ + ID: client2.ID, + Status: mgclients.EnabledStatus, }, err: nil, }, { - desc: "retrieve all clients unknown name with SQL injection", - page: mgclients.Page{ - Name: "unknown' OR '1'='1", - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: []mgclients.Client(nil), - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, + desc: "for invalid client", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Status: mgclients.DisabledStatus, }, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients unknown identity", - page: mgclients.Page{ - Identity: "unknown", - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{ - Clients: []mgclients.Client(nil), - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - err: nil, + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.ChangeStatus(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.client.Status, expected.Status) + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) + } + }) + } +} + +func TestUpdateRole(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := &postgres.Repository{database} + + client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) + client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) + + cases := []struct { + desc string + client mgclients.Client + err error + }{ { - desc: "retrieve all clients with order", - page: mgclients.Page{ - Order: "name", - Dir: "asc", - Name: users[0].Name[:1], - Offset: 0, - Limit: 10, + desc: "for an enabled client", + client: mgclients.Client{ + ID: client1.ID, + Role: mgclients.AdminRole, }, - response: mgclients.ClientsPage{}, - err: nil, + err: nil, }, { - desc: "retrieve all clients with order", - page: mgclients.Page{ - Order: "name", - Dir: "desc", - Name: users[0].Name[:1], - Offset: 0, - Limit: 10, + desc: "for a disabled client", + client: mgclients.Client{ + ID: client2.ID, + Role: mgclients.AdminRole, }, - response: mgclients.ClientsPage{}, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with order", - page: mgclients.Page{ - Order: "identity", - Dir: "asc", - Identity: users[0].Name[:1], - Offset: 0, - Limit: 10, + desc: "for invalid client", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Role: mgclients.AdminRole, }, - response: mgclients.ClientsPage{}, - err: nil, + err: errors.ErrNotFound, }, { - desc: "retrieve all clients with order", - page: mgclients.Page{ - Order: "identity", - Dir: "desc", - Identity: users[0].Name[:1], - Offset: 0, - Limit: 10, - }, - response: mgclients.ClientsPage{}, - err: nil, + desc: "for empty client", + client: mgclients.Client{}, + err: errors.ErrNotFound, }, } - for _, tc := range cases { - resp, err := repo.RetrieveAllBasicInfo(context.Background(), tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - if tc.page.Order != "" && tc.page.Dir != "" { - tc.response = resp + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) + c.client.UpdatedBy = testsutil.GenerateUUID(t) + expected, err := repo.UpdateRole(context.Background(), c.client) + assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) + if err == nil { + assert.Equal(t, c.client.Role, expected.Role) + assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) + assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) } - assert.Equal(t, tc.response, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, resp)) - } + }) } } -func findClients(clients []mgclients.Client, query string, offset, limit uint64) []mgclients.Client { - clis := []mgclients.Client{} - for _, client := range clients { +func findClients(clis []mgclients.Client, query string, offset, limit uint64) []mgclients.Client { + rclients := []mgclients.Client{} + for _, client := range clis { if strings.Contains(client.Name, query) { - clis = append(clis, client) + rclients = append(rclients, client) } } - if offset > uint64(len(clis)) { + if offset > uint64(len(rclients)) { return []mgclients.Client{} } - if limit > uint64(len(clis)) { - return clis[offset:] + if limit > uint64(len(rclients)) { + return rclients[offset:] + } + + return rclients[offset:limit] +} + +func generateClient(t *testing.T, status mgclients.Status, role mgclients.Role, repo *postgres.Repository) mgclients.Client { + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + Credentials: mgclients.Credentials{ + Identity: namegen.Generate() + emailSuffix, + Secret: password, + }, + Tags: namegen.GenerateNames(5), + Metadata: mgclients.Metadata{ + "name": namegen.Generate(), + }, + Status: status, + Role: role, + CreatedAt: time.Now().UTC().Truncate(time.Millisecond), + } + _, err := save(context.Background(), repo, client) + require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err)) + + return client +} + +func save(ctx context.Context, repo *postgres.Repository, c mgclients.Client) (mgclients.Client, error) { + q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, status, role) + VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :status, :role) + RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at` + dbc, err := pgclients.ToDBClient(c) + if err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + + row, err := repo.DB.NamedQueryContext(ctx, q, dbc) + if err != nil { + return mgclients.Client{}, ipostgres.HandleError(repoerr.ErrCreateEntity, err) + } + defer row.Close() + + dbc = pgclients.DBClient{} + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + return pgclients.ToClient(dbc) + } + + return mgclients.Client{}, repoerr.ErrCreateEntity +} + +func getIDs(clis []mgclients.Client) []string { + var ids []string + for _, client := range clis { + ids = append(ids, client.ID) } - return clis[offset:limit] + return ids } diff --git a/pkg/clients/roles.go b/pkg/clients/roles.go index a75929bd00..caf743406d 100644 --- a/pkg/clients/roles.go +++ b/pkg/clients/roles.go @@ -17,6 +17,12 @@ type Role uint8 const ( UserRole Role = iota AdminRole + + // AllRole is used for querying purposes to list clients irrespective + // of their role - both admin and user. It is never stored in the + // database as the actual Client role and should always be the largest + // value in this enumeration. + AllRole ) // String representation of the possible role values. @@ -32,6 +38,8 @@ func (cs Role) String() string { return Admin case UserRole: return User + case AllRole: + return All default: return Unknown } @@ -44,8 +52,11 @@ func ToRole(status string) (Role, error) { return UserRole, nil case Admin: return AdminRole, nil + case All: + return AllRole, nil + default: + return Role(0), apiutil.ErrInvalidRole } - return Role(0), apiutil.ErrInvalidRole } func (r Role) MarshalJSON() ([]byte, error) { diff --git a/pkg/clients/roles_test.go b/pkg/clients/roles_test.go new file mode 100644 index 0000000000..23e52c54a3 --- /dev/null +++ b/pkg/clients/roles_test.go @@ -0,0 +1,175 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients_test + +import ( + "testing" + + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/pkg/clients" + "github.com/stretchr/testify/assert" +) + +func TestRoleString(t *testing.T) { + cases := []struct { + desc string + role clients.Role + expected string + }{ + { + desc: "User", + role: clients.UserRole, + expected: "user", + }, + { + desc: "Admin", + role: clients.AdminRole, + expected: "admin", + }, + { + desc: "All", + role: clients.AllRole, + expected: "all", + }, + { + desc: "Unknown", + role: clients.Role(100), + expected: "unknown", + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got := c.role.String() + assert.Equal(t, c.expected, got, "String() = %v, expected %v", got, c.expected) + }) + } +} + +func TestToRole(t *testing.T) { + cases := []struct { + desc string + role string + expected clients.Role + err error + }{ + { + desc: "User", + role: "user", + expected: clients.UserRole, + err: nil, + }, + { + desc: "Admin", + role: "admin", + expected: clients.AdminRole, + err: nil, + }, + { + desc: "All", + role: "all", + expected: clients.AllRole, + err: nil, + }, + { + desc: "Unknown", + role: "unknown", + expected: clients.Role(0), + err: apiutil.ErrInvalidRole, + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got, err := clients.ToRole(c.role) + assert.Equal(t, c.err, err, "ToRole() error = %v, expected %v", err, c.err) + assert.Equal(t, c.expected, got, "ToRole() = %v, expected %v", got, c.expected) + }) + } +} + +func TestRoleMarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected []byte + role clients.Role + err error + }{ + { + desc: "User", + expected: []byte(`"user"`), + role: clients.UserRole, + err: nil, + }, + { + desc: "Admin", + expected: []byte(`"admin"`), + role: clients.AdminRole, + err: nil, + }, + { + desc: "All", + expected: []byte(`"all"`), + role: clients.AllRole, + err: nil, + }, + { + desc: "Unknown", + expected: []byte(`"unknown"`), + role: clients.Role(100), + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.role.MarshalJSON() + assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) + }) + } +} + +func TestRoleUnmarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected clients.Role + role []byte + err error + }{ + { + desc: "User", + expected: clients.UserRole, + role: []byte(`"user"`), + err: nil, + }, + { + desc: "Admin", + expected: clients.AdminRole, + role: []byte(`"admin"`), + err: nil, + }, + { + desc: "All", + expected: clients.AllRole, + role: []byte(`"all"`), + err: nil, + }, + { + desc: "Unknown", + expected: clients.Role(0), + role: []byte(`"unknown"`), + err: apiutil.ErrInvalidRole, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + var r clients.Role + err := r.UnmarshalJSON(tc.role) + assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, r, "UnmarshalJSON() = %v, expected %v", r, tc.expected) + }) + } +} diff --git a/pkg/clients/status_test.go b/pkg/clients/status_test.go new file mode 100644 index 0000000000..1d6e08f198 --- /dev/null +++ b/pkg/clients/status_test.go @@ -0,0 +1,217 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients_test + +import ( + "testing" + + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/pkg/clients" + "github.com/stretchr/testify/assert" +) + +func TestStatusString(t *testing.T) { + cases := []struct { + desc string + status clients.Status + expected string + }{ + { + desc: "Enabled", + status: clients.EnabledStatus, + expected: "enabled", + }, + { + desc: "Disabled", + status: clients.DisabledStatus, + expected: "disabled", + }, + { + desc: "All", + status: clients.AllStatus, + expected: "all", + }, + { + desc: "Unknown", + status: clients.Status(100), + expected: "unknown", + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got := tc.status.String() + assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected) + }) + } +} + +func TestToStatus(t *testing.T) { + cases := []struct { + desc string + status string + expetcted clients.Status + err error + }{ + { + desc: "Enabled", + status: "enabled", + expetcted: clients.EnabledStatus, + err: nil, + }, + { + desc: "Disabled", + status: "disabled", + expetcted: clients.DisabledStatus, + err: nil, + }, + { + desc: "All", + status: "all", + expetcted: clients.AllStatus, + err: nil, + }, + { + desc: "Unknown", + status: "unknown", + expetcted: clients.Status(0), + err: apiutil.ErrInvalidStatus, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := clients.ToStatus(tc.status) + assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expetcted, got, "ToStatus() = %v, expected %v", got, tc.expetcted) + }) + } +} + +func TestStatusMarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected []byte + status clients.Status + err error + }{ + { + desc: "Enabled", + expected: []byte(`"enabled"`), + status: clients.EnabledStatus, + err: nil, + }, + { + desc: "Disabled", + expected: []byte(`"disabled"`), + status: clients.DisabledStatus, + err: nil, + }, + { + desc: "All", + expected: []byte(`"all"`), + status: clients.AllStatus, + err: nil, + }, + { + desc: "Unknown", + expected: []byte(`"unknown"`), + status: clients.Status(100), + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.status.MarshalJSON() + assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) + }) + } +} + +func TestStatusUnmarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected clients.Status + status []byte + err error + }{ + { + desc: "Enabled", + expected: clients.EnabledStatus, + status: []byte(`"enabled"`), + err: nil, + }, + { + desc: "Disabled", + expected: clients.DisabledStatus, + status: []byte(`"disabled"`), + err: nil, + }, + { + desc: "All", + expected: clients.AllStatus, + status: []byte(`"all"`), + err: nil, + }, + { + desc: "Unknown", + expected: clients.Status(0), + status: []byte(`"unknown"`), + err: apiutil.ErrInvalidStatus, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + var s clients.Status + err := s.UnmarshalJSON(tc.status) + assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, s, "UnmarshalJSON() = %v, expected %v", s, tc.expected) + }) + } +} + +func TestUserMarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected []byte + user clients.Client + err error + }{ + { + desc: "Enabled", + expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"enabled"}`), + user: clients.Client{Status: clients.EnabledStatus}, + err: nil, + }, + { + desc: "Disabled", + expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"disabled"}`), + user: clients.Client{Status: clients.DisabledStatus}, + err: nil, + }, + { + desc: "All", + expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"all"}`), + user: clients.Client{Status: clients.AllStatus}, + err: nil, + }, + { + desc: "Unknown", + expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"unknown"}`), + user: clients.Client{Status: clients.Status(100)}, + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.user.MarshalJSON() + assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) + }) + } +} diff --git a/things/postgres/clients.go b/things/postgres/clients.go index 065bc29922..e03f947906 100644 --- a/things/postgres/clients.go +++ b/things/postgres/clients.go @@ -18,7 +18,7 @@ import ( var _ mgclients.Repository = (*clientRepo)(nil) type clientRepo struct { - pgclients.ClientRepository + pgclients.Repository } // Repository is the interface that wraps the basic methods for @@ -43,12 +43,12 @@ type Repository interface { // implementation of Clients repository. func NewRepository(db postgres.Database) Repository { return &clientRepo{ - ClientRepository: pgclients.ClientRepository{DB: db}, + Repository: pgclients.Repository{DB: db}, } } func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgclients.Client, error) { - tx, err := repo.ClientRepository.DB.BeginTxx(ctx, nil) + tx, err := repo.DB.BeginTxx(ctx, nil) if err != nil { return []mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) } @@ -64,7 +64,7 @@ func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgcl return []mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) } - row, err := repo.ClientRepository.DB.NamedQueryContext(ctx, q, dbcli) + row, err := repo.DB.NamedQueryContext(ctx, q, dbcli) if err != nil { if err := tx.Rollback(); err != nil { return []mgclients.Client{}, postgres.HandleError(repoerr.ErrCreateEntity, err) diff --git a/users/postgres/clients.go b/users/postgres/clients.go index 7f5ebb53da..e22dad596d 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -18,7 +18,7 @@ import ( var _ mgclients.Repository = (*clientRepo)(nil) type clientRepo struct { - pgclients.ClientRepository + pgclients.Repository } // Repository defines the required dependencies for Client repository. @@ -42,7 +42,7 @@ type Repository interface { // implementation of Clients repository. func NewRepository(db postgres.Database) Repository { return &clientRepo{ - ClientRepository: pgclients.ClientRepository{DB: db}, + Repository: pgclients.Repository{DB: db}, } } @@ -55,7 +55,7 @@ func (repo clientRepo) Save(ctx context.Context, c mgclients.Client) (mgclients. return mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) } - row, err := repo.ClientRepository.DB.NamedQueryContext(ctx, q, dbc) + row, err := repo.DB.NamedQueryContext(ctx, q, dbc) if err != nil { return mgclients.Client{}, postgres.HandleError(repoerr.ErrCreateEntity, err) } @@ -77,7 +77,7 @@ func (repo clientRepo) Save(ctx context.Context, c mgclients.Client) (mgclients. func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) error { q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2" - rows, err := repo.ClientRepository.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole) + rows, err := repo.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole) if err != nil { if err == sql.ErrNoRows { return errors.ErrAuthorization diff --git a/users/service.go b/users/service.go index 04cfd02dcc..22cc05a19e 100644 --- a/users/service.go +++ b/users/service.go @@ -185,14 +185,14 @@ func (svc service) ListClients(ctx context.Context, token string, pm mgclients.P } return pg, err } - role := mgclients.UserRole + p := mgclients.Page{ Status: mgclients.EnabledStatus, Offset: pm.Offset, Limit: pm.Limit, Name: pm.Name, Identity: pm.Identity, - Role: &role, + Role: mgclients.UserRole, } pg, err := svc.clients.RetrieveAll(ctx, p) if err != nil { From da8ab373067e770828a540081ac8dff0991911cd Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:54:59 +0300 Subject: [PATCH 15/71] NOISSUE - Fix Compose With v2.24.0 (#273) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- Makefile | 10 +++---- README.md | 47 +++++++++++++------------------ docker/brokers/docker-compose.yml | 4 +-- docker/brokers/profiles/nats.yml | 2 +- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index f43285ff58..d95a679061 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ clean: cleandocker: # Stops containers and removes containers, networks, volumes, and images created by up - docker-compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans + docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans ifdef pv # Remove unused volumes @@ -213,7 +213,7 @@ define edit_docker_config sed -i "s/MG_MQTT_BROKER_HEALTH_CHECK=.*/MG_MQTT_BROKER_HEALTH_CHECK=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_HEALTH_CHECK}/" docker/.env sed -i "s/MG_MQTT_ADAPTER_WS_TARGET_PATH=.*/MG_MQTT_ADAPTER_WS_TARGET_PATH=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_WS_TARGET_PATH}/" docker/.env sed -i "s/MG_MESSAGE_BROKER_TYPE=.*/MG_MESSAGE_BROKER_TYPE=$(2)/" docker/.env - sed -i "s,file: .*.yml,file: $(2).yml," docker/brokers/docker-compose.yml + sed -i "s,file: .*.yml,file: brokers/$(2).yml," docker/brokers/docker-compose.yml sed -i "s,MG_MESSAGE_BROKER_URL=.*,MG_MESSAGE_BROKER_URL=$$\{MG_$(shell echo ${MG_MESSAGE_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env sed -i "s,MG_MQTT_ADAPTER_MQTT_QOS=.*,MG_MQTT_ADAPTER_MQTT_QOS=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_MQTT_QOS\}," docker/.env endef @@ -246,16 +246,16 @@ run: check_certs change_config ifeq ($(MG_ES_TYPE), redis) sed -i "s/MG_ES_TYPE=.*/MG_ES_TYPE=redis/" docker/.env sed -i "s/MG_ES_URL=.*/MG_ES_URL=$$\{MG_REDIS_URL}/" docker/.env - docker-compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) --profile redis -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) + docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) --profile redis -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) else sed -i "s,MG_ES_TYPE=.*,MG_ES_TYPE=$$\{MG_MESSAGE_BROKER_TYPE}," docker/.env sed -i "s,MG_ES_URL=.*,MG_ES_URL=$$\{MG_$(shell echo ${MG_MESSAGE_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env - docker-compose -f docker/docker-compose.yml --env-file docker/.env --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) + docker compose -f docker/docker-compose.yml --env-file docker/.env --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) endif run_addons: check_certs $(call change_config) $(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC)))) @for SVC in $(RUN_ADDON_ARGS); do \ - MG_ADDONS_CERTS_PATH_PREFIX="../." docker-compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ + MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ done diff --git a/README.md b/README.md index 4d2c15a693..806b2dfdaf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Magistrala + [![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml) [![Check the consistency of generated files](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yml) [![Continuous Delivery](https://github.com/absmach/magistrala/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yml) @@ -11,9 +12,7 @@ Magistrala is modern, scalable, secure, open-source, and patent-free IoT cloud platform written in Go. -It accepts user and thing (sensor, actuator, application) connections over various network protocols (i.e. HTTP, -MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware -for building complex IoT solutions. +It accepts user and thing (sensor, actuator, application) connections over various network protocols (i.e. HTTP, MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware for building complex IoT solutions. For more details, check out the [official documentation][docs]. @@ -28,8 +27,8 @@ For more details, check out the [official documentation][docs]. - Event sourcing - Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes] - [LoRaWAN][lora] network integration -- [OPC UA](opcua) integration -- Edge [Agent](agent) and [Export](export) services for remote IoT gateway management and edge computing +- [OPC UA][opcua] integration +- Edge [Agent][agent] and [Export][export] services for remote IoT gateway management and edge computing - SDK - CLI - Small memory footprint and fast execution @@ -39,20 +38,20 @@ For more details, check out the [official documentation][docs]. The following are needed to run Magistrala: -- [Docker](https://docs.docker.com/install/) (version 20.10) -- [Docker compose](https://docs.docker.com/compose/install/) (version 2.20) +- [Docker](https://docs.docker.com/install/) (version 24.0.7) +- [Docker compose](https://docs.docker.com/compose/install/) (version 2.24.0) Developing Magistrala will also require: -- [Go](https://golang.org/doc/install) (version 1.19.2) -- [Protobuf](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) (version 3.6.1) +- [Go](https://golang.org/doc/install) (version 1.21) +- [Protobuf](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) (version 25.1) ## Install Once the prerequisites are installed, execute the following commands from the project's root: ```bash -docker-compose -f docker/docker-compose.yml --env-file docker/.env --profile nats_nats up +docker compose -f docker/docker-compose.yml --env-file docker/.env --profile nats_nats -p git_github_com_absmach_magistrala_git_ up ``` This will bring up the Magistrala docker services and interconnect them. This command can also be executed using the project's included Makefile: @@ -75,11 +74,11 @@ Check that `.env` file contains: MG_RELEASE_TAG= ``` ->`docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://docs.mainflux.io/kubernetes). +> `docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://docs.mainflux.io/kubernetes). ## Usage -The quickest way to start using Magistrala is via the CLI. The latest version can be downloaded from the [official releases page][rel]. +The quickest way to start using Magistrala is via the CLI. The latest version can be downloaded from the [official releases page][releases]. It can also be built and used from the project's root directory: @@ -100,19 +99,13 @@ If you spot an error or a need for corrections, please let us know - or even bet Main architect and BDFL of Magistrala project is [@drasko][drasko]. -Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured -overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko] -helped with crafting initial implementation and continuously worked on the project evolutions. +Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko] helped with crafting initial implementation and continuously worked on the project evolutions. -Besides them, Magistrala is constantly improved and actively -developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava], -[@gsaleh][george], [@blokovi][iva], [@chombium][kole], [@mteodor][mirko] and a large set of contributors. +Besides them, Magistrala is constantly improved and actively developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava], [@gsaleh][george], [@blokovi][iva], [@chombium][kole], [@mteodor][mirko], [@rodneyosodo][rodneyosodo] and a large set of contributors. Maintainers are listed in [MAINTAINERS](MAINTAINERS) file. -The Magistrala team would like to give special thanks to [@mijicd][dejan] for his monumental work -on designing and implementing a highly improved and optimized version of the platform, -and [@malidukica][dusanm] for his effort on implementing the initial user interface. +The Magistrala team would like to give special thanks to [@mijicd][dejan] for his monumental work on designing and implementing a highly improved and optimized version of the platform, and [@malidukica][dusanm] for his effort on implementing the initial user interface. ## Professional Support @@ -132,7 +125,7 @@ Thank you for your interest in Magistrala and the desire to contribute! You like Magistrala and you would like to make it your day job? We're always looking for talented engineers interested in open-source, IoT and distributed systems. If you recognize yourself, reach out to [@drasko][drasko] - he will contact you back. ->The best way to grab our attention is, of course, by sending PRs :sunglasses:. +> The best way to grab our attention is, of course, by sending PRs :sunglasses:. ## Community @@ -147,9 +140,11 @@ You like Magistrala and you would like to make it your day job? We're always loo [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux?ref=badge_large) ## Data Collection for Magistrala + Magistrala is committed to continuously improving its services and ensuring a seamless experience for its users. To achieve this, we collect certain data from your deployments. Rest assured, this data is collected solely for the purpose of enhancing Magistrala and is not used with any malicious intent. The deployment summary can be found on our [website][callhome]. The collected data includes: + - **IP Address** - Used for approximate location information on deployments. - **Services Used** - To understand which features are popular and prioritize future developments. - **Last Seen Time** - To ensure the stability and availability of Magistrala. @@ -179,12 +174,7 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w [agent]: https://github.com/mainflux/agent [export]: https://github.com/mainflux/export [kubernetes]: https://kubernetes.io/ -[rel]: https://github.com/absmach/magistrala/releases -[careers]: https://www.mainflux.com/careers.html -[lf]: https://www.linuxfoundation.org/ -[edgex]: https://www.edgexfoundry.org/ -[company]: https://abstractmachines.fr -[blog]: https://medium.com/abstract-machines-blog +[releases]: https://github.com/absmach/magistrala/releases [drasko]: https://github.com/drasko [nikola]: https://github.com/nmarcetic [dejan]: https://github.com/mijicd @@ -199,4 +189,5 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w [kole]: https://github.com/chombium [dusanm]: https://github.com/malidukica [mirko]: https://github.com/mteodor +[rodneyosodo]: https://github.com/rodneyosodo [callhome]: https://deployments.mainflux.io diff --git a/docker/brokers/docker-compose.yml b/docker/brokers/docker-compose.yml index 13709ffd88..419b3b6815 100644 --- a/docker/brokers/docker-compose.yml +++ b/docker/brokers/docker-compose.yml @@ -16,7 +16,7 @@ # include: - - path: profiles/nats.yml + - path: brokers/profiles/nats.yml env_file: docker/.env services: @@ -37,7 +37,7 @@ services: broker: extends: - file: nats.yml + file: brokers/nats.yml service: broker container_name: magistrala-broker restart: on-failure diff --git a/docker/brokers/profiles/nats.yml b/docker/brokers/profiles/nats.yml index c8382f5a65..3f747cfcd2 100644 --- a/docker/brokers/profiles/nats.yml +++ b/docker/brokers/profiles/nats.yml @@ -6,7 +6,7 @@ services: nats: extends: - file: ../nats.yml + file: brokers/nats.yml service: broker container_name: magistrala-nats restart: on-failure From b65884793afb09be91106458a2669206b5242b8c Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:57:22 +0300 Subject: [PATCH 16/71] NOISSUE - Use methods for accessing in generated code (#204) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- cmd/twins/main.go | 2 +- coap/adapter.go | 4 ++-- coap/api/logging.go | 6 +++--- coap/api/transport.go | 4 ++-- consumers/notifiers/service.go | 12 +++++------ consumers/notifiers/smpp/notifier.go | 2 +- consumers/notifiers/smtp/notifier.go | 10 +++++----- mqtt/forwarder.go | 8 ++++---- mqtt/handler.go | 2 +- opcua/gopcua/subscribe.go | 2 +- pkg/messaging/mqtt/pubsub_test.go | 2 +- pkg/messaging/nats/publisher.go | 4 ++-- pkg/messaging/nats/tracing/publisher.go | 2 +- pkg/messaging/nats/tracing/pubsub.go | 2 +- pkg/messaging/rabbitmq/publisher.go | 4 ++-- pkg/messaging/rabbitmq/pubsub_test.go | 2 +- pkg/messaging/rabbitmq/tracing/publisher.go | 2 +- pkg/messaging/rabbitmq/tracing/pubsub.go | 2 +- pkg/transformers/json/transformer.go | 12 +++++------ pkg/transformers/senml/transformer.go | 12 +++++------ twins/mocks/messages.go | 2 +- twins/service.go | 22 ++++++++++----------- ws/client.go | 3 ++- ws/handler.go | 2 +- 24 files changed, 63 insertions(+), 62 deletions(-) diff --git a/cmd/twins/main.go b/cmd/twins/main.go index 7b0409e5ee..acf0dc367d 100644 --- a/cmd/twins/main.go +++ b/cmd/twins/main.go @@ -217,7 +217,7 @@ func newService(ctx context.Context, id string, ps messaging.PubSub, cfg config, func handle(ctx context.Context, logger mglog.Logger, chanID string, svc twins.Service) handlerFunc { return func(msg *messaging.Message) error { - if msg.Channel == chanID { + if msg.GetChannel() == chanID { return nil } diff --git a/coap/adapter.go b/coap/adapter.go index 357c92b780..380213f85f 100644 --- a/coap/adapter.go +++ b/coap/adapter.go @@ -58,7 +58,7 @@ func (svc *adapterService) Publish(ctx context.Context, key string, msg *messagi SubjectType: auth.ThingType, Permission: auth.PublishPermission, Subject: key, - Object: msg.Channel, + Object: msg.GetChannel(), ObjectType: auth.GroupType, } res, err := svc.auth.Authorize(ctx, ar) @@ -70,7 +70,7 @@ func (svc *adapterService) Publish(ctx context.Context, key string, msg *messagi } msg.Publisher = res.GetId() - return svc.pubsub.Publish(ctx, msg.Channel, msg) + return svc.pubsub.Publish(ctx, msg.GetChannel(), msg) } func (svc *adapterService) Subscribe(ctx context.Context, key, chanID, subtopic string, c Client) error { diff --git a/coap/api/logging.go b/coap/api/logging.go index d4a0a6e890..12bbf8304d 100644 --- a/coap/api/logging.go +++ b/coap/api/logging.go @@ -31,9 +31,9 @@ func LoggingMiddleware(svc coap.Service, logger mglog.Logger) coap.Service { // If the request fails, it logs the error. func (lm *loggingMiddleware) Publish(ctx context.Context, key string, msg *messaging.Message) (err error) { defer func(begin time.Time) { - destChannel := msg.Channel - if msg.Subtopic != "" { - destChannel = fmt.Sprintf("%s.%s", destChannel, msg.Subtopic) + destChannel := msg.GetChannel() + if msg.GetSubtopic() != "" { + destChannel = fmt.Sprintf("%s.%s", destChannel, msg.GetSubtopic()) } message := fmt.Sprintf("Method publish to %s took %s to complete", destChannel, time.Since(begin)) if err != nil { diff --git a/coap/api/transport.go b/coap/api/transport.go index e93cf599bb..766d4823e1 100644 --- a/coap/api/transport.go +++ b/coap/api/transport.go @@ -124,9 +124,9 @@ func handleGet(ctx context.Context, m *mux.Message, c mux.Client, msg *messaging } if obs == startObserve { c := coap.NewClient(c, m.Token, logger) - return service.Subscribe(ctx, key, msg.Channel, msg.Subtopic, c) + return service.Subscribe(ctx, key, msg.GetChannel(), msg.GetSubtopic(), c) } - return service.Unsubscribe(ctx, key, msg.Channel, msg.Subtopic, m.Token.String()) + return service.Unsubscribe(ctx, key, msg.GetChannel(), msg.GetSubtopic(), m.Token.String()) } func decodeMessage(msg *mux.Message) (*messaging.Message, error) { diff --git a/consumers/notifiers/service.go b/consumers/notifiers/service.go index 26b365b90d..e7333650d2 100644 --- a/consumers/notifiers/service.go +++ b/consumers/notifiers/service.go @@ -102,9 +102,9 @@ func (ns *notifierService) ConsumeBlocking(ctx context.Context, message interfac if !ok { return ErrMessage } - topic := msg.Channel - if msg.Subtopic != "" { - topic = fmt.Sprintf("%s.%s", msg.Channel, msg.Subtopic) + topic := msg.GetChannel() + if msg.GetSubtopic() != "" { + topic = fmt.Sprintf("%s.%s", msg.GetChannel(), msg.GetSubtopic()) } pm := PageMetadata{ Topic: topic, @@ -136,9 +136,9 @@ func (ns *notifierService) ConsumeAsync(ctx context.Context, message interface{} ns.errCh <- ErrMessage return } - topic := msg.Channel - if msg.Subtopic != "" { - topic = fmt.Sprintf("%s.%s", msg.Channel, msg.Subtopic) + topic := msg.GetChannel() + if msg.GetSubtopic() != "" { + topic = fmt.Sprintf("%s.%s", msg.GetChannel(), msg.GetSubtopic()) } pm := PageMetadata{ Topic: topic, diff --git a/consumers/notifiers/smpp/notifier.go b/consumers/notifiers/smpp/notifier.go index 6daf94486f..bd2dd8fe99 100644 --- a/consumers/notifiers/smpp/notifier.go +++ b/consumers/notifiers/smpp/notifier.go @@ -56,7 +56,7 @@ func (n *notifier) Notify(from string, to []string, msg *messaging.Message) erro DestAddrTON: n.destAddrTON, SourceAddrNPI: n.sourceAddrNPI, DestAddrNPI: n.destAddrNPI, - Text: pdutext.Raw(msg.Payload), + Text: pdutext.Raw(msg.GetPayload()), Register: pdufield.NoDeliveryReceipt, } _, err := n.transmitter.Submit(send) diff --git a/consumers/notifiers/smtp/notifier.go b/consumers/notifiers/smtp/notifier.go index 7f0009469d..fb8d618e03 100644 --- a/consumers/notifiers/smtp/notifier.go +++ b/consumers/notifiers/smtp/notifier.go @@ -28,13 +28,13 @@ func New(agent *email.Agent) notifiers.Notifier { } func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error { - subject := fmt.Sprintf(`Notification for Channel %s`, msg.Channel) - if msg.Subtopic != "" { - subject = fmt.Sprintf("%s and subtopic %s", subject, msg.Subtopic) + subject := fmt.Sprintf(`Notification for Channel %s`, msg.GetChannel()) + if msg.GetSubtopic() != "" { + subject = fmt.Sprintf("%s and subtopic %s", subject, msg.GetSubtopic()) } - values := string(msg.Payload) - content := fmt.Sprintf(contentTemplate, msg.Publisher, msg.Protocol, values) + values := string(msg.GetPayload()) + content := fmt.Sprintf(contentTemplate, msg.GetPublisher(), msg.GetProtocol(), values) return n.agent.Send(to, from, subject, "", "", content, footer) } diff --git a/mqtt/forwarder.go b/mqtt/forwarder.go index a6639c7f58..c91e84c748 100644 --- a/mqtt/forwarder.go +++ b/mqtt/forwarder.go @@ -44,14 +44,14 @@ func (f forwarder) Forward(ctx context.Context, id string, sub messaging.Subscri func handle(ctx context.Context, pub messaging.Publisher, logger mglog.Logger) handleFunc { return func(msg *messaging.Message) error { - if msg.Protocol == protocol { + if msg.GetProtocol() == protocol { return nil } // Use concatenation instead of fmt.Sprintf for the // sake of simplicity and performance. - topic := "channels/" + msg.Channel + "/messages" - if msg.Subtopic != "" { - topic = topic + "/" + strings.ReplaceAll(msg.Subtopic, ".", "/") + topic := "channels/" + msg.GetChannel() + "/messages" + if msg.GetSubtopic() != "" { + topic = topic + "/" + strings.ReplaceAll(msg.GetSubtopic(), ".", "/") } go func() { diff --git a/mqtt/handler.go b/mqtt/handler.go index d989b10669..9921cef703 100644 --- a/mqtt/handler.go +++ b/mqtt/handler.go @@ -169,7 +169,7 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e Created: time.Now().UnixNano(), } - if err := h.publisher.Publish(ctx, msg.Channel, &msg); err != nil { + if err := h.publisher.Publish(ctx, msg.GetChannel(), &msg); err != nil { return errors.Wrap(ErrFailedPublishToMsgBroker, err) } diff --git a/opcua/gopcua/subscribe.go b/opcua/gopcua/subscribe.go index 3f6ed1711c..fff26ed54d 100644 --- a/opcua/gopcua/subscribe.go +++ b/opcua/gopcua/subscribe.go @@ -242,7 +242,7 @@ func (c client) publish(ctx context.Context, token string, m message) error { Created: time.Now().UnixNano(), } - if err := c.publisher.Publish(ctx, msg.Channel, &msg); err != nil { + if err := c.publisher.Publish(ctx, msg.GetChannel(), &msg); err != nil { return err } diff --git a/pkg/messaging/mqtt/pubsub_test.go b/pkg/messaging/mqtt/pubsub_test.go index b981fbe390..d0bdafc45d 100644 --- a/pkg/messaging/mqtt/pubsub_test.go +++ b/pkg/messaging/mqtt/pubsub_test.go @@ -460,7 +460,7 @@ type handler struct { } func (h handler) Handle(msg *messaging.Message) error { - if msg.Publisher != h.publisher { + if msg.GetPublisher() != h.publisher { h.msgChan <- msg } return nil diff --git a/pkg/messaging/nats/publisher.go b/pkg/messaging/nats/publisher.go index 50504d0ba2..2aca0b843c 100644 --- a/pkg/messaging/nats/publisher.go +++ b/pkg/messaging/nats/publisher.go @@ -73,8 +73,8 @@ func (pub *publisher) Publish(ctx context.Context, topic string, msg *messaging. } subject := fmt.Sprintf("%s.%s", pub.prefix, topic) - if msg.Subtopic != "" { - subject = fmt.Sprintf("%s.%s", subject, msg.Subtopic) + if msg.GetSubtopic() != "" { + subject = fmt.Sprintf("%s.%s", subject, msg.GetSubtopic()) } _, err = pub.js.Publish(ctx, subject, data) diff --git a/pkg/messaging/nats/tracing/publisher.go b/pkg/messaging/nats/tracing/publisher.go index e2bedf900f..320ccdcea9 100644 --- a/pkg/messaging/nats/tracing/publisher.go +++ b/pkg/messaging/nats/tracing/publisher.go @@ -40,7 +40,7 @@ func NewPublisher(config server.Config, tracer trace.Tracer, publisher messaging } func (pm *publisherMiddleware) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - ctx, span := tracing.CreateSpan(ctx, publishOP, msg.Publisher, topic, msg.Subtopic, len(msg.Payload), pm.host, trace.SpanKindClient, pm.tracer) + ctx, span := tracing.CreateSpan(ctx, publishOP, msg.GetPublisher(), topic, msg.GetSubtopic(), len(msg.GetPayload()), pm.host, trace.SpanKindClient, pm.tracer) defer span.End() span.SetAttributes(defaultAttributes...) diff --git a/pkg/messaging/nats/tracing/pubsub.go b/pkg/messaging/nats/tracing/pubsub.go index 18f10c3f70..f911dfd25a 100644 --- a/pkg/messaging/nats/tracing/pubsub.go +++ b/pkg/messaging/nats/tracing/pubsub.go @@ -82,7 +82,7 @@ type traceHandler struct { // Handle instruments the message handling operation. func (h *traceHandler) Handle(msg *messaging.Message) error { - _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.Subtopic, len(msg.Payload), h.host, trace.SpanKindConsumer, h.tracer) + _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.GetSubtopic(), len(msg.GetPayload()), h.host, trace.SpanKindConsumer, h.tracer) defer span.End() span.SetAttributes(defaultAttributes...) diff --git a/pkg/messaging/rabbitmq/publisher.go b/pkg/messaging/rabbitmq/publisher.go index 54e82c9611..0003758246 100644 --- a/pkg/messaging/rabbitmq/publisher.go +++ b/pkg/messaging/rabbitmq/publisher.go @@ -62,8 +62,8 @@ func (pub *publisher) Publish(ctx context.Context, topic string, msg *messaging. } subject := fmt.Sprintf("%s.%s", pub.prefix, topic) - if msg.Subtopic != "" { - subject = fmt.Sprintf("%s.%s", subject, msg.Subtopic) + if msg.GetSubtopic() != "" { + subject = fmt.Sprintf("%s.%s", subject, msg.GetSubtopic()) } subject = formatTopic(subject) diff --git a/pkg/messaging/rabbitmq/pubsub_test.go b/pkg/messaging/rabbitmq/pubsub_test.go index 2f42a7b9b0..2dcf3ecf9c 100644 --- a/pkg/messaging/rabbitmq/pubsub_test.go +++ b/pkg/messaging/rabbitmq/pubsub_test.go @@ -446,7 +446,7 @@ type handler struct { } func (h handler) Handle(msg *messaging.Message) error { - if msg.Publisher != h.publisher { + if msg.GetPublisher() != h.publisher { msgChan <- msg } return nil diff --git a/pkg/messaging/rabbitmq/tracing/publisher.go b/pkg/messaging/rabbitmq/tracing/publisher.go index 65208a1e6c..df5c926e77 100644 --- a/pkg/messaging/rabbitmq/tracing/publisher.go +++ b/pkg/messaging/rabbitmq/tracing/publisher.go @@ -41,7 +41,7 @@ func NewPublisher(config server.Config, tracer trace.Tracer, publisher messaging } func (pm *publisherMiddleware) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - ctx, span := tracing.CreateSpan(ctx, publishOP, msg.Publisher, topic, msg.Subtopic, len(msg.Payload), pm.host, trace.SpanKindClient, pm.tracer) + ctx, span := tracing.CreateSpan(ctx, publishOP, msg.GetPublisher(), topic, msg.GetSubtopic(), len(msg.GetPayload()), pm.host, trace.SpanKindClient, pm.tracer) defer span.End() span.SetAttributes(defaultAttributes...) diff --git a/pkg/messaging/rabbitmq/tracing/pubsub.go b/pkg/messaging/rabbitmq/tracing/pubsub.go index 18f10c3f70..f911dfd25a 100644 --- a/pkg/messaging/rabbitmq/tracing/pubsub.go +++ b/pkg/messaging/rabbitmq/tracing/pubsub.go @@ -82,7 +82,7 @@ type traceHandler struct { // Handle instruments the message handling operation. func (h *traceHandler) Handle(msg *messaging.Message) error { - _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.Subtopic, len(msg.Payload), h.host, trace.SpanKindConsumer, h.tracer) + _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.GetSubtopic(), len(msg.GetPayload()), h.host, trace.SpanKindConsumer, h.tracer) defer span.End() span.SetAttributes(defaultAttributes...) diff --git a/pkg/transformers/json/transformer.go b/pkg/transformers/json/transformer.go index 38e9855a59..fa032167df 100644 --- a/pkg/transformers/json/transformer.go +++ b/pkg/transformers/json/transformer.go @@ -50,11 +50,11 @@ func New(tfs []TimeField) transformers.Transformer { // Transform transforms Magistrala message to a list of JSON messages. func (ts *transformerService) Transform(msg *messaging.Message) (interface{}, error) { ret := Message{ - Publisher: msg.Publisher, - Created: msg.Created, - Protocol: msg.Protocol, - Channel: msg.Channel, - Subtopic: msg.Subtopic, + Publisher: msg.GetPublisher(), + Created: msg.GetCreated(), + Protocol: msg.GetProtocol(), + Channel: msg.GetChannel(), + Subtopic: msg.GetSubtopic(), } if ret.Subtopic == "" { @@ -68,7 +68,7 @@ func (ts *transformerService) Transform(msg *messaging.Message) (interface{}, er format := subs[len(subs)-1] var payload interface{} - if err := json.Unmarshal(msg.Payload, &payload); err != nil { + if err := json.Unmarshal(msg.GetPayload(), &payload); err != nil { return nil, errors.Wrap(ErrTransform, err) } diff --git a/pkg/transformers/senml/transformer.go b/pkg/transformers/senml/transformer.go index 1c292df864..1e253df632 100644 --- a/pkg/transformers/senml/transformer.go +++ b/pkg/transformers/senml/transformer.go @@ -44,7 +44,7 @@ func New(contentFormat string) transformers.Transformer { } func (t transformer) Transform(msg *messaging.Message) (interface{}, error) { - raw, err := senml.Decode(msg.Payload, t.format) + raw, err := senml.Decode(msg.GetPayload(), t.format) if err != nil { return nil, errors.Wrap(errDecode, err) } @@ -60,14 +60,14 @@ func (t transformer) Transform(msg *messaging.Message) (interface{}, error) { t := v.Time if t == 0 { // Convert the Unix timestamp in nanoseconds to float64 - t = float64(msg.Created) / float64(1e9) + t = float64(msg.GetCreated()) / float64(1e9) } msgs[i] = Message{ - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, + Channel: msg.GetChannel(), + Subtopic: msg.GetSubtopic(), + Publisher: msg.GetPublisher(), + Protocol: msg.GetProtocol(), Name: v.Name, Unit: v.Unit, Time: t, diff --git a/twins/mocks/messages.go b/twins/mocks/messages.go index 7a4802e4af..2e662f45c5 100644 --- a/twins/mocks/messages.go +++ b/twins/mocks/messages.go @@ -24,7 +24,7 @@ func NewBroker(sub map[string]string) messaging.Publisher { } func (mb mockBroker) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - if len(msg.Payload) == 0 { + if len(msg.GetPayload()) == 0 { return errors.New("failed to publish") } return nil diff --git a/twins/service.go b/twins/service.go index ad5a8d83e6..188f50fb74 100644 --- a/twins/service.go +++ b/twins/service.go @@ -248,7 +248,7 @@ func (ts *twinsService) ListStates(ctx context.Context, token string, offset, li func (ts *twinsService) SaveStates(ctx context.Context, msg *messaging.Message) error { var ids []string - channel, subtopic := msg.Channel, msg.Subtopic + channel, subtopic := msg.GetChannel(), msg.GetSubtopic() ids, err := ts.twinCache.IDs(ctx, channel, subtopic) if err != nil { return err @@ -283,17 +283,17 @@ func (ts *twinsService) saveState(ctx context.Context, msg *messaging.Message, t tw, err := ts.twins.RetrieveByID(ctx, twinID) if err != nil { - return fmt.Errorf("retrieving twin for %s failed: %s", msg.Publisher, err) + return fmt.Errorf("retrieving twin for %s failed: %s", msg.GetPublisher(), err) } var recs []senml.Record - if err := json.Unmarshal(msg.Payload, &recs); err != nil { - return fmt.Errorf("unmarshal payload for %s failed: %s", msg.Publisher, err) + if err := json.Unmarshal(msg.GetPayload(), &recs); err != nil { + return fmt.Errorf("unmarshal payload for %s failed: %s", msg.GetPublisher(), err) } st, err := ts.states.RetrieveLast(ctx, tw.ID) if err != nil { - return fmt.Errorf("retrieve last state for %s failed: %s", msg.Publisher, err) + return fmt.Errorf("retrieve last state for %s failed: %s", msg.GetPublisher(), err) } for _, rec := range recs { @@ -303,17 +303,17 @@ func (ts *twinsService) saveState(ctx context.Context, msg *messaging.Message, t return nil case update: if err := ts.states.Update(ctx, st); err != nil { - return fmt.Errorf("update state for %s failed: %s", msg.Publisher, err) + return fmt.Errorf("update state for %s failed: %s", msg.GetPublisher(), err) } case save: if err := ts.states.Save(ctx, st); err != nil { - return fmt.Errorf("save state for %s failed: %s", msg.Publisher, err) + return fmt.Errorf("save state for %s failed: %s", msg.GetPublisher(), err) } } } - twinID = msg.Publisher - b = msg.Payload + twinID = msg.GetPublisher() + b = msg.GetPayload() return nil } @@ -345,7 +345,7 @@ func (ts *twinsService) prepareState(st *State, tw *Twin, rec senml.Record, msg if !attr.PersistState { continue } - if attr.Channel == msg.Channel && (attr.Subtopic == SubtopicWildcard || attr.Subtopic == msg.Subtopic) { + if attr.Channel == msg.GetChannel() && (attr.Subtopic == SubtopicWildcard || attr.Subtopic == msg.GetSubtopic()) { action = update delta := math.Abs(float64(st.Created.UnixNano()) - recNano) if recNano == 0 || delta > float64(def.Delta) { @@ -419,7 +419,7 @@ func (ts *twinsService) publish(ctx context.Context, twinID *string, err *error, Created: time.Now().UnixNano(), } - if err := ts.publisher.Publish(ctx, msg.Channel, &msg); err != nil { + if err := ts.publisher.Publish(ctx, msg.GetChannel(), &msg); err != nil { ts.logger.Warn(fmt.Sprintf("Failed to publish notification on Message Broker: %s", err)) } } diff --git a/ws/client.go b/ws/client.go index 513a8e922a..cf33a105ac 100644 --- a/ws/client.go +++ b/ws/client.go @@ -36,5 +36,6 @@ func (c *Client) Handle(msg *messaging.Message) error { if msg.GetPublisher() == c.id { return nil } - return c.conn.WriteMessage(websocket.TextMessage, msg.Payload) + + return c.conn.WriteMessage(websocket.TextMessage, msg.GetPayload()) } diff --git a/ws/handler.go b/ws/handler.go index 487516a985..f608e9e338 100644 --- a/ws/handler.go +++ b/ws/handler.go @@ -189,7 +189,7 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e Created: time.Now().UnixNano(), } - if err := h.pubsub.Publish(ctx, msg.Channel, &msg); err != nil { + if err := h.pubsub.Publish(ctx, msg.GetChannel(), &msg); err != nil { return errors.Wrap(ErrFailedPublishToMsgBroker, err) } From 1b9d11aefe30a43b57675ba80c964d824fd5bf14 Mon Sep 17 00:00:00 2001 From: Nataly Musilah <115026536+Musilah@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:28:04 +0300 Subject: [PATCH 17/71] MG-234 - Improve Logging (#255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Musilah Signed-off-by: Musilah Co-authored-by: Musilah Co-authored-by: Dušan Borovčanin --- .github/workflows/tests.yml | 6 + .golangci.yml | 2 +- auth/api/http/domains/transport.go | 5 +- auth/api/http/keys/endpoint_test.go | 3 +- auth/api/http/keys/transport.go | 4 +- auth/api/http/transport.go | 4 +- auth/api/logging.go | 6 +- auth/spicedb/policies.go | 6 +- bootstrap/api/logging.go | 6 +- bootstrap/api/transport.go | 4 +- bootstrap/postgres/configs.go | 6 +- bootstrap/postgres/setup_test.go | 2 +- certs/api/logging.go | 6 +- certs/api/transport.go | 4 +- certs/postgres/certs.go | 6 +- certs/postgres/setup_test.go | 2 +- cmd/auth/main.go | 9 +- cmd/bootstrap/main.go | 11 +- cmd/cassandra-reader/main.go | 7 +- cmd/cassandra-writer/main.go | 7 +- cmd/certs/main.go | 9 +- cmd/coap/main.go | 4 +- cmd/http/main.go | 9 +- cmd/influxdb-reader/main.go | 7 +- cmd/influxdb-writer/main.go | 7 +- cmd/invitations/main.go | 8 +- cmd/lora/main.go | 15 +- cmd/mongodb-reader/main.go | 9 +- cmd/mongodb-writer/main.go | 7 +- cmd/mqtt/main.go | 20 +- cmd/opcua/main.go | 13 +- cmd/postgres-reader/main.go | 7 +- cmd/postgres-writer/main.go | 9 +- cmd/provision/main.go | 4 +- cmd/smpp-notifier/main.go | 9 +- cmd/smtp-notifier/main.go | 9 +- cmd/things/main.go | 8 +- cmd/timescale-reader/main.go | 7 +- cmd/timescale-writer/main.go | 7 +- cmd/twins/main.go | 11 +- cmd/users/main.go | 7 +- cmd/ws/main.go | 9 +- coap/api/logging.go | 6 +- coap/api/transport.go | 6 +- coap/client.go | 6 +- consumers/messages.go | 6 +- consumers/notifiers/api/logging.go | 6 +- consumers/notifiers/api/transport.go | 4 +- consumers/writers/api/logging.go | 6 +- consumers/writers/cassandra/setup_test.go | 4 +- consumers/writers/influxdb/consumer_test.go | 2 +- consumers/writers/mongodb/consumer_test.go | 2 +- go.mod | 21 +- go.sum | 618 +------------------- http/handler.go | 6 +- internal/apiutil/transport.go | 4 +- internal/apiutil/transport_test.go | 4 +- internal/groups/api/logging.go | 6 +- internal/server/coap/coap.go | 4 +- internal/server/grpc/grpc.go | 4 +- internal/server/http/http.go | 4 +- internal/server/server.go | 7 +- invitations/api/endpoint_test.go | 2 +- invitations/api/transport.go | 4 +- invitations/middleware/logging.go | 6 +- logger/level.go | 58 -- logger/level_test.go | 182 ------ logger/logger.go | 70 +-- logger/logger_test.go | 246 +------- logger/mock.go | 28 +- lora/api/logging.go | 6 +- lora/mqtt/sub.go | 6 +- mqtt/forwarder.go | 8 +- mqtt/handler.go | 6 +- opcua/adapter.go | 6 +- opcua/api/logging.go | 6 +- opcua/api/transport.go | 4 +- opcua/gopcua/browser.go | 6 +- opcua/gopcua/subscribe.go | 6 +- pkg/events/nats/subscriber.go | 8 +- pkg/events/rabbitmq/subscriber.go | 8 +- pkg/events/redis/subscriber.go | 6 +- pkg/events/store/brokers_nats.go | 4 +- pkg/events/store/brokers_rabbitmq.go | 4 +- pkg/events/store/brokers_redis.go | 4 +- pkg/messaging/brokers/brokers_nats.go | 4 +- pkg/messaging/brokers/brokers_rabbitmq.go | 4 +- pkg/messaging/handler/logging.go | 6 +- pkg/messaging/mqtt/pubsub.go | 6 +- pkg/messaging/mqtt/setup_test.go | 5 +- pkg/messaging/nats/pubsub.go | 6 +- pkg/messaging/rabbitmq/pubsub.go | 6 +- pkg/messaging/rabbitmq/setup_test.go | 5 +- provision/api/logging.go | 6 +- provision/api/transport.go | 4 +- provision/service.go | 6 +- readers/api/logging.go | 6 +- readers/cassandra/setup_test.go | 4 +- readers/influxdb/setup_test.go | 2 +- readers/mongodb/setup_test.go | 2 +- things/api/http/channels.go | 4 +- things/api/http/clients.go | 4 +- things/api/http/transport.go | 4 +- things/api/logging.go | 6 +- tools/mqtt-bench/bench.go | 2 +- twins/api/http/transport.go | 4 +- twins/api/logging.go | 6 +- twins/mocks/service.go | 3 +- twins/mongodb/init.go | 4 +- twins/mongodb/twins_test.go | 2 +- twins/service.go | 6 +- users/api/clients.go | 4 +- users/api/groups.go | 4 +- users/api/logging.go | 6 +- users/api/transport.go | 4 +- ws/api/logging.go | 6 +- ws/api/transport.go | 6 +- ws/handler.go | 6 +- 118 files changed, 411 insertions(+), 1453 deletions(-) delete mode 100644 logger/level.go delete mode 100644 logger/level_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3001f1d3ec..7122d58260 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,6 +51,12 @@ jobs: with: fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + cache-dependency-path: "go.sum" + - name: Check for changes in specific paths uses: dorny/paths-filter@v2 id: changes diff --git a/.golangci.yml b/.golangci.yml index fbc67c8d9d..a9bcbbe680 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,7 +18,7 @@ linters-settings: no-unaliased: true no-extra-aliases: false alias: - - pkg: github.com/mainflux/callhome/pkg/client + - pkg: github.com/absmach/callhome/pkg/client alias: chclient - pkg: github.com/absmach/magistrala/logger alias: mglog diff --git a/auth/api/http/domains/transport.go b/auth/api/http/domains/transport.go index 939b9acbb6..caacb74dba 100644 --- a/auth/api/http/domains/transport.go +++ b/auth/api/http/domains/transport.go @@ -4,16 +4,17 @@ package domains import ( + "log/slog" + "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func MakeHandler(svc auth.Service, mux *chi.Mux, logger mglog.Logger) *chi.Mux { +func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 096e348889..65ed1237be 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -78,8 +78,7 @@ func newService() (auth.Service, *mocks.KeyRepository) { } func newServer(svc auth.Service) *httptest.Server { - logger := mglog.NewMock() - mux := httpapi.MakeHandler(svc, logger, "") + mux := httpapi.MakeHandler(svc, mglog.NewMock(), "") return httptest.NewServer(mux) } diff --git a/auth/api/http/keys/transport.go b/auth/api/http/keys/transport.go index 858349ada4..cbd2afd317 100644 --- a/auth/api/http/keys/transport.go +++ b/auth/api/http/keys/transport.go @@ -6,13 +6,13 @@ package keys import ( "context" "encoding/json" + "log/slog" "net/http" "strings" "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -21,7 +21,7 @@ import ( const contentType = "application/json" // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc auth.Service, mux *chi.Mux, logger mglog.Logger) *chi.Mux { +func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/auth/api/http/transport.go b/auth/api/http/transport.go index 23381ca04b..5e31ee553f 100644 --- a/auth/api/http/transport.go +++ b/auth/api/http/transport.go @@ -3,19 +3,19 @@ package http import ( + "log/slog" "net/http" "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/auth/api/http/domains" "github.com/absmach/magistrala/auth/api/http/keys" - mglog "github.com/absmach/magistrala/logger" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc auth.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc auth.Service, logger *slog.Logger, instanceID string) http.Handler { mux := chi.NewRouter() mux = keys.MakeHandler(svc, mux, logger) diff --git a/auth/api/logging.go b/auth/api/logging.go index e96f86faf2..f73b9b05fe 100644 --- a/auth/api/logging.go +++ b/auth/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/auth" - mglog "github.com/absmach/magistrala/logger" ) var _ auth.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc auth.Service } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc auth.Service, logger mglog.Logger) auth.Service { +func LoggingMiddleware(svc auth.Service, logger *slog.Logger) auth.Service { return &loggingMiddleware{logger, svc} } diff --git a/auth/spicedb/policies.go b/auth/spicedb/policies.go index 9842bf99c4..4012569253 100644 --- a/auth/spicedb/policies.go +++ b/auth/spicedb/policies.go @@ -7,9 +7,9 @@ import ( "context" "fmt" "io" + "log/slog" "github.com/absmach/magistrala/auth" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" @@ -33,10 +33,10 @@ var ( type policyAgent struct { client *authzed.ClientWithExperimental permissionClient v1.PermissionsServiceClient - logger mglog.Logger + logger *slog.Logger } -func NewPolicyAgent(client *authzed.ClientWithExperimental, logger mglog.Logger) auth.PolicyAgent { +func NewPolicyAgent(client *authzed.ClientWithExperimental, logger *slog.Logger) auth.PolicyAgent { return &policyAgent{ client: client, permissionClient: client.PermissionsServiceClient, diff --git a/bootstrap/api/logging.go b/bootstrap/api/logging.go index 1c28075528..43658a58e8 100644 --- a/bootstrap/api/logging.go +++ b/bootstrap/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/bootstrap" - mglog "github.com/absmach/magistrala/logger" ) var _ bootstrap.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc bootstrap.Service } // LoggingMiddleware adds logging facilities to the bootstrap service. -func LoggingMiddleware(svc bootstrap.Service, logger mglog.Logger) bootstrap.Service { +func LoggingMiddleware(svc bootstrap.Service, logger *slog.Logger) bootstrap.Service { return &loggingMiddleware{logger, svc} } diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index 4662adc7b0..ff3eee6a65 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -6,6 +6,7 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "net/url" "strings" @@ -13,7 +14,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-chi/chi/v5" @@ -38,7 +38,7 @@ var ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/bootstrap/postgres/configs.go b/bootstrap/postgres/configs.go index 0271af3098..74fe79b275 100644 --- a/bootstrap/postgres/configs.go +++ b/bootstrap/postgres/configs.go @@ -8,12 +8,12 @@ import ( "database/sql" "encoding/json" "fmt" + "log/slog" "strings" "time" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/internal/postgres" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" "github.com/jackc/pgerrcode" @@ -37,12 +37,12 @@ var _ bootstrap.ConfigRepository = (*configRepository)(nil) type configRepository struct { db postgres.Database - log mglog.Logger + log *slog.Logger } // NewConfigRepository instantiates a PostgreSQL implementation of config // repository. -func NewConfigRepository(db postgres.Database, log mglog.Logger) bootstrap.ConfigRepository { +func NewConfigRepository(db postgres.Database, log *slog.Logger) bootstrap.ConfigRepository { return &configRepository{db: db, log: log} } diff --git a/bootstrap/postgres/setup_test.go b/bootstrap/postgres/setup_test.go index 3d200786e2..6bc0c324d0 100644 --- a/bootstrap/postgres/setup_test.go +++ b/bootstrap/postgres/setup_test.go @@ -16,7 +16,7 @@ import ( ) var ( - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") db *sqlx.DB ) diff --git a/certs/api/logging.go b/certs/api/logging.go index ba8e6e3842..63eba824c2 100644 --- a/certs/api/logging.go +++ b/certs/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/certs" - mglog "github.com/absmach/magistrala/logger" ) var _ certs.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc certs.Service } // LoggingMiddleware adds logging facilities to the bootstrap service. -func LoggingMiddleware(svc certs.Service, logger mglog.Logger) certs.Service { +func LoggingMiddleware(svc certs.Service, logger *slog.Logger) certs.Service { return &loggingMiddleware{logger, svc} } diff --git a/certs/api/transport.go b/certs/api/transport.go index fc435af979..d61ba52c8a 100644 --- a/certs/api/transport.go +++ b/certs/api/transport.go @@ -6,12 +6,12 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "github.com/absmach/magistrala" "github.com/absmach/magistrala/certs" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-chi/chi/v5" @@ -29,7 +29,7 @@ const ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc certs.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc certs.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/certs/postgres/certs.go b/certs/postgres/certs.go index 46b07f8b8c..ea6667253c 100644 --- a/certs/postgres/certs.go +++ b/certs/postgres/certs.go @@ -7,11 +7,11 @@ import ( "context" "database/sql" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/certs" "github.com/absmach/magistrala/internal/postgres" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" @@ -29,12 +29,12 @@ type Cert struct { type certsRepository struct { db postgres.Database - log mglog.Logger + log *slog.Logger } // NewRepository instantiates a PostgreSQL implementation of certs // repository. -func NewRepository(db postgres.Database, log mglog.Logger) certs.Repository { +func NewRepository(db postgres.Database, log *slog.Logger) certs.Repository { return &certsRepository{db: db, log: log} } diff --git a/certs/postgres/setup_test.go b/certs/postgres/setup_test.go index d3002690ee..803411b5ad 100644 --- a/certs/postgres/setup_test.go +++ b/certs/postgres/setup_test.go @@ -17,7 +17,7 @@ import ( ) var ( - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") db *sqlx.DB ) diff --git a/cmd/auth/main.go b/cmd/auth/main.go index a61a77b833..bacbeccd9c 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -7,10 +7,12 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" "time" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" api "github.com/absmach/magistrala/auth/api" @@ -34,7 +36,6 @@ import ( "github.com/authzed/grpcutil" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -79,7 +80,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - logger.Fatal(fmt.Sprintf("failed to init logger: %s", err.Error())) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -95,7 +96,7 @@ func main() { dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } db, err := pgclient.Setup(dbConfig, *apostgres.Migration()) @@ -200,7 +201,7 @@ func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, sch return nil } -func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger mglog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { +func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { database := postgres.NewDatabase(db, dbConfig, tracer) keysRepo := apostgres.New(database) domainsRepo := apostgres.NewDomainRepository(database) diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index 9a31dd8ec2..2262dd4db3 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/bootstrap/api" @@ -31,7 +33,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -71,7 +72,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -88,7 +89,7 @@ func main() { // Create new postgres client dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } db, err := pgclient.Setup(dbConfig, *bootstrappg.Migration()) if err != nil { @@ -167,7 +168,7 @@ func main() { } } -func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db *sqlx.DB, tracer trace.Tracer, logger mglog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { +func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) repoConfig := bootstrappg.NewConfigRepository(database, logger) @@ -194,7 +195,7 @@ func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db return svc, nil } -func subscribeToThingsES(ctx context.Context, svc bootstrap.Service, cfg config, logger mglog.Logger) error { +func subscribeToThingsES(ctx context.Context, svc bootstrap.Service, cfg config, logger *slog.Logger) error { subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) if err != nil { return err diff --git a/cmd/cassandra-reader/main.go b/cmd/cassandra-reader/main.go index 8bdaba670c..f2ecc30159 100644 --- a/cmd/cassandra-reader/main.go +++ b/cmd/cassandra-reader/main.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "log" + "log/slog" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" cassandraclient "github.com/absmach/magistrala/internal/clients/cassandra" @@ -23,7 +25,6 @@ import ( "github.com/absmach/magistrala/readers/cassandra" "github.com/caarlos0/env/v10" "github.com/gocql/gocql" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -54,7 +55,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -142,7 +143,7 @@ func main() { } } -func newService(csdSession *gocql.Session, logger mglog.Logger) readers.MessageRepository { +func newService(csdSession *gocql.Session, logger *slog.Logger) readers.MessageRepository { repo := cassandra.New(csdSession) repo = api.LoggingMiddleware(repo, logger) counter, latency := internal.MakeMetrics("cassandra", "message_reader") diff --git a/cmd/cassandra-writer/main.go b/cmd/cassandra-writer/main.go index cc23d09eb8..20ad38f85c 100644 --- a/cmd/cassandra-writer/main.go +++ b/cmd/cassandra-writer/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" consumertracing "github.com/absmach/magistrala/consumers/tracing" @@ -27,7 +29,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/gocql/gocql" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -60,7 +61,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -145,7 +146,7 @@ func main() { } } -func newService(session *gocql.Session, logger mglog.Logger) consumers.BlockingConsumer { +func newService(session *gocql.Session, logger *slog.Logger) consumers.BlockingConsumer { repo := cassandra.New(session) repo = api.LoggingMiddleware(repo, logger) counter, latency := internal.MakeMetrics("cassandra", "message_writer") diff --git a/cmd/certs/main.go b/cmd/certs/main.go index 2f6445adcf..598d91a585 100644 --- a/cmd/certs/main.go +++ b/cmd/certs/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/certs" "github.com/absmach/magistrala/certs/api" @@ -29,7 +31,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -73,7 +74,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -102,7 +103,7 @@ func main() { dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } db, err := pgclient.Setup(dbConfig, *certspg.Migration()) if err != nil { @@ -170,7 +171,7 @@ func main() { } } -func newService(authClient magistrala.AuthServiceClient, db *sqlx.DB, tracer trace.Tracer, logger mglog.Logger, cfg config, dbConfig pgclient.Config, pkiAgent vault.Agent) certs.Service { +func newService(authClient magistrala.AuthServiceClient, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config, pkiAgent vault.Agent) certs.Service { database := postgres.NewDatabase(db, dbConfig, tracer) certsRepo := certspg.NewRepository(database, logger) config := mgsdk.Config{ diff --git a/cmd/coap/main.go b/cmd/coap/main.go index 11bed22621..036dcb6030 100644 --- a/cmd/coap/main.go +++ b/cmd/coap/main.go @@ -11,6 +11,7 @@ import ( "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/coap" "github.com/absmach/magistrala/coap/api" @@ -26,7 +27,6 @@ import ( brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -59,7 +59,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int diff --git a/cmd/http/main.go b/cmd/http/main.go index 911009d3e1..9a1cabd874 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -8,10 +8,12 @@ import ( "context" "fmt" "log" + "log/slog" "net/http" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" adapter "github.com/absmach/magistrala/http" "github.com/absmach/magistrala/http/api" @@ -29,7 +31,6 @@ import ( mproxy "github.com/absmach/mproxy/pkg/http" "github.com/absmach/mproxy/pkg/session" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -63,7 +64,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -150,7 +151,7 @@ func main() { } } -func newService(pub messaging.Publisher, tc magistrala.AuthzServiceClient, logger mglog.Logger, tracer trace.Tracer) session.Handler { +func newService(pub messaging.Publisher, tc magistrala.AuthzServiceClient, logger *slog.Logger, tracer trace.Tracer) session.Handler { svc := adapter.NewHandler(pub, logger, tc) svc = handler.NewTracing(tracer, svc) svc = handler.LoggingMiddleware(svc, logger) @@ -159,7 +160,7 @@ func newService(pub messaging.Publisher, tc magistrala.AuthzServiceClient, logge return svc } -func proxyHTTP(ctx context.Context, cfg server.Config, logger mglog.Logger, sessionHandler session.Handler) error { +func proxyHTTP(ctx context.Context, cfg server.Config, logger *slog.Logger, sessionHandler session.Handler) error { address := fmt.Sprintf("%s:%s", "", cfg.Port) target := fmt.Sprintf("%s:%s", targetHTTPHost, targetHTTPPort) mp, err := mproxy.NewProxy(address, target, sessionHandler, logger) diff --git a/cmd/influxdb-reader/main.go b/cmd/influxdb-reader/main.go index a3acfb383d..af911851f7 100644 --- a/cmd/influxdb-reader/main.go +++ b/cmd/influxdb-reader/main.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "log" + "log/slog" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" influxdbclient "github.com/absmach/magistrala/internal/clients/influxdb" @@ -23,7 +25,6 @@ import ( "github.com/absmach/magistrala/readers/influxdb" "github.com/caarlos0/env/v10" influxdb2 "github.com/influxdata/influxdb-client-go/v2" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -53,7 +54,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -150,7 +151,7 @@ func main() { } } -func newService(client influxdb2.Client, repocfg influxdb.RepoConfig, logger mglog.Logger) readers.MessageRepository { +func newService(client influxdb2.Client, repocfg influxdb.RepoConfig, logger *slog.Logger) readers.MessageRepository { repo := influxdb.New(client, repocfg) repo = api.LoggingMiddleware(repo, logger) counter, latency := internal.MakeMetrics("influxdb", "message_reader") diff --git a/cmd/influxdb-writer/main.go b/cmd/influxdb-writer/main.go index 8b8a1488e9..5e26064304 100644 --- a/cmd/influxdb-writer/main.go +++ b/cmd/influxdb-writer/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" consumertracing "github.com/absmach/magistrala/consumers/tracing" @@ -25,7 +27,6 @@ import ( brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -57,7 +58,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -125,7 +126,7 @@ func main() { repo = consumertracing.NewAsync(tracer, repo, httpServerConfig) // Start consuming and logging errors. - go func(log mglog.Logger) { + go func(log *slog.Logger) { for err := range repo.Errors() { if err != nil { log.Error(err.Error()) diff --git a/cmd/invitations/main.go b/cmd/invitations/main.go index f896f816bd..c49651ffe0 100644 --- a/cmd/invitations/main.go +++ b/cmd/invitations/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" "github.com/absmach/magistrala/internal/clients/jaeger" @@ -28,7 +30,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -63,8 +64,9 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } + var exitCode int defer mglog.ExitWithError(&exitCode) @@ -152,7 +154,7 @@ func main() { } } -func newService(db *sqlx.DB, dbConfig clientspg.Config, authClient magistrala.AuthServiceClient, tracer trace.Tracer, conf config, logger mglog.Logger) (invitations.Service, error) { +func newService(db *sqlx.DB, dbConfig clientspg.Config, authClient magistrala.AuthServiceClient, tracer trace.Tracer, conf config, logger *slog.Logger) (invitations.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) repo := invitationspg.NewRepository(database) diff --git a/cmd/lora/main.go b/cmd/lora/main.go index 8238b209d3..348029afc9 100644 --- a/cmd/lora/main.go +++ b/cmd/lora/main.go @@ -8,10 +8,12 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" "time" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" "github.com/absmach/magistrala/internal/clients/jaeger" @@ -31,7 +33,6 @@ import ( "github.com/caarlos0/env/v10" mqttpaho "github.com/eclipse/paho.mqtt.golang" "github.com/go-redis/redis/v8" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -74,7 +75,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -166,7 +167,7 @@ func main() { } } -func connectToMQTTBroker(burl, user, password string, timeout time.Duration, logger mglog.Logger) (mqttpaho.Client, error) { +func connectToMQTTBroker(burl, user, password string, timeout time.Duration, logger *slog.Logger) (mqttpaho.Client, error) { opts := mqttpaho.NewClientOptions() opts.AddBroker(burl) opts.SetUsername(user) @@ -187,7 +188,7 @@ func connectToMQTTBroker(burl, user, password string, timeout time.Duration, log return client, nil } -func subscribeToLoRaBroker(svc lora.Service, mc mqttpaho.Client, timeout time.Duration, topic string, logger mglog.Logger) error { +func subscribeToLoRaBroker(svc lora.Service, mc mqttpaho.Client, timeout time.Duration, topic string, logger *slog.Logger) error { mqttBroker := mqtt.NewBroker(svc, mc, timeout, logger) logger.Info("Subscribed to Lora MQTT broker") if err := mqttBroker.Subscribe(topic); err != nil { @@ -196,7 +197,7 @@ func subscribeToLoRaBroker(svc lora.Service, mc mqttpaho.Client, timeout time.Du return nil } -func subscribeToThingsES(ctx context.Context, svc lora.Service, cfg config, logger mglog.Logger) error { +func subscribeToThingsES(ctx context.Context, svc lora.Service, cfg config, logger *slog.Logger) error { subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) if err != nil { return err @@ -209,12 +210,12 @@ func subscribeToThingsES(ctx context.Context, svc lora.Service, cfg config, logg return subscriber.Subscribe(ctx, handler) } -func newRouteMapRepository(client *redis.Client, prefix string, logger mglog.Logger) lora.RouteMapRepository { +func newRouteMapRepository(client *redis.Client, prefix string, logger *slog.Logger) lora.RouteMapRepository { logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix)) return events.NewRouteMapRepository(client, prefix) } -func newService(pub messaging.Publisher, rmConn *redis.Client, thingsRMPrefix, channelsRMPrefix, connsRMPrefix string, logger mglog.Logger) lora.Service { +func newService(pub messaging.Publisher, rmConn *redis.Client, thingsRMPrefix, channelsRMPrefix, connsRMPrefix string, logger *slog.Logger) lora.Service { thingsRM := newRouteMapRepository(rmConn, thingsRMPrefix, logger) chansRM := newRouteMapRepository(rmConn, channelsRMPrefix, logger) connsRM := newRouteMapRepository(rmConn, connsRMPrefix, logger) diff --git a/cmd/mongodb-reader/main.go b/cmd/mongodb-reader/main.go index 2ff6dc4a0a..c0a349733a 100644 --- a/cmd/mongodb-reader/main.go +++ b/cmd/mongodb-reader/main.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "log" + "log/slog" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" mongoclient "github.com/absmach/magistrala/internal/clients/mongo" @@ -22,7 +24,6 @@ import ( "github.com/absmach/magistrala/readers/api" "github.com/absmach/magistrala/readers/mongodb" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "go.mongodb.org/mongo-driver/mongo" "golang.org/x/sync/errgroup" ) @@ -53,7 +54,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -85,7 +86,7 @@ func main() { ac, acHandler, err := auth.Setup(authConfig) if err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) exitCode = 1 return } @@ -136,7 +137,7 @@ func main() { } } -func newService(db *mongo.Database, logger mglog.Logger) readers.MessageRepository { +func newService(db *mongo.Database, logger *slog.Logger) readers.MessageRepository { repo := mongodb.New(db) repo = api.LoggingMiddleware(repo, logger) counter, latency := internal.MakeMetrics("mongodb", "message_reader") diff --git a/cmd/mongodb-writer/main.go b/cmd/mongodb-writer/main.go index 4e85cce11a..cec6e8d434 100644 --- a/cmd/mongodb-writer/main.go +++ b/cmd/mongodb-writer/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" consumertracing "github.com/absmach/magistrala/consumers/tracing" @@ -26,7 +28,6 @@ import ( brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "go.mongodb.org/mongo-driver/mongo" "golang.org/x/sync/errgroup" ) @@ -59,7 +60,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -138,7 +139,7 @@ func main() { } } -func newService(db *mongo.Database, logger mglog.Logger) consumers.BlockingConsumer { +func newService(db *mongo.Database, logger *slog.Logger) consumers.BlockingConsumer { repo := mongodb.New(db) repo = api.LoggingMiddleware(repo, logger) counter, latency := internal.MakeMetrics("mongodb", "message_writer") diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index b6671a1766..68b190d18f 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "net/url" "os" @@ -16,6 +17,7 @@ import ( "syscall" "time" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" jaegerclient "github.com/absmach/magistrala/internal/clients/jaeger" "github.com/absmach/magistrala/internal/server" @@ -35,7 +37,6 @@ import ( "github.com/absmach/mproxy/pkg/session" "github.com/caarlos0/env/v10" "github.com/cenkalti/backoff/v4" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -76,7 +77,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -187,14 +188,15 @@ func main() { go chc.CallHome(ctx) } + var interceptor session.Interceptor logger.Info(fmt.Sprintf("Starting MQTT proxy on port %s", cfg.MQTTPort)) g.Go(func() error { - return proxyMQTT(ctx, cfg, logger, h) + return proxyMQTT(ctx, cfg, logger, h, interceptor) }) logger.Info(fmt.Sprintf("Starting MQTT over WS proxy on port %s", cfg.HTTPPort)) g.Go(func() error { - return proxyWS(ctx, cfg, logger, h) + return proxyWS(ctx, cfg, logger, h, interceptor) }) g.Go(func() error { @@ -206,10 +208,10 @@ func main() { } } -func proxyMQTT(ctx context.Context, cfg config, logger mglog.Logger, sessionHandler session.Handler) error { +func proxyMQTT(ctx context.Context, cfg config, logger *slog.Logger, sessionHandler session.Handler, interceptor session.Interceptor) error { address := fmt.Sprintf(":%s", cfg.MQTTPort) target := fmt.Sprintf("%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort) - mproxy := mp.New(address, target, sessionHandler, logger) + mproxy := mp.New(address, target, sessionHandler, interceptor, logger) errCh := make(chan error) go func() { @@ -225,9 +227,9 @@ func proxyMQTT(ctx context.Context, cfg config, logger mglog.Logger, sessionHand } } -func proxyWS(ctx context.Context, cfg config, logger mglog.Logger, sessionHandler session.Handler) error { +func proxyWS(ctx context.Context, cfg config, logger *slog.Logger, sessionHandler session.Handler, interceptor session.Interceptor) error { target := fmt.Sprintf("%s:%s", cfg.HTTPTargetHost, cfg.HTTPTargetPort) - wp := websocket.New(target, cfg.HTTPTargetPath, "ws", sessionHandler, logger) + wp := websocket.New(target, cfg.HTTPTargetPath, "ws", sessionHandler, interceptor, logger) http.Handle("/mqtt", wp.Handler()) errCh := make(chan error) @@ -263,7 +265,7 @@ func healthcheck(cfg config) func() error { } } -func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger mglog.Logger) error { +func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger) error { c := make(chan os.Signal, 2) signal.Notify(c, syscall.SIGINT, syscall.SIGABRT) select { diff --git a/cmd/opcua/main.go b/cmd/opcua/main.go index 355a729ef6..2f4f63af3e 100644 --- a/cmd/opcua/main.go +++ b/cmd/opcua/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" jaegerclient "github.com/absmach/magistrala/internal/clients/jaeger" @@ -29,7 +31,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/go-redis/redis/v8" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -73,7 +74,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -161,7 +162,7 @@ func main() { } } -func subscribeToStoredSubs(ctx context.Context, sub opcua.Subscriber, cfg opcua.Config, logger mglog.Logger) { +func subscribeToStoredSubs(ctx context.Context, sub opcua.Subscriber, cfg opcua.Config, logger *slog.Logger) { // Get all stored subscriptions nodes, err := db.ReadAll() if err != nil { @@ -179,7 +180,7 @@ func subscribeToStoredSubs(ctx context.Context, sub opcua.Subscriber, cfg opcua. } } -func subscribeToThingsES(ctx context.Context, svc opcua.Service, cfg config, logger mglog.Logger) error { +func subscribeToThingsES(ctx context.Context, svc opcua.Service, cfg config, logger *slog.Logger) error { subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) if err != nil { return err @@ -192,12 +193,12 @@ func subscribeToThingsES(ctx context.Context, svc opcua.Service, cfg config, log return subscriber.Subscribe(ctx, handler) } -func newRouteMapRepositoy(client *redis.Client, prefix string, logger mglog.Logger) opcua.RouteMapRepository { +func newRouteMapRepositoy(client *redis.Client, prefix string, logger *slog.Logger) opcua.RouteMapRepository { logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix)) return events.NewRouteMapRepository(client, prefix) } -func newService(sub opcua.Subscriber, browser opcua.Browser, thingRM, chanRM, connRM opcua.RouteMapRepository, opcuaConfig opcua.Config, logger mglog.Logger) opcua.Service { +func newService(sub opcua.Subscriber, browser opcua.Browser, thingRM, chanRM, connRM opcua.RouteMapRepository, opcuaConfig opcua.Config, logger *slog.Logger) opcua.Service { svc := opcua.New(sub, browser, thingRM, chanRM, connRM, opcuaConfig, logger) svc = api.LoggingMiddleware(svc, logger) counter, latency := internal.MakeMetrics("opc_ua_adapter", "api") diff --git a/cmd/postgres-reader/main.go b/cmd/postgres-reader/main.go index 52736e093c..b4b0e25a0f 100644 --- a/cmd/postgres-reader/main.go +++ b/cmd/postgres-reader/main.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "log" + "log/slog" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" pgclient "github.com/absmach/magistrala/internal/clients/postgres" @@ -23,7 +25,6 @@ import ( "github.com/absmach/magistrala/readers/postgres" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -54,7 +55,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -144,7 +145,7 @@ func main() { } } -func newService(db *sqlx.DB, logger mglog.Logger) readers.MessageRepository { +func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { svc := postgres.New(db) svc = api.LoggingMiddleware(svc, logger) counter, latency := internal.MakeMetrics("postgres", "message_reader") diff --git a/cmd/postgres-writer/main.go b/cmd/postgres-writer/main.go index e583cc7396..bd3d624a82 100644 --- a/cmd/postgres-writer/main.go +++ b/cmd/postgres-writer/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" consumertracing "github.com/absmach/magistrala/consumers/tracing" @@ -27,7 +29,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -60,7 +61,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -89,7 +90,7 @@ func main() { } db, err := pgclient.Setup(dbConfig, *writerpg.Migration()) if err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } defer db.Close() @@ -144,7 +145,7 @@ func main() { } } -func newService(db *sqlx.DB, logger mglog.Logger) consumers.BlockingConsumer { +func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { svc := writerpg.New(db) svc = api.LoggingMiddleware(svc, logger) counter, latency := internal.MakeMetrics("postgres", "message_writer") diff --git a/cmd/provision/main.go b/cmd/provision/main.go index 5034063d7d..6057fd4359 100644 --- a/cmd/provision/main.go +++ b/cmd/provision/main.go @@ -12,6 +12,7 @@ import ( "os" "reflect" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/server" httpserver "github.com/absmach/magistrala/internal/server/http" @@ -24,7 +25,6 @@ import ( "github.com/absmach/magistrala/provision" "github.com/absmach/magistrala/provision/api" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -50,7 +50,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.Server.LogLevel) if err != nil { - log.Fatalf(err.Error()) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int diff --git a/cmd/smpp-notifier/main.go b/cmd/smpp-notifier/main.go index f4870e5aab..6ae77b4d2f 100644 --- a/cmd/smpp-notifier/main.go +++ b/cmd/smpp-notifier/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" "github.com/absmach/magistrala/consumers/notifiers" @@ -31,7 +33,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -67,7 +68,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -89,7 +90,7 @@ func main() { } db, err := pgclient.Setup(dbConfig, *notifierpg.Migration()) if err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } defer db.Close() @@ -172,7 +173,7 @@ func main() { } } -func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, sc mgsmpp.Config, logger mglog.Logger) notifiers.Service { +func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, sc mgsmpp.Config, logger *slog.Logger) notifiers.Service { database := notifierpg.NewDatabase(db, tracer) repo := tracing.New(tracer, notifierpg.New(database)) idp := ulid.New() diff --git a/cmd/smtp-notifier/main.go b/cmd/smtp-notifier/main.go index 86226b3930..58ac55450c 100644 --- a/cmd/smtp-notifier/main.go +++ b/cmd/smtp-notifier/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" "github.com/absmach/magistrala/consumers/notifiers" @@ -32,7 +34,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -68,7 +69,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -90,7 +91,7 @@ func main() { } db, err := pgclient.Setup(dbConfig, *notifierpg.Migration()) if err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) exitCode = 1 return } @@ -182,7 +183,7 @@ func main() { } } -func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, ec email.Config, logger mglog.Logger) (notifiers.Service, error) { +func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, ec email.Config, logger *slog.Logger) (notifiers.Service, error) { database := notifierpg.NewDatabase(db, tracer) repo := tracing.New(tracer, notifierpg.New(database)) idp := ulid.New() diff --git a/cmd/things/main.go b/cmd/things/main.go index 1c0e65a334..1357698131 100644 --- a/cmd/things/main.go +++ b/cmd/things/main.go @@ -8,10 +8,12 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" "time" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" jaegerclient "github.com/absmach/magistrala/internal/clients/jaeger" @@ -43,7 +45,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-redis/redis/v8" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -86,9 +87,10 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } + var logger *slog.Logger logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -218,7 +220,7 @@ func main() { } } -func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authClient magistrala.AuthServiceClient, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger mglog.Logger) (things.Service, groups.Service, error) { +func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authClient magistrala.AuthServiceClient, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger *slog.Logger) (things.Service, groups.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) cRepo := thingspg.NewRepository(database) gRepo := gpostgres.New(database) diff --git a/cmd/timescale-reader/main.go b/cmd/timescale-reader/main.go index 589be05673..57550067ae 100644 --- a/cmd/timescale-reader/main.go +++ b/cmd/timescale-reader/main.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "log" + "log/slog" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" pgclient "github.com/absmach/magistrala/internal/clients/postgres" @@ -23,7 +25,6 @@ import ( "github.com/absmach/magistrala/readers/timescale" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -54,7 +55,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -142,7 +143,7 @@ func main() { } } -func newService(db *sqlx.DB, logger mglog.Logger) readers.MessageRepository { +func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { svc := timescale.New(db) svc = api.LoggingMiddleware(svc, logger) counter, latency := internal.MakeMetrics("timescale", "message_reader") diff --git a/cmd/timescale-writer/main.go b/cmd/timescale-writer/main.go index 1f78042ffe..c678819ed8 100644 --- a/cmd/timescale-writer/main.go +++ b/cmd/timescale-writer/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers" consumertracing "github.com/absmach/magistrala/consumers/tracing" @@ -27,7 +29,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/caarlos0/env/v10" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "golang.org/x/sync/errgroup" ) @@ -60,7 +61,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatal(err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -146,7 +147,7 @@ func main() { } } -func newService(db *sqlx.DB, logger mglog.Logger) consumers.BlockingConsumer { +func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { svc := timescale.New(db) svc = api.LoggingMiddleware(svc, logger) counter, latency := internal.MakeMetrics("timescale", "message_writer") diff --git a/cmd/twins/main.go b/cmd/twins/main.go index acf0dc367d..c6c05d9b05 100644 --- a/cmd/twins/main.go +++ b/cmd/twins/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" jaegerclient "github.com/absmach/magistrala/internal/clients/jaeger" @@ -33,7 +35,6 @@ import ( "github.com/absmach/magistrala/twins/tracing" "github.com/caarlos0/env/v10" "github.com/go-redis/redis/v8" - chclient "github.com/mainflux/callhome/pkg/client" "go.mongodb.org/mongo-driver/mongo" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" @@ -72,7 +73,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -180,7 +181,7 @@ func main() { } } -func newService(ctx context.Context, id string, ps messaging.PubSub, cfg config, users magistrala.AuthServiceClient, tracer trace.Tracer, db *mongo.Database, cacheclient *redis.Client, logger mglog.Logger) (twins.Service, error) { +func newService(ctx context.Context, id string, ps messaging.PubSub, cfg config, users magistrala.AuthServiceClient, tracer trace.Tracer, db *mongo.Database, cacheclient *redis.Client, logger *slog.Logger) (twins.Service, error) { twinRepo := twmongodb.NewTwinRepository(db) twinRepo = tracing.TwinRepositoryMiddleware(tracer, twinRepo) @@ -209,13 +210,13 @@ func newService(ctx context.Context, id string, ps messaging.PubSub, cfg config, Handler: handle(ctx, logger, cfg.ChannelID, svc), } if err = ps.Subscribe(ctx, subCfg); err != nil { - logger.Fatal(err.Error()) + logger.Error(err.Error()) } return svc, nil } -func handle(ctx context.Context, logger mglog.Logger, chanID string, svc twins.Service) handlerFunc { +func handle(ctx context.Context, logger *slog.Logger, chanID string, svc twins.Service) handlerFunc { return func(msg *messaging.Message) error { if msg.GetChannel() == chanID { return nil diff --git a/cmd/users/main.go b/cmd/users/main.go index db7e17d74e..1b53de1f10 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -8,11 +8,13 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" "regexp" "time" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" authSvc "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal" @@ -43,7 +45,6 @@ import ( "github.com/caarlos0/env/v10" "github.com/go-chi/chi/v5" "github.com/jmoiron/sqlx" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -90,7 +91,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - logger.Fatal(fmt.Sprintf("failed to init logger: %s", err.Error())) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -192,7 +193,7 @@ func main() { } } -func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger mglog.Logger) (users.Service, groups.Service, error) { +func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger *slog.Logger) (users.Service, groups.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) cRepo := clientspg.NewRepository(database) gRepo := gpostgres.New(database) diff --git a/cmd/ws/main.go b/cmd/ws/main.go index b46dca357d..068a1399ec 100644 --- a/cmd/ws/main.go +++ b/cmd/ws/main.go @@ -8,9 +8,11 @@ import ( "context" "fmt" "log" + "log/slog" "net/url" "os" + chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal" jaegerclient "github.com/absmach/magistrala/internal/clients/jaeger" @@ -28,7 +30,6 @@ import ( "github.com/absmach/mproxy/pkg/session" "github.com/absmach/mproxy/pkg/websockets" "github.com/caarlos0/env/v10" - chclient "github.com/mainflux/callhome/pkg/client" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -62,7 +63,7 @@ func main() { logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { - log.Fatalf("failed to init logger: %s", err) + log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int @@ -153,7 +154,7 @@ func main() { } } -func newService(tc magistrala.AuthzServiceClient, nps messaging.PubSub, logger mglog.Logger, tracer trace.Tracer) ws.Service { +func newService(tc magistrala.AuthzServiceClient, nps messaging.PubSub, logger *slog.Logger, tracer trace.Tracer) ws.Service { svc := ws.New(tc, nps) svc = tracing.New(tracer, svc) svc = api.LoggingMiddleware(svc, logger) @@ -162,7 +163,7 @@ func newService(tc magistrala.AuthzServiceClient, nps messaging.PubSub, logger m return svc } -func proxyWS(ctx context.Context, hostConfig, targetConfig server.Config, logger mglog.Logger, handler session.Handler) error { +func proxyWS(ctx context.Context, hostConfig, targetConfig server.Config, logger *slog.Logger, handler session.Handler) error { target := fmt.Sprintf("ws://%s:%s", targetConfig.Host, targetConfig.Port) address := fmt.Sprintf("%s:%s", hostConfig.Host, hostConfig.Port) wp, err := websockets.NewProxy(address, target, logger, handler) diff --git a/coap/api/logging.go b/coap/api/logging.go index 12bbf8304d..6d0a612481 100644 --- a/coap/api/logging.go +++ b/coap/api/logging.go @@ -8,22 +8,22 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/coap" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" ) var _ coap.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc coap.Service } // LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(svc coap.Service, logger mglog.Logger) coap.Service { +func LoggingMiddleware(svc coap.Service, logger *slog.Logger) coap.Service { return &loggingMiddleware{logger, svc} } diff --git a/coap/api/transport.go b/coap/api/transport.go index 766d4823e1..784e341386 100644 --- a/coap/api/transport.go +++ b/coap/api/transport.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" "net/url" "regexp" @@ -15,7 +16,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/coap" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" "github.com/go-chi/chi/v5" @@ -44,7 +44,7 @@ var ( ) var ( - logger mglog.Logger + logger *slog.Logger service coap.Service ) @@ -58,7 +58,7 @@ func MakeHandler(instanceID string) http.Handler { } // MakeCoAPHandler creates handler for CoAP messages. -func MakeCoAPHandler(svc coap.Service, l mglog.Logger) mux.HandlerFunc { +func MakeCoAPHandler(svc coap.Service, l *slog.Logger) mux.HandlerFunc { logger = l service = svc diff --git a/coap/client.go b/coap/client.go index 589c825b9c..a71d896e56 100644 --- a/coap/client.go +++ b/coap/client.go @@ -7,9 +7,9 @@ import ( "bytes" "context" "fmt" + "log/slog" "sync/atomic" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" "github.com/plgd-dev/go-coap/v2/message" @@ -39,11 +39,11 @@ type client struct { client mux.Client token message.Token observe uint32 - logger mglog.Logger + logger *slog.Logger } // NewClient instantiates a new Observer. -func NewClient(c mux.Client, tkn message.Token, l mglog.Logger) Client { +func NewClient(c mux.Client, tkn message.Token, l *slog.Logger) Client { return &client{ client: c, token: tkn, diff --git a/consumers/messages.go b/consumers/messages.go index b8559412eb..a635547be2 100644 --- a/consumers/messages.go +++ b/consumers/messages.go @@ -6,11 +6,11 @@ package consumers import ( "context" "fmt" + "log/slog" "os" "strings" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/messaging/brokers" @@ -33,7 +33,7 @@ var ( // Start method starts consuming messages received from Message broker. // This method transforms messages to SenML format before // using MessageRepository to store them. -func Start(ctx context.Context, id string, sub messaging.Subscriber, consumer interface{}, configPath string, logger mglog.Logger) error { +func Start(ctx context.Context, id string, sub messaging.Subscriber, consumer interface{}, configPath string, logger *slog.Logger) error { cfg, err := loadConfig(configPath) if err != nil { logger.Warn(fmt.Sprintf("Failed to load consumer config: %s", err)) @@ -143,7 +143,7 @@ func loadConfig(configPath string) (config, error) { return cfg, nil } -func makeTransformer(cfg transformerConfig, logger mglog.Logger) transformers.Transformer { +func makeTransformer(cfg transformerConfig, logger *slog.Logger) transformers.Transformer { switch strings.ToUpper(cfg.Format) { case "SENML": logger.Info("Using SenML transformer") diff --git a/consumers/notifiers/api/logging.go b/consumers/notifiers/api/logging.go index 58ca621e42..e8e0ebba2d 100644 --- a/consumers/notifiers/api/logging.go +++ b/consumers/notifiers/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/consumers/notifiers" - mglog "github.com/absmach/magistrala/logger" ) var _ notifiers.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc notifiers.Service } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc notifiers.Service, logger mglog.Logger) notifiers.Service { +func LoggingMiddleware(svc notifiers.Service, logger *slog.Logger) notifiers.Service { return &loggingMiddleware{logger, svc} } diff --git a/consumers/notifiers/api/transport.go b/consumers/notifiers/api/transport.go index f99f19ae34..a603d2dac0 100644 --- a/consumers/notifiers/api/transport.go +++ b/consumers/notifiers/api/transport.go @@ -6,13 +6,13 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "strings" "github.com/absmach/magistrala" "github.com/absmach/magistrala/consumers/notifiers" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-chi/chi/v5" @@ -32,7 +32,7 @@ const ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc notifiers.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc notifiers.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/consumers/writers/api/logging.go b/consumers/writers/api/logging.go index 4970ecf2f1..f5c3f41a31 100644 --- a/consumers/writers/api/logging.go +++ b/consumers/writers/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/consumers" - mglog "github.com/absmach/magistrala/logger" ) var _ consumers.BlockingConsumer = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger consumer consumers.BlockingConsumer } // LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(consumer consumers.BlockingConsumer, logger mglog.Logger) consumers.BlockingConsumer { +func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger) consumers.BlockingConsumer { return &loggingMiddleware{ logger: logger, consumer: consumer, diff --git a/consumers/writers/cassandra/setup_test.go b/consumers/writers/cassandra/setup_test.go index 951dc55941..ad1577a94e 100644 --- a/consumers/writers/cassandra/setup_test.go +++ b/consumers/writers/cassandra/setup_test.go @@ -14,7 +14,7 @@ import ( "github.com/ory/dockertest/v3" ) -var logger, _ = mglog.New(os.Stdout, mglog.Info.String()) +var logger, _ = mglog.New(os.Stdout, "info") func TestMain(m *testing.M) { pool, err := dockertest.NewPool("") @@ -46,7 +46,7 @@ func TestMain(m *testing.M) { return nil }); err != nil { - logger.Fatal(fmt.Sprintf("Could not connect to docker: %s", err)) + logger.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } code := m.Run() diff --git a/consumers/writers/influxdb/consumer_test.go b/consumers/writers/influxdb/consumer_test.go index f17d8545a2..5442688955 100644 --- a/consumers/writers/influxdb/consumer_test.go +++ b/consumers/writers/influxdb/consumer_test.go @@ -23,7 +23,7 @@ import ( const valueFields = 5 var ( - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") streamsSize = 250 rowCountSenml = fmt.Sprintf(`from(bucket: "%s") |> range(start: -1h, stop: 1h) diff --git a/consumers/writers/mongodb/consumer_test.go b/consumers/writers/mongodb/consumer_test.go index d84e5c56ea..bb58a558d1 100644 --- a/consumers/writers/mongodb/consumer_test.go +++ b/consumers/writers/mongodb/consumer_test.go @@ -25,7 +25,7 @@ import ( var ( port string addr string - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") testDB = "test" collection = "messages" msgsNum = 100 diff --git a/go.mod b/go.mod index f7b4d9f32c..a789885131 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.21 require ( github.com/0x6flab/namegenerator v1.1.0 - github.com/absmach/mproxy v0.3.1-0.20231221215510-0ffbc4fc2337 + github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd + github.com/absmach/mproxy v0.4.2 github.com/absmach/senml v1.0.5 github.com/authzed/authzed-go v0.10.1 github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 @@ -16,13 +17,12 @@ require ( github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba github.com/go-chi/chi/v5 v5.0.10 github.com/go-kit/kit v0.13.0 - github.com/go-kit/log v0.2.1 github.com/go-redis/redis/v8 v8.11.5 github.com/gocql/gocql v1.6.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/gookit/color v1.5.4 github.com/gopcua/opcua v0.1.6 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.1 github.com/hashicorp/vault/api v1.10.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/influxdata/influxdb-client-go/v2 v2.12.3 @@ -32,7 +32,6 @@ require ( github.com/jackc/pgx/v5 v5.4.3 github.com/jmoiron/sqlx v1.3.5 github.com/lestrrat-go/jwx/v2 v2.0.16 - github.com/mainflux/callhome v0.0.0-20230920140432-33c5663382ce github.com/mitchellh/mapstructure v1.5.0 github.com/nats-io/nats.go v1.31.0 github.com/oklog/ulid/v2 v2.1.0 @@ -53,9 +52,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 go.opentelemetry.io/otel/sdk v1.20.0 go.opentelemetry.io/otel/trace v1.20.0 - golang.org/x/crypto v0.17.0 - golang.org/x/net v0.17.0 - golang.org/x/sync v0.4.0 + golang.org/x/crypto v0.18.0 + golang.org/x/net v0.20.0 + golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 google.golang.org/grpc v1.59.0 @@ -76,6 +75,7 @@ require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bitly/go-hostpool v0.1.0 // indirect github.com/bytedance/sonic v1.10.2 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect @@ -102,6 +102,7 @@ require ( github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -114,7 +115,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect @@ -154,10 +155,10 @@ require ( github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailgun/raymond/v2 v2.0.48 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mainflux/mainflux v0.12.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect @@ -220,7 +221,7 @@ require ( golang.org/x/arch v0.5.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect diff --git a/go.sum b/go.sum index 0bc2c090ea..743a11ac51 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -26,14 +24,12 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/compute v1.23.2 h1:nWEMDhgbBkBJjfpVySqU4jgWdc22PLR0o4vEexZHers= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -44,11 +40,9 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/0x6flab/namegenerator v1.1.0 h1:JSdBX3Y8S8O3QUUzKsS8p3zmNhx26GF2pD+9KgrUtYk= github.com/0x6flab/namegenerator v1.1.0/go.mod h1:h28wadnJ13Q7PxpUzAHckVj9ToyAEdx5T186Zj1kp+k= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -59,98 +53,61 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4s github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/absmach/mproxy v0.3.1-0.20231221215510-0ffbc4fc2337 h1:OW2WIn094hQCwrkXZ2KHgoOzsKAwqPaxZvRZ94VTc5U= -github.com/absmach/mproxy v0.3.1-0.20231221215510-0ffbc4fc2337/go.mod h1:HmXsnuSWIN0OKrcscIxBzDO/GRjvqYxUTnd6vpuo+MQ= +github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd h1:w6/9rG7Ov0aFz6BuArGW2HjWCHAlB1FyZI278rigR/c= +github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd/go.mod h1:GCP7YZVCYC/LmG1pnqDcQl819ej9kXwsGgi6YvjzIDk= +github.com/absmach/mproxy v0.4.2 h1:u0ORPxSrUknqbVrC+E1MdsCv/7Q5eWNG7clIwOMV5hk= +github.com/absmach/mproxy v0.4.2/go.mod h1:TeXhbHdjihXLVoohSzxvIEFzWu16WDOa91LNduks/N8= github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/authzed/authzed-go v0.10.1 h1:0aX2Ox9PPPknID92kLs/FnmhCmfl6Ni16v3ZTLsds5M= github.com/authzed/authzed-go v0.10.1/go.mod h1:ZsaFPCiMjwT0jLW0gCyYzh3elHqhKDDGGRySyykXwqc= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 h1:bQeIwWWRI9bl93poTqpix4sYHi+gnXUPK7N6bMtXzBE= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -162,7 +119,6 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -170,33 +126,16 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/continuity v0.0.0-20180416230128-c6cef3483023/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -206,39 +145,23 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+UPAUXI= github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dsnet/golib/memfile v0.0.0-20190531212259-571cdbcff553/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64= github.com/dsnet/golib/memfile v0.0.0-20200723050859-c110804dfa93/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64= github.com/dsnet/golib/memfile v1.0.0 h1:J9pUspY2bDCbF9o+YGwcf3uG6MdyITfh/Fk3/CaEiFs= github.com/dsnet/golib/memfile v1.0.0/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64= -github.com/dustin/go-coap v0.0.0-20170214053734-ddcc80675fa4/go.mod h1:as2rZ2aojRzZF8bGx1bPAn1yi9ICG6LwkiPOj6PBtjc= -github.com/dustin/go-coap v0.0.0-20190908170653-752e0f79981e/go.mod h1:as2rZ2aojRzZF8bGx1bPAn1yi9ICG6LwkiPOj6PBtjc= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -247,8 +170,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -260,28 +181,20 @@ github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba h1:vBqABUa2HUSc6tj2 github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba/go.mod h1:VfKFK7fGeCP81xEhbrOqUEh45n73Yy6jaPWwTVbxprI= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor v1.3.2/go.mod h1:Uy2lR31/2WfmW0yiA4i3t+we5kF3B/wzKsttcux+i/g= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -292,16 +205,12 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= @@ -321,87 +230,36 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-redis/redis v6.15.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-zoo/bone v1.3.0/go.mod h1:HI3Lhb7G3UQcAwEhOJ2WyNcsFtQX1WYHa0Hl4OBbhW8= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= -github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gocql/gocql v0.0.0-20181106112037-68ae1e384be4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= -github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= -github.com/gocql/gocql v0.0.0-20200624222514-34081eda590e/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= -github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -429,8 +287,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -439,7 +295,6 @@ github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -471,11 +326,9 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -483,98 +336,46 @@ github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gopcua/opcua v0.1.6 h1:B9SVRKQGzcWcwP2QPYN93Uku32+3wL+v5cgzBxE6V5I= github.com/gopcua/opcua v0.1.6/go.mod h1:INwnDoRxmNWAt7+tzqxuGqQkSF2c1C69VAL0c2q6AcY= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= -github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -584,21 +385,10 @@ github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/flux v0.65.0/go.mod h1:BwN2XG2lMszOoquQaFdPET8FRQfrXiZsWmcMO9rkaVY= -github.com/influxdata/influxdb v1.6.4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ= -github.com/influxdata/influxdb v1.8.1/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ= github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0IpXeMSkY/uJa/O/vC4= github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= @@ -656,42 +446,18 @@ github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSlj github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= -github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmoiron/sqlx v1.2.1-0.20190319043955-cdf62fdf55f6/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/jzelinskie/stringz v0.0.2 h1:OSjMEYvz8tjhovgZ/6cGcPID736ubeukr35mu6RYAmg= github.com/jzelinskie/stringz v0.0.2/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= @@ -711,18 +477,13 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -758,205 +519,94 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= -github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mainflux/callhome v0.0.0-20230920140432-33c5663382ce h1:BX3a91DXaewIBd+3zp9GpUZCpFfZX2FRLUJ2Mpas7jU= -github.com/mainflux/callhome v0.0.0-20230920140432-33c5663382ce/go.mod h1:a8I+g3fXeefHGoGBG69Ndj1Dg1nrahaM2OWdUc9K4EI= -github.com/mainflux/mainflux v0.0.0-20191223163044-f42f2095bab4/go.mod h1:K3ghSIpAqwv5F/t30LO57+11S7tE97ur2Z6wWEHa2CA= -github.com/mainflux/mainflux v0.0.0-20200314190902-c91fe0d45353/go.mod h1:yijZGLNkcDOPJfPhRMwuu5ZFcNHqDHzWurN4q1rOT/Q= -github.com/mainflux/mainflux v0.0.0-20200324100741-6ffa916ed229/go.mod h1:mde8cQhTPjLulu2pn/x8OgQ2S++lDufS+ODE93zuHjY= -github.com/mainflux/mainflux v0.0.0-20200512161904-df6f5adff8e4/go.mod h1:2caJ68GaQPVNe85z5pNJMJk0CflgcS3XWghYsJSBesU= -github.com/mainflux/mainflux v0.11.1-0.20200603183352-7f3e2c1b21ed/go.mod h1:8jwcwH3MKYgoQks9BBHq19Br25ElzW25vteZX7tWZ+w= -github.com/mainflux/mainflux v0.12.0 h1:UcZpOlGgkXi27gMFVmppAw3Mi6gTjfA0wyUELTTcA3E= -github.com/mainflux/mainflux v0.12.0/go.mod h1:d5L91byP5g4Y8dMzd+4LDjXuMy2oM80qg5nt5jMXN5I= -github.com/mainflux/mproxy v0.1.3/go.mod h1:/BdaBfgye1GNCD+eat4ipFamy9IEVRH5nhZS0yEShVg= -github.com/mainflux/mproxy v0.1.5/go.mod h1:MBLtv/RvhT8QsmXz4g3GxkRaP8PqlVqBWeqvw9QmO8k= -github.com/mainflux/mproxy v0.1.8/go.mod h1:NnhrUDytvV4pCI5LDuet86/WrymrUaX0/x1tlUHTKhU= -github.com/mainflux/mproxy v0.2.1-0.20200603122422-b08e1fa2cf5c/go.mod h1:lFD56bDgNTslCLoTlZfo2DyQbkQOnoxEXmbE4VumRm4= -github.com/mainflux/mproxy v0.2.2/go.mod h1:+T8h6ZupYPl6Lx9A0hqpcUQtcLyOBdzm/lfkjvPfGXo= -github.com/mainflux/senml v1.0.0/go.mod h1:g9i8pj4WMs29KkUpXivbe/PP0qJd1kt3b1CF77S8A3s= -github.com/mainflux/senml v1.0.1/go.mod h1:SMX76mM5yenjLVjZOM27+njCGkP+AA64O46nRQiBRlE= -github.com/mainflux/senml v1.5.0/go.mod h1:SMX76mM5yenjLVjZOM27+njCGkP+AA64O46nRQiBRlE= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/go-nats v1.6.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats-server/v2 v2.1.4/go.mod h1:Jw1Z28soD/QasIA2uWjXyM9El1jly3YwyFOuR8tH1rg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= -github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/ory/dockertest v3.3.0+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= -github.com/ory/dockertest/v3 v3.6.0/go.mod h1:4ZOpj8qBUmh8fcBSVzkH2bws2s91JdGvHUqan4GHEuQ= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/panjf2000/ants/v2 v2.4.3/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pion/dtls/v2 v2.0.1-0.20200503085337-8e86b3a7d585/go.mod h1:/GahSOC8ZY/+17zkaGJIG4OUkSGAcZu/N/g3roBOCkM= github.com/pion/dtls/v2 v2.0.10-0.20210502094952-3dc563b9aede/go.mod h1:86wv5dgx2J/z871nUR+5fTTY9tISLUlo+C5Gm86r1Hs= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= @@ -970,15 +620,11 @@ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1A github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/plgd-dev/go-coap/v2 v2.0.4-0.20200819112225-8eb712b901bc/go.mod h1:+tCi9Q78H/orWRtpVWyBgrr4vKFo2zYtbbxUllerBp4= -github.com/plgd-dev/go-coap/v2 v2.0.4/go.mod h1:DccQmYY6swDlNlOCQOAX+SXTI9laSfGytskmeeNWmms= github.com/plgd-dev/go-coap/v2 v2.4.1-0.20210517130748-95c37ac8e1fa/go.mod h1:rA7fc7ar+B/qa+Q0hRqv7yj/EMtIlmo1l7vkQGSrHPU= github.com/plgd-dev/go-coap/v2 v2.6.0 h1:T8tefZK4jag1ssr6gAGU+922QhVcrjk207aPhdg7i3o= github.com/plgd-dev/go-coap/v2 v2.6.0/go.mod h1:wm9fcL58Ky442Krix74S9Y54rCo36u59xFcYKRQaSBg= @@ -988,140 +634,67 @@ github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90/go.mod h1:Z7oKFLSG github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rubenv/sql-migrate v0.0.0-20181106121204-ba2c6a7295c5/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= -github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.0.0-20180905101324-b2a34562d02c/go.mod h1:XvpJiTD8NibaH7z0NzyfhR1+NQDtR9F/x92xheTwC9k= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1129,12 +702,10 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1144,7 +715,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= @@ -1153,25 +723,12 @@ github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew= github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-client-go v2.24.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -1184,7 +741,6 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -1193,9 +749,6 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xdg/stringprep v1.0.1-0.20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1203,11 +756,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= @@ -1226,17 +776,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.3.3/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c= go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1286,29 +827,17 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1319,12 +848,9 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -1336,7 +862,6 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1367,28 +892,19 @@ golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1400,7 +916,6 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1419,8 +934,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1436,7 +951,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1446,48 +960,31 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1495,13 +992,10 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1530,8 +1024,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1540,12 +1034,11 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1558,7 +1051,6 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1566,39 +1058,27 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1608,7 +1088,6 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1622,7 +1101,6 @@ golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1649,17 +1127,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20190808205415-ced62fe5104b/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1680,7 +1149,6 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -1690,12 +1158,9 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1703,13 +1168,11 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200225123651-fc8f55426688/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1719,8 +1182,6 @@ google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1738,18 +1199,10 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 h1: google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1778,64 +1231,35 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/ory-am/dockertest.v3 v3.3.2/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= -gopkg.in/ory/dockertest.v3 v3.3.5/go.mod h1:wI78nwA6jQZVXv3va0CcbJAuftRnAa063zO5Fek7+uI= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1850,5 +1274,3 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/http/handler.go b/http/handler.go index 697b208e79..ec5321bfb1 100644 --- a/http/handler.go +++ b/http/handler.go @@ -6,6 +6,7 @@ package http import ( "context" "fmt" + "log/slog" "net/url" "regexp" "strings" @@ -14,7 +15,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/mproxy/pkg/session" @@ -52,11 +52,11 @@ var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?] type handler struct { publisher messaging.Publisher auth magistrala.AuthzServiceClient - logger mglog.Logger + logger *slog.Logger } // NewHandler creates new Handler entity. -func NewHandler(publisher messaging.Publisher, logger mglog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { +func NewHandler(publisher messaging.Publisher, logger *slog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { return &handler{ logger: logger, publisher: publisher, diff --git a/internal/apiutil/transport.go b/internal/apiutil/transport.go index 0866f56d6c..35e22a3bc8 100644 --- a/internal/apiutil/transport.go +++ b/internal/apiutil/transport.go @@ -6,16 +6,16 @@ package apiutil import ( "context" "encoding/json" + "log/slog" "net/http" "strconv" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" kithttp "github.com/go-kit/kit/transport/http" ) // LoggingErrorEncoder is a go-kit error encoder logging decorator. -func LoggingErrorEncoder(logger mglog.Logger, enc kithttp.ErrorEncoder) kithttp.ErrorEncoder { +func LoggingErrorEncoder(logger *slog.Logger, enc kithttp.ErrorEncoder) kithttp.ErrorEncoder { return func(ctx context.Context, err error, w http.ResponseWriter) { if errors.Contains(err, ErrValidation) { logger.Error(err.Error()) diff --git a/internal/apiutil/transport_test.go b/internal/apiutil/transport_test.go index f277f3b309..8c31009a06 100644 --- a/internal/apiutil/transport_test.go +++ b/internal/apiutil/transport_test.go @@ -333,8 +333,6 @@ func TestReadNumQuery(t *testing.T) { } func TestLoggingErrorEncoder(t *testing.T) { - logger := mglog.NewMock() - cases := []struct { desc string err error @@ -356,7 +354,7 @@ func TestLoggingErrorEncoder(t *testing.T) { encCalled = true } - errorEncoder := apiutil.LoggingErrorEncoder(logger, encFunc) + errorEncoder := apiutil.LoggingErrorEncoder(mglog.NewMock(), encFunc) errorEncoder(context.Background(), c.err, httptest.NewRecorder()) assert.True(t, encCalled) diff --git a/internal/groups/api/logging.go b/internal/groups/api/logging.go index 83c285c120..69fd42a1f8 100644 --- a/internal/groups/api/logging.go +++ b/internal/groups/api/logging.go @@ -6,21 +6,21 @@ package api import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/groups" ) var _ groups.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc groups.Service } // LoggingMiddleware adds logging facilities to the groups service. -func LoggingMiddleware(svc groups.Service, logger mglog.Logger) groups.Service { +func LoggingMiddleware(svc groups.Service, logger *slog.Logger) groups.Service { return &loggingMiddleware{logger, svc} } diff --git a/internal/server/coap/coap.go b/internal/server/coap/coap.go index 5b2715a6d8..ddf1e6224c 100644 --- a/internal/server/coap/coap.go +++ b/internal/server/coap/coap.go @@ -7,10 +7,10 @@ import ( "context" "crypto/tls" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/internal/server" - mglog "github.com/absmach/magistrala/logger" gocoap "github.com/plgd-dev/go-coap/v2" "github.com/plgd-dev/go-coap/v2/mux" ) @@ -26,7 +26,7 @@ type Server struct { var _ server.Server = (*Server)(nil) -func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler mux.HandlerFunc, logger mglog.Logger) server.Server { +func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler mux.HandlerFunc, logger *slog.Logger) server.Server { listenFullAddress := fmt.Sprintf("%s:%s", config.Host, config.Port) return &Server{ BaseServer: server.BaseServer{ diff --git a/internal/server/grpc/grpc.go b/internal/server/grpc/grpc.go index 84a130de78..86042cfe69 100644 --- a/internal/server/grpc/grpc.go +++ b/internal/server/grpc/grpc.go @@ -8,12 +8,12 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "log/slog" "net" "os" "time" "github.com/absmach/magistrala/internal/server" - mglog "github.com/absmach/magistrala/logger" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -34,7 +34,7 @@ type serviceRegister func(srv *grpc.Server) var _ server.Server = (*Server)(nil) -func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger mglog.Logger) server.Server { +func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger) server.Server { listenFullAddress := fmt.Sprintf("%s:%s", config.Host, config.Port) return &Server{ BaseServer: server.BaseServer{ diff --git a/internal/server/http/http.go b/internal/server/http/http.go index 8569d21d6f..bc2fc04abf 100644 --- a/internal/server/http/http.go +++ b/internal/server/http/http.go @@ -6,11 +6,11 @@ package http import ( "context" "fmt" + "log/slog" "net/http" "time" "github.com/absmach/magistrala/internal/server" - mglog "github.com/absmach/magistrala/logger" ) const ( @@ -26,7 +26,7 @@ type Server struct { var _ server.Server = (*Server)(nil) -func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler http.Handler, logger mglog.Logger) server.Server { +func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler http.Handler, logger *slog.Logger) server.Server { listenFullAddress := fmt.Sprintf("%s:%s", config.Host, config.Port) httpServer := &http.Server{Addr: listenFullAddress, Handler: handler} return &Server{ diff --git a/internal/server/server.go b/internal/server/server.go index 855a668640..1085e5dca9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,11 +5,10 @@ package server import ( "context" "fmt" + "log/slog" "os" "os/signal" "syscall" - - mglog "github.com/absmach/magistrala/logger" ) type Server interface { @@ -32,7 +31,7 @@ type BaseServer struct { Name string Address string Config Config - Logger mglog.Logger + Logger *slog.Logger Protocol string } @@ -51,7 +50,7 @@ func stopAllServer(servers ...Server) error { return err } -func StopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger mglog.Logger, svcName string, servers ...Server) error { +func StopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger, svcName string, servers ...Server) error { var err error c := make(chan os.Signal, 2) signal.Notify(c, syscall.SIGINT, syscall.SIGABRT) diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go index b03cb2fef6..9aab6f0499 100644 --- a/invitations/api/endpoint_test.go +++ b/invitations/api/endpoint_test.go @@ -56,8 +56,8 @@ func (tr testRequest) make() (*http.Response, error) { func newIvitationsServer() (*httptest.Server, *mocks.Service) { svc := new(mocks.Service) - logger := mglog.NewMock() + mux := api.MakeHandler(svc, logger, "test") return httptest.NewServer(mux), svc } diff --git a/invitations/api/transport.go b/invitations/api/transport.go index c3fa34efd3..68c01fa8fb 100644 --- a/invitations/api/transport.go +++ b/invitations/api/transport.go @@ -6,6 +6,7 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "strings" @@ -13,7 +14,6 @@ import ( "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/invitations" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -29,7 +29,7 @@ const ( stateKey = "state" ) -func MakeHandler(svc invitations.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc invitations.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/invitations/middleware/logging.go b/invitations/middleware/logging.go index 04a64041f0..49524a9c9e 100644 --- a/invitations/middleware/logging.go +++ b/invitations/middleware/logging.go @@ -6,20 +6,20 @@ package middleware import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala/invitations" - mglog "github.com/absmach/magistrala/logger" ) var _ invitations.Service = (*logging)(nil) type logging struct { - logger mglog.Logger + logger *slog.Logger svc invitations.Service } -func Logging(logger mglog.Logger, svc invitations.Service) invitations.Service { +func Logging(logger *slog.Logger, svc invitations.Service) invitations.Service { return &logging{logger, svc} } diff --git a/logger/level.go b/logger/level.go deleted file mode 100644 index 08cb63ef60..0000000000 --- a/logger/level.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import ( - "errors" - "strings" -) - -const ( - // Error level is used when logging errors. - Error Level = iota + 1 - // Warn level is used when logging warnings. - Warn - // Info level is used when logging info data. - Info - // Debug level is used when logging debugging info. - Debug -) - -// ErrInvalidLogLevel indicates an unrecognized log level. -var ErrInvalidLogLevel = errors.New("unrecognized log level") - -// Level represents severity level while logging. -type Level int - -var levels = map[Level]string{ - Error: "error", - Warn: "warn", - Info: "info", - Debug: "debug", -} - -func (lvl Level) String() string { - return levels[lvl] -} - -func (lvl Level) isAllowed(logLevel Level) bool { - return lvl <= logLevel -} - -// UnmarshalText returns log Level for the given string representation. -func (lvl *Level) UnmarshalText(text string) error { - switch strings.ToLower(text) { - case "debug": - *lvl = Debug - case "info": - *lvl = Info - case "warn": - *lvl = Warn - case "error": - *lvl = Error - default: - return ErrInvalidLogLevel - } - return nil -} diff --git a/logger/level_test.go b/logger/level_test.go deleted file mode 100644 index 9f6319e1bc..0000000000 --- a/logger/level_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUnmarshalText(t *testing.T) { - cases := []struct { - desc string - input string - output Level - err error - }{ - { - desc: "select log level Not_A_Level", - input: "Not_A_Level", - output: 0, - err: ErrInvalidLogLevel, - }, - { - desc: "select log level Bad_Input", - input: "Bad_Input", - output: 0, - err: ErrInvalidLogLevel, - }, - - { - desc: "select log level debug", - input: "debug", - output: Debug, - err: nil, - }, - { - desc: "select log level DEBUG", - input: "DEBUG", - output: Debug, - err: nil, - }, - { - desc: "select log level info", - input: "info", - output: Info, - err: nil, - }, - { - desc: "select log level INFO", - input: "INFO", - output: Info, - err: nil, - }, - { - desc: "select log level warn", - input: "warn", - output: Warn, - err: nil, - }, - { - desc: "select log level WARN", - input: "WARN", - output: Warn, - err: nil, - }, - { - desc: "select log level Error", - input: "Error", - output: Error, - err: nil, - }, - { - desc: "select log level ERROR", - input: "ERROR", - output: Error, - err: nil, - }, - } - - for _, tc := range cases { - var logLevel Level - err := logLevel.UnmarshalText(tc.input) - assert.Equal(t, tc.output, logLevel, fmt.Sprintf("%s: expected %s got %d", tc.desc, tc.output, logLevel)) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %d", tc.desc, tc.err, err)) - } -} - -func TestLevelIsAllowed(t *testing.T) { - cases := []struct { - desc string - requestedLevel Level - allowedLevel Level - output bool - }{ - { - desc: "log debug when level debug", - requestedLevel: Debug, - allowedLevel: Debug, - output: true, - }, - { - desc: "log info when level debug", - requestedLevel: Info, - allowedLevel: Debug, - output: true, - }, - { - desc: "log warn when level debug", - requestedLevel: Warn, - allowedLevel: Debug, - output: true, - }, - { - desc: "log error when level debug", - requestedLevel: Error, - allowedLevel: Debug, - output: true, - }, - { - desc: "log warn when level info", - requestedLevel: Warn, - allowedLevel: Info, - output: true, - }, - { - desc: "log error when level warn", - requestedLevel: Error, - allowedLevel: Warn, - output: true, - }, - { - desc: "log error when level error", - requestedLevel: Error, - allowedLevel: Error, - output: true, - }, - - { - desc: "log debug when level error", - requestedLevel: Debug, - allowedLevel: Error, - output: false, - }, - { - desc: "log info when level error", - requestedLevel: Info, - allowedLevel: Error, - output: false, - }, - { - desc: "log warn when level error", - requestedLevel: Warn, - allowedLevel: Error, - output: false, - }, - { - desc: "log debug when level warn", - requestedLevel: Debug, - allowedLevel: Warn, - output: false, - }, - { - desc: "log info when level warn", - requestedLevel: Info, - allowedLevel: Warn, - output: false, - }, - { - desc: "log debug when level info", - requestedLevel: Debug, - allowedLevel: Info, - output: false, - }, - } - for _, tc := range cases { - result := tc.requestedLevel.isAllowed(tc.allowedLevel) - assert.Equal(t, tc.output, result, fmt.Sprintf("%s: expected %t got %t", tc.desc, tc.output, result)) - } -} diff --git a/logger/logger.go b/logger/logger.go index 8e15c5f38b..edaf84e315 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -6,70 +6,20 @@ package logger import ( "fmt" "io" - "os" + "log/slog" "time" - - "github.com/go-kit/log" ) -// Logger specifies logging API. -type Logger interface { - // Debug logs any object in JSON format on debug level. - Debug(string) - // Info logs any object in JSON format on info level. - Info(string) - // Warn logs any object in JSON format on warning level. - Warn(string) - // Error logs any object in JSON format on error level. - Error(string) - // Fatal logs any object in JSON format on any level and calls os.Exit(1). - Fatal(string) -} - -var _ Logger = (*logger)(nil) - -type logger struct { - kitLogger log.Logger - level Level -} - -// New returns wrapped go kit logger. -func New(out io.Writer, levelText string) (Logger, error) { - var level Level - err := level.UnmarshalText(levelText) - if err != nil { - return nil, fmt.Errorf(`{"level":"error","message":"%s: %s","ts":"%s"}`, err, levelText, time.RFC3339Nano) - } - l := log.NewJSONLogger(log.NewSyncWriter(out)) - l = log.With(l, "ts", log.DefaultTimestampUTC) - return &logger{l, level}, err -} - -func (l logger) Debug(msg string) { - if Debug.isAllowed(l.level) { - _ = l.kitLogger.Log("level", Debug.String(), "message", msg) +// New returns wrapped slog logger. +func New(w io.Writer, levelText string) (*slog.Logger, error) { + var level slog.Level + if err := level.UnmarshalText([]byte(levelText)); err != nil { + return &slog.Logger{}, fmt.Errorf(`{"level":"error","message":"%s: %s","ts":"%s"}`, err, levelText, time.RFC3339Nano) } -} -func (l logger) Info(msg string) { - if Info.isAllowed(l.level) { - _ = l.kitLogger.Log("level", Info.String(), "message", msg) - } -} - -func (l logger) Warn(msg string) { - if Warn.isAllowed(l.level) { - _ = l.kitLogger.Log("level", Warn.String(), "message", msg) - } -} - -func (l logger) Error(msg string) { - if Error.isAllowed(l.level) { - _ = l.kitLogger.Log("level", Error.String(), "message", msg) - } -} + logHandler := slog.NewJSONHandler(w, &slog.HandlerOptions{ + Level: level, + }) -func (l logger) Fatal(msg string) { - _ = l.kitLogger.Log("fatal", msg) - os.Exit(1) + return slog.New(logHandler), nil } diff --git a/logger/logger_test.go b/logger/logger_test.go index 79f5f1586c..9612f889a9 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -4,30 +4,11 @@ package logger_test import ( - "encoding/json" - "fmt" - "io" - "os" - "os/exec" + "log/slog" "testing" mglog "github.com/absmach/magistrala/logger" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Env vars needed for testing Fatal in subprocess. -const ( - testMsg = "TEST_MSG" - testFlag = "TEST_FLAG" - testFlagVal = "assert_test" -) - -var ( - _ io.Writer = (*mockWriter)(nil) - logger mglog.Logger - err error - output logMsg ) type mockWriter struct { @@ -39,221 +20,44 @@ func (writer *mockWriter) Write(p []byte) (int, error) { return len(p), nil } -func (writer *mockWriter) Read() (logMsg, error) { - var output logMsg - err := json.Unmarshal(writer.value, &output) - return output, err -} - -type logMsg struct { - Level string `json:"level"` - Message string `json:"message"` - Fatal string `json:"fatal,omitempty"` // needed for Fatal messages -} - -func TestDebug(t *testing.T) { +func TestLoggerInitialization(t *testing.T) { cases := []struct { - desc string - input string - level string - output logMsg + desc string + level string }{ { - desc: "debug log ordinary string", - input: "input_string", - level: mglog.Debug.String(), - output: logMsg{mglog.Debug.String(), "input_string", ""}, + desc: "debug level", + level: slog.LevelDebug.String(), }, { - desc: "debug log empty string", - input: "", - level: mglog.Debug.String(), - output: logMsg{mglog.Debug.String(), "", ""}, - }, - { - desc: "debug ordinary string lvl not allowed", - input: "input_string", - level: mglog.Info.String(), - output: logMsg{"", "", ""}, - }, - { - desc: "debug empty string lvl not allowed", - input: "", - level: mglog.Info.String(), - output: logMsg{"", "", ""}, - }, - } - - for _, tc := range cases { - writer := mockWriter{} - logger, err = mglog.New(&writer, tc.level) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - logger.Debug(tc.input) - output, err = writer.Read() - assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.output, output)) - } -} - -func TestInfo(t *testing.T) { - cases := []struct { - desc string - input string - level string - output logMsg - }{ - { - desc: "info log ordinary string", - input: "input_string", - level: mglog.Info.String(), - output: logMsg{mglog.Info.String(), "input_string", ""}, + desc: "info level", + level: slog.LevelInfo.String(), }, { - desc: "info log empty string", - input: "", - level: mglog.Info.String(), - output: logMsg{mglog.Info.String(), "", ""}, + desc: "warn level", + level: slog.LevelWarn.String(), }, { - desc: "info ordinary string lvl not allowed", - input: "input_string", - level: mglog.Warn.String(), - output: logMsg{"", "", ""}, + desc: "error level", + level: slog.LevelError.String(), }, { - desc: "info empty string lvl not allowed", - input: "", - level: mglog.Warn.String(), - output: logMsg{"", "", ""}, + desc: "invalid level", + level: "invalid", }, } for _, tc := range cases { - writer := mockWriter{} - logger, err = mglog.New(&writer, tc.level) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - logger.Info(tc.input) - output, err = writer.Read() - assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.output, output)) - } -} - -func TestWarn(t *testing.T) { - cases := []struct { - desc string - input string - level string - output logMsg - }{ - { - desc: "warn log ordinary string", - input: "input_string", - level: mglog.Warn.String(), - output: logMsg{mglog.Warn.String(), "input_string", ""}, - }, - { - desc: "warn log empty string", - input: "", - level: mglog.Warn.String(), - output: logMsg{mglog.Warn.String(), "", ""}, - }, - { - desc: "warn ordinary string lvl not allowed", - input: "input_string", - level: mglog.Error.String(), - output: logMsg{"", "", ""}, - }, - { - desc: "warn empty string lvl not allowed", - input: "", - level: mglog.Error.String(), - output: logMsg{"", "", ""}, - }, - } - - for _, tc := range cases { - writer := mockWriter{} - logger, err = mglog.New(&writer, tc.level) - require.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - logger.Warn(tc.input) - output, err = writer.Read() - assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.output, output)) - } -} - -func TestError(t *testing.T) { - cases := []struct { - desc string - input string - output logMsg - }{ - { - desc: "error log ordinary string", - input: "input_string", - output: logMsg{mglog.Error.String(), "input_string", ""}, - }, - { - desc: "error log empty string", - input: "", - output: logMsg{mglog.Error.String(), "", ""}, - }, - } - - writer := mockWriter{} - logger, err := mglog.New(&writer, mglog.Error.String()) - require.Nil(t, err) - for _, tc := range cases { - logger.Error(tc.input) - output, err := writer.Read() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.output, output)) - } -} - -func TestFatal(t *testing.T) { - // This is the actually Fatal call we test that will - // be executed in the subprocess spawned by the test. - if os.Getenv(testFlag) == testFlagVal { - logger, err := mglog.New(os.Stderr, mglog.Error.String()) - require.Nil(t, err) - msg := os.Getenv(testMsg) - logger.Fatal(msg) - return - } - - cases := []struct { - desc string - input string - output logMsg - }{ - { - desc: "error log ordinary string", - input: "input_string", - output: logMsg{"", "", "input_string"}, - }, - { - desc: "error log empty string", - input: "", - output: logMsg{"", "", ""}, - }, - } - writer := mockWriter{} - for _, tc := range cases { - // This command will run this same test as a separate subprocess. - // It needs to be executed as a subprocess because we need to test os.Exit(1) call. - cmd := exec.Command(os.Args[0], "-test.run=TestFatal") - // This flag is used to prevent an infinite loop of spawning this test and never - // actually running the necessary Fatal call. - cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", testFlag, testFlagVal)) - cmd.Stderr = &writer - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", testMsg, tc.input)) - err := cmd.Run() - if e, ok := err.(*exec.ExitError); ok && !e.Success() { - res, err := writer.Read() - require.Nil(t, err, "required successful buffer read") - assert.Equal(t, 1, e.ExitCode(), fmt.Sprintf("%s: expected exit code %d, got %d", tc.desc, 1, e.ExitCode())) - assert.Equal(t, tc.output, res, fmt.Sprintf("%s: expected output %s got %s", tc.desc, tc.output, res)) - continue - } - t.Fatal("subprocess ran successfully, want non-zero exit status") + t.Run(tc.desc, func(t *testing.T) { + writer := &mockWriter{} + logger, err := mglog.New(writer, tc.level) + if tc.level == "invalid" { + assert.NotNil(t, err, "expected error during logger initialization") + assert.NotNil(t, logger, "logger should not be nil when an error occurs") + } else { + assert.Nil(t, err, "unexpected error during logger initialization") + assert.NotNil(t, logger, "logger should not be nil") + } + }) } } diff --git a/logger/mock.go b/logger/mock.go index 3314d0c475..190fc229d9 100644 --- a/logger/mock.go +++ b/logger/mock.go @@ -3,26 +3,14 @@ package logger -var _ Logger = (*loggerMock)(nil) +import ( + "bytes" + "log/slog" +) -type loggerMock struct{} +// NewMock returns wrapped slog logger mock. +func NewMock() *slog.Logger { + buf := &bytes.Buffer{} -// NewMock returns wrapped go kit logger mock. -func NewMock() Logger { - return &loggerMock{} -} - -func (l loggerMock) Debug(msg string) { -} - -func (l loggerMock) Info(msg string) { -} - -func (l loggerMock) Warn(msg string) { -} - -func (l loggerMock) Error(msg string) { -} - -func (l loggerMock) Fatal(msg string) { + return slog.New(slog.NewJSONHandler(buf, nil)) } diff --git a/lora/api/logging.go b/lora/api/logging.go index ed2fee1ab9..fb9a37cfea 100644 --- a/lora/api/logging.go +++ b/lora/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/lora" ) var _ lora.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc lora.Service } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc lora.Service, logger mglog.Logger) lora.Service { +func LoggingMiddleware(svc lora.Service, logger *slog.Logger) lora.Service { return &loggingMiddleware{ logger: logger, svc: svc, diff --git a/lora/mqtt/sub.go b/lora/mqtt/sub.go index feab1d3a3d..e883ed7461 100644 --- a/lora/mqtt/sub.go +++ b/lora/mqtt/sub.go @@ -8,9 +8,9 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/lora" mqtt "github.com/eclipse/paho.mqtt.golang" ) @@ -24,12 +24,12 @@ type Subscriber interface { type broker struct { svc lora.Service client mqtt.Client - logger mglog.Logger + logger *slog.Logger timeout time.Duration } // NewBroker returns new MQTT broker instance. -func NewBroker(svc lora.Service, client mqtt.Client, t time.Duration, log mglog.Logger) Subscriber { +func NewBroker(svc lora.Service, client mqtt.Client, t time.Duration, log *slog.Logger) Subscriber { return broker{ svc: svc, client: client, diff --git a/mqtt/forwarder.go b/mqtt/forwarder.go index c91e84c748..735b29c287 100644 --- a/mqtt/forwarder.go +++ b/mqtt/forwarder.go @@ -6,9 +6,9 @@ package mqtt import ( "context" "fmt" + "log/slog" "strings" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" ) @@ -21,11 +21,11 @@ type Forwarder interface { type forwarder struct { topic string - logger mglog.Logger + logger *slog.Logger } // NewForwarder returns new Forwarder implementation. -func NewForwarder(topic string, logger mglog.Logger) Forwarder { +func NewForwarder(topic string, logger *slog.Logger) Forwarder { return forwarder{ topic: topic, logger: logger, @@ -42,7 +42,7 @@ func (f forwarder) Forward(ctx context.Context, id string, sub messaging.Subscri return sub.Subscribe(ctx, subCfg) } -func handle(ctx context.Context, pub messaging.Publisher, logger mglog.Logger) handleFunc { +func handle(ctx context.Context, pub messaging.Publisher, logger *slog.Logger) handleFunc { return func(msg *messaging.Message) error { if msg.GetProtocol() == protocol { return nil diff --git a/mqtt/handler.go b/mqtt/handler.go index 9921cef703..ad09d7e7fc 100644 --- a/mqtt/handler.go +++ b/mqtt/handler.go @@ -6,6 +6,7 @@ package mqtt import ( "context" "fmt" + "log/slog" "net/url" "regexp" "strings" @@ -13,7 +14,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/mqtt/events" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" @@ -58,12 +58,12 @@ var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?] type handler struct { publisher messaging.Publisher auth magistrala.AuthzServiceClient - logger mglog.Logger + logger *slog.Logger es events.EventStore } // NewHandler creates new Handler entity. -func NewHandler(publisher messaging.Publisher, es events.EventStore, logger mglog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { +func NewHandler(publisher messaging.Publisher, es events.EventStore, logger *slog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { return &handler{ es: es, logger: logger, diff --git a/opcua/adapter.go b/opcua/adapter.go index 740c05fe4a..b07801dfbd 100644 --- a/opcua/adapter.go +++ b/opcua/adapter.go @@ -6,8 +6,8 @@ package opcua import ( "context" "fmt" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/opcua/db" ) @@ -62,11 +62,11 @@ type adapterService struct { channelsRM RouteMapRepository connectRM RouteMapRepository cfg Config - logger mglog.Logger + logger *slog.Logger } // New instantiates the OPC-UA adapter implementation. -func New(sub Subscriber, brow Browser, thingsRM, channelsRM, connectRM RouteMapRepository, cfg Config, log mglog.Logger) Service { +func New(sub Subscriber, brow Browser, thingsRM, channelsRM, connectRM RouteMapRepository, cfg Config, log *slog.Logger) Service { return &adapterService{ subscriber: sub, browser: brow, diff --git a/opcua/api/logging.go b/opcua/api/logging.go index 6a5fc2681f..27eeb6ce03 100644 --- a/opcua/api/logging.go +++ b/opcua/api/logging.go @@ -8,21 +8,21 @@ package api import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/opcua" ) var _ opcua.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc opcua.Service } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc opcua.Service, logger mglog.Logger) opcua.Service { +func LoggingMiddleware(svc opcua.Service, logger *slog.Logger) opcua.Service { return &loggingMiddleware{ logger: logger, svc: svc, diff --git a/opcua/api/transport.go b/opcua/api/transport.go index 3fcadf9170..ec9ece461b 100644 --- a/opcua/api/transport.go +++ b/opcua/api/transport.go @@ -6,11 +6,11 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/opcua" "github.com/absmach/magistrala/pkg/errors" "github.com/go-chi/chi/v5" @@ -28,7 +28,7 @@ const ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc opcua.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc opcua.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/opcua/gopcua/browser.go b/opcua/gopcua/browser.go index 1ad4a77aa0..9e942821eb 100644 --- a/opcua/gopcua/browser.go +++ b/opcua/gopcua/browser.go @@ -5,8 +5,8 @@ package gopcua import ( "context" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/opcua" "github.com/absmach/magistrala/pkg/errors" opcuagocpua "github.com/gopcua/opcua" @@ -36,11 +36,11 @@ var _ opcua.Browser = (*browser)(nil) type browser struct { ctx context.Context - logger mglog.Logger + logger *slog.Logger } // NewBrowser returns new OPC-UA browser instance. -func NewBrowser(ctx context.Context, log mglog.Logger) opcua.Browser { +func NewBrowser(ctx context.Context, log *slog.Logger) opcua.Browser { return browser{ ctx: ctx, logger: log, diff --git a/opcua/gopcua/subscribe.go b/opcua/gopcua/subscribe.go index fff26ed54d..8375fbc16a 100644 --- a/opcua/gopcua/subscribe.go +++ b/opcua/gopcua/subscribe.go @@ -6,10 +6,10 @@ package gopcua import ( "context" "fmt" + "log/slog" "strconv" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/opcua" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/messaging" @@ -45,7 +45,7 @@ type client struct { thingsRM opcua.RouteMapRepository channelsRM opcua.RouteMapRepository connectRM opcua.RouteMapRepository - logger mglog.Logger + logger *slog.Logger } type message struct { @@ -58,7 +58,7 @@ type message struct { } // NewSubscriber returns new OPC-UA client instance. -func NewSubscriber(ctx context.Context, publisher messaging.Publisher, thingsRM, channelsRM, connectRM opcua.RouteMapRepository, log mglog.Logger) opcua.Subscriber { +func NewSubscriber(ctx context.Context, publisher messaging.Publisher, thingsRM, channelsRM, connectRM opcua.RouteMapRepository, log *slog.Logger) opcua.Subscriber { return client{ ctx: ctx, publisher: publisher, diff --git a/pkg/events/nats/subscriber.go b/pkg/events/nats/subscriber.go index 090570fbd7..9154678fb5 100644 --- a/pkg/events/nats/subscriber.go +++ b/pkg/events/nats/subscriber.go @@ -8,9 +8,9 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/messaging" broker "github.com/absmach/magistrala/pkg/messaging/nats" @@ -51,10 +51,10 @@ type subEventStore struct { pubsub messaging.PubSub stream string consumer string - logger mglog.Logger + logger *slog.Logger } -func NewSubscriber(ctx context.Context, url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(ctx context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { if stream == "" { return nil, ErrEmptyStream } @@ -121,7 +121,7 @@ func (re event) Encode() (map[string]interface{}, error) { type eventHandler struct { handler events.EventHandler ctx context.Context - logger mglog.Logger + logger *slog.Logger } func (eh *eventHandler) Handle(msg *messaging.Message) error { diff --git a/pkg/events/rabbitmq/subscriber.go b/pkg/events/rabbitmq/subscriber.go index 6786aff9fd..88d2713f44 100644 --- a/pkg/events/rabbitmq/subscriber.go +++ b/pkg/events/rabbitmq/subscriber.go @@ -8,8 +8,8 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/messaging" broker "github.com/absmach/magistrala/pkg/messaging/rabbitmq" @@ -34,10 +34,10 @@ type subEventStore struct { pubsub messaging.PubSub stream string consumer string - logger mglog.Logger + logger *slog.Logger } -func NewSubscriber(url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { if stream == "" { return nil, ErrEmptyStream } @@ -103,7 +103,7 @@ func (re event) Encode() (map[string]interface{}, error) { type eventHandler struct { handler events.EventHandler ctx context.Context - logger mglog.Logger + logger *slog.Logger } func (eh *eventHandler) Handle(msg *messaging.Message) error { diff --git a/pkg/events/redis/subscriber.go b/pkg/events/redis/subscriber.go index 29fb5f5f2c..64d1724d2e 100644 --- a/pkg/events/redis/subscriber.go +++ b/pkg/events/redis/subscriber.go @@ -10,8 +10,8 @@ import ( "context" "errors" "fmt" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/go-redis/redis/v8" ) @@ -36,10 +36,10 @@ type subEventStore struct { client *redis.Client stream string consumer string - logger mglog.Logger + logger *slog.Logger } -func NewSubscriber(url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { if stream == "" { return nil, ErrEmptyStream } diff --git a/pkg/events/store/brokers_nats.go b/pkg/events/store/brokers_nats.go index 5c5ba56386..282fc6ad5e 100644 --- a/pkg/events/store/brokers_nats.go +++ b/pkg/events/store/brokers_nats.go @@ -9,8 +9,8 @@ package store import ( "context" "log" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/nats" ) @@ -28,7 +28,7 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(ctx context.Context, url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(ctx context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { pb, err := nats.NewSubscriber(ctx, url, stream, consumer, logger) if err != nil { return nil, err diff --git a/pkg/events/store/brokers_rabbitmq.go b/pkg/events/store/brokers_rabbitmq.go index 5de1965bbd..bf895502f2 100644 --- a/pkg/events/store/brokers_rabbitmq.go +++ b/pkg/events/store/brokers_rabbitmq.go @@ -9,8 +9,8 @@ package store import ( "context" "log" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/rabbitmq" ) @@ -28,7 +28,7 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(_ context.Context, url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(_ context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { pb, err := rabbitmq.NewSubscriber(url, stream, consumer, logger) if err != nil { return nil, err diff --git a/pkg/events/store/brokers_redis.go b/pkg/events/store/brokers_redis.go index 0ec127d6fb..f88f093e46 100644 --- a/pkg/events/store/brokers_redis.go +++ b/pkg/events/store/brokers_redis.go @@ -9,8 +9,8 @@ package store import ( "context" "log" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/redis" ) @@ -28,7 +28,7 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(_ context.Context, url, stream, consumer string, logger mglog.Logger) (events.Subscriber, error) { +func NewSubscriber(_ context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { pb, err := redis.NewSubscriber(url, stream, consumer, logger) if err != nil { return nil, err diff --git a/pkg/messaging/brokers/brokers_nats.go b/pkg/messaging/brokers/brokers_nats.go index 06bc0b8a6e..1cc25ffe47 100644 --- a/pkg/messaging/brokers/brokers_nats.go +++ b/pkg/messaging/brokers/brokers_nats.go @@ -9,8 +9,8 @@ package brokers import ( "context" "log" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/messaging/nats" ) @@ -31,7 +31,7 @@ func NewPublisher(ctx context.Context, url string, opts ...messaging.Option) (me return pb, nil } -func NewPubSub(ctx context.Context, url string, logger mglog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { +func NewPubSub(ctx context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { pb, err := nats.NewPubSub(ctx, url, logger, opts...) if err != nil { return nil, err diff --git a/pkg/messaging/brokers/brokers_rabbitmq.go b/pkg/messaging/brokers/brokers_rabbitmq.go index cf3a445d51..4ccaec613f 100644 --- a/pkg/messaging/brokers/brokers_rabbitmq.go +++ b/pkg/messaging/brokers/brokers_rabbitmq.go @@ -9,8 +9,8 @@ package brokers import ( "context" "log" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/messaging/rabbitmq" ) @@ -31,7 +31,7 @@ func NewPublisher(_ context.Context, url string, opts ...messaging.Option) (mess return pb, nil } -func NewPubSub(_ context.Context, url string, logger mglog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { +func NewPubSub(_ context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { pb, err := rabbitmq.NewPubSub(url, logger, opts...) if err != nil { return nil, err diff --git a/pkg/messaging/handler/logging.go b/pkg/messaging/handler/logging.go index a932a2ec14..5019c20b4b 100644 --- a/pkg/messaging/handler/logging.go +++ b/pkg/messaging/handler/logging.go @@ -8,16 +8,16 @@ package handler import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/mproxy/pkg/session" ) var _ session.Handler = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc session.Handler } @@ -135,6 +135,6 @@ func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, topics *[]string) } // LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(svc session.Handler, logger mglog.Logger) session.Handler { +func LoggingMiddleware(svc session.Handler, logger *slog.Logger) session.Handler { return &loggingMiddleware{logger, svc} } diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go index e408b8bbf0..36d9989b2f 100644 --- a/pkg/messaging/mqtt/pubsub.go +++ b/pkg/messaging/mqtt/pubsub.go @@ -7,10 +7,10 @@ import ( "context" "errors" "fmt" + "log/slog" "sync" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" mqtt "github.com/eclipse/paho.mqtt.golang" "google.golang.org/protobuf/proto" @@ -51,7 +51,7 @@ type subscription struct { type pubsub struct { publisher - logger mglog.Logger + logger *slog.Logger mu sync.RWMutex address string timeout time.Duration @@ -59,7 +59,7 @@ type pubsub struct { } // NewPubSub returns MQTT message publisher/subscriber. -func NewPubSub(url string, qos uint8, timeout time.Duration, logger mglog.Logger) (messaging.PubSub, error) { +func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { client, err := newClient(url, "mqtt-publisher", timeout) if err != nil { return nil, err diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index cb568a7627..869211b885 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -6,6 +6,7 @@ package mqtt_test import ( "fmt" "log" + "log/slog" "os" "os/signal" "syscall" @@ -21,7 +22,7 @@ import ( var ( pubsub messaging.PubSub - logger mglog.Logger + logger *slog.Logger address string ) @@ -51,7 +52,7 @@ func TestMain(m *testing.M) { address = fmt.Sprintf("%s:%s", "localhost", container.GetPort(port)) pool.MaxWait = poolMaxWait - logger, err = mglog.New(os.Stdout, mglog.Debug.String()) + logger, err = mglog.New(os.Stdout, "debug") if err != nil { log.Fatalf(err.Error()) } diff --git a/pkg/messaging/nats/pubsub.go b/pkg/messaging/nats/pubsub.go index 976d37dd38..7161a0d995 100644 --- a/pkg/messaging/nats/pubsub.go +++ b/pkg/messaging/nats/pubsub.go @@ -7,10 +7,10 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" broker "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" @@ -42,7 +42,7 @@ var _ messaging.PubSub = (*pubsub)(nil) type pubsub struct { publisher - logger mglog.Logger + logger *slog.Logger stream jetstream.Stream } @@ -53,7 +53,7 @@ type pubsub struct { // from ordinary subscribe. For more information, please take a look // here: https://docs.nats.io/developing-with-nats/receiving/queues. // If the queue is empty, Subscribe will be used. -func NewPubSub(ctx context.Context, url string, logger mglog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { +func NewPubSub(ctx context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { conn, err := broker.Connect(url, broker.MaxReconnects(maxReconnects)) if err != nil { return nil, err diff --git a/pkg/messaging/rabbitmq/pubsub.go b/pkg/messaging/rabbitmq/pubsub.go index fff04b0ad8..59b06a496b 100644 --- a/pkg/messaging/rabbitmq/pubsub.go +++ b/pkg/messaging/rabbitmq/pubsub.go @@ -7,9 +7,9 @@ import ( "context" "errors" "fmt" + "log/slog" "sync" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" amqp "github.com/rabbitmq/amqp091-go" "google.golang.org/protobuf/proto" @@ -40,13 +40,13 @@ type subscription struct { } type pubsub struct { publisher - logger mglog.Logger + logger *slog.Logger subscriptions map[string]map[string]subscription mu sync.Mutex } // NewPubSub returns RabbitMQ message publisher/subscriber. -func NewPubSub(url string, logger mglog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { +func NewPubSub(url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { conn, err := amqp.Dial(url) if err != nil { return nil, err diff --git a/pkg/messaging/rabbitmq/setup_test.go b/pkg/messaging/rabbitmq/setup_test.go index 1405829a69..2deeaf4dfb 100644 --- a/pkg/messaging/rabbitmq/setup_test.go +++ b/pkg/messaging/rabbitmq/setup_test.go @@ -6,6 +6,7 @@ package rabbitmq_test import ( "fmt" "log" + "log/slog" "os" "os/signal" "syscall" @@ -29,7 +30,7 @@ const ( var ( publisher messaging.Publisher pubsub messaging.PubSub - logger mglog.Logger + logger *slog.Logger address string ) @@ -53,7 +54,7 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - logger, err = mglog.New(os.Stdout, mglog.Debug.String()) + logger, err = mglog.New(os.Stdout, "debug") if err != nil { log.Fatalf(err.Error()) } diff --git a/provision/api/logging.go b/provision/api/logging.go index e3ecf4a5a5..f1dfd948e0 100644 --- a/provision/api/logging.go +++ b/provision/api/logging.go @@ -7,21 +7,21 @@ package api import ( "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/provision" ) var _ provision.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc provision.Service } // NewLoggingMiddleware adds logging facilities to the core service. -func NewLoggingMiddleware(svc provision.Service, logger mglog.Logger) provision.Service { +func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service { return &loggingMiddleware{logger, svc} } diff --git a/provision/api/transport.go b/provision/api/transport.go index 6ed3a07284..c2ced9b51f 100644 --- a/provision/api/transport.go +++ b/provision/api/transport.go @@ -6,12 +6,12 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/provision" "github.com/go-chi/chi/v5" @@ -24,7 +24,7 @@ const ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc provision.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/provision/service.go b/provision/service.go index 83b7a1c4e1..f9e844c517 100644 --- a/provision/service.go +++ b/provision/service.go @@ -6,8 +6,8 @@ package provision import ( "encoding/json" "fmt" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" sdk "github.com/absmach/magistrala/pkg/sdk/go" ) @@ -70,7 +70,7 @@ type Service interface { } type provisionService struct { - logger mglog.Logger + logger *slog.Logger sdk sdk.SDK conf Config } @@ -87,7 +87,7 @@ type Result struct { } // New returns new provision service. -func New(cfg Config, mgsdk sdk.SDK, logger mglog.Logger) Service { +func New(cfg Config, mgsdk sdk.SDK, logger *slog.Logger) Service { return &provisionService{ logger: logger, conf: cfg, diff --git a/readers/api/logging.go b/readers/api/logging.go index a1dbc52bba..73bb9dcffa 100644 --- a/readers/api/logging.go +++ b/readers/api/logging.go @@ -7,21 +7,21 @@ package api import ( "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/readers" ) var _ readers.MessageRepository = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc readers.MessageRepository } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc readers.MessageRepository, logger mglog.Logger) readers.MessageRepository { +func LoggingMiddleware(svc readers.MessageRepository, logger *slog.Logger) readers.MessageRepository { return &loggingMiddleware{ logger: logger, svc: svc, diff --git a/readers/cassandra/setup_test.go b/readers/cassandra/setup_test.go index 77386fb436..7bfd6414dd 100644 --- a/readers/cassandra/setup_test.go +++ b/readers/cassandra/setup_test.go @@ -14,7 +14,7 @@ import ( "github.com/ory/dockertest/v3" ) -var logger, _ = mglog.New(os.Stdout, mglog.Info.String()) +var logger, _ = mglog.New(os.Stdout, "info") func TestMain(m *testing.M) { pool, err := dockertest.NewPool("") @@ -46,7 +46,7 @@ func TestMain(m *testing.M) { return nil }); err != nil { - logger.Fatal(fmt.Sprintf("Could not connect to docker: %s", err)) + logger.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } code := m.Run() diff --git a/readers/influxdb/setup_test.go b/readers/influxdb/setup_test.go index 7034e5aa41..73f84dbe02 100644 --- a/readers/influxdb/setup_test.go +++ b/readers/influxdb/setup_test.go @@ -19,7 +19,7 @@ import ( ) var ( - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") address string ) diff --git a/readers/mongodb/setup_test.go b/readers/mongodb/setup_test.go index d7e1541cca..770897e0d2 100644 --- a/readers/mongodb/setup_test.go +++ b/readers/mongodb/setup_test.go @@ -15,7 +15,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -var testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) +var testLog, _ = mglog.New(os.Stdout, "info") func TestMain(m *testing.M) { pool, err := dockertest.NewPool("") diff --git a/things/api/http/channels.go b/things/api/http/channels.go index 10c5a57b9e..09ac000a0c 100644 --- a/things/api/http/channels.go +++ b/things/api/http/channels.go @@ -6,6 +6,7 @@ package http import ( "context" "encoding/json" + "log/slog" "net/http" "strings" @@ -13,7 +14,6 @@ import ( "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" gapi "github.com/absmach/magistrala/internal/groups/api" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/groups" "github.com/go-chi/chi/v5" @@ -21,7 +21,7 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func groupsHandler(svc groups.Service, r *chi.Mux, logger mglog.Logger) http.Handler { +func groupsHandler(svc groups.Service, r *chi.Mux, logger *slog.Logger) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/things/api/http/clients.go b/things/api/http/clients.go index 4abcd9587c..4f3d8a1a31 100644 --- a/things/api/http/clients.go +++ b/things/api/http/clients.go @@ -6,12 +6,12 @@ package http import ( "context" "encoding/json" + "log/slog" "net/http" "strings" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/things" @@ -20,7 +20,7 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func clientsHandler(svc things.Service, r *chi.Mux, logger mglog.Logger) http.Handler { +func clientsHandler(svc things.Service, r *chi.Mux, logger *slog.Logger) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/things/api/http/transport.go b/things/api/http/transport.go index 6c0fb41318..09094d77d9 100644 --- a/things/api/http/transport.go +++ b/things/api/http/transport.go @@ -4,10 +4,10 @@ package http import ( + "log/slog" "net/http" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/things" "github.com/go-chi/chi/v5" @@ -15,7 +15,7 @@ import ( ) // MakeHandler returns a HTTP handler for Things and Groups API endpoints. -func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { clientsHandler(tsvc, mux, logger) groupsHandler(grps, mux, logger) diff --git a/things/api/logging.go b/things/api/logging.go index cb7743d2e5..134c8eea82 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -6,10 +6,10 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/things" ) @@ -17,11 +17,11 @@ import ( var _ things.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc things.Service } -func LoggingMiddleware(svc things.Service, logger mglog.Logger) things.Service { +func LoggingMiddleware(svc things.Service, logger *slog.Logger) things.Service { return &loggingMiddleware{logger, svc} } diff --git a/tools/mqtt-bench/bench.go b/tools/mqtt-bench/bench.go index 98705b16b1..2c46406b91 100644 --- a/tools/mqtt-bench/bench.go +++ b/tools/mqtt-bench/bench.go @@ -22,7 +22,7 @@ func Benchmark(cfg Config) error { if err := checkConnection(cfg.MQTT.Broker.URL, 1); err != nil { return err } - logger, err := mglog.New(os.Stdout, mglog.Debug.String()) + logger, err := mglog.New(os.Stdout, "debug") if err != nil { return err } diff --git a/twins/api/http/transport.go b/twins/api/http/transport.go index 83287466c7..ae79b7b12d 100644 --- a/twins/api/http/transport.go +++ b/twins/api/http/transport.go @@ -6,12 +6,12 @@ package http import ( "context" "encoding/json" + "log/slog" "net/http" "strings" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/twins" @@ -32,7 +32,7 @@ const ( ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc twins.Service, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(svc twins.Service, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), } diff --git a/twins/api/logging.go b/twins/api/logging.go index c2fba63d61..c8b65305d1 100644 --- a/twins/api/logging.go +++ b/twins/api/logging.go @@ -8,9 +8,9 @@ package api import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/twins" ) @@ -18,12 +18,12 @@ import ( var _ twins.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc twins.Service } // LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc twins.Service, logger mglog.Logger) twins.Service { +func LoggingMiddleware(svc twins.Service, logger *slog.Logger) twins.Service { return &loggingMiddleware{logger, svc} } diff --git a/twins/mocks/service.go b/twins/mocks/service.go index e49aac522e..6e7aef6e4e 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -9,6 +9,7 @@ import ( "time" authmocks "github.com/absmach/magistrala/auth/mocks" + mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/twins" @@ -29,7 +30,7 @@ func NewService() (twins.Service, *authmocks.Service) { subs := map[string]string{"chanID": "chanID"} broker := NewBroker(subs) - return twins.New(broker, auth, twinsRepo, twinCache, statesRepo, idProvider, "chanID", nil), auth + return twins.New(broker, auth, twinsRepo, twinCache, statesRepo, idProvider, "chanID", mglog.NewMock()), auth } // CreateDefinition creates twin definition. diff --git a/twins/mongodb/init.go b/twins/mongodb/init.go index 2feaeb70a0..22f745e8f9 100644 --- a/twins/mongodb/init.go +++ b/twins/mongodb/init.go @@ -6,8 +6,8 @@ package mongodb import ( "context" "fmt" + "log/slog" - mglog "github.com/absmach/magistrala/logger" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -20,7 +20,7 @@ type Config struct { } // Connect creates a connection to the MongoDB instance. -func Connect(cfg Config, logger mglog.Logger) (*mongo.Database, error) { +func Connect(cfg Config, logger *slog.Logger) (*mongo.Database, error) { addr := fmt.Sprintf("mongodb://%s:%s", cfg.Host, cfg.Port) client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr)) if err != nil { diff --git a/twins/mongodb/twins_test.go b/twins/mongodb/twins_test.go index 04df067e9c..647e90023f 100644 --- a/twins/mongodb/twins_test.go +++ b/twins/mongodb/twins_test.go @@ -36,7 +36,7 @@ const ( var ( port string addr string - testLog, _ = mglog.New(os.Stdout, mglog.Info.String()) + testLog, _ = mglog.New(os.Stdout, "info") idProvider = uuid.New() invalidName = strings.Repeat("m", maxNameSize+1) ) diff --git a/twins/service.go b/twins/service.go index 188f50fb74..763c6cd529 100644 --- a/twins/service.go +++ b/twins/service.go @@ -7,11 +7,11 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "math" "time" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -80,13 +80,13 @@ type twinsService struct { idProvider magistrala.IDProvider channelID string twinCache TwinCache - logger mglog.Logger + logger *slog.Logger } var _ Service = (*twinsService)(nil) // New instantiates the twins service implementation. -func New(publisher messaging.Publisher, auth magistrala.AuthServiceClient, twins TwinRepository, tcache TwinCache, sr StateRepository, idp magistrala.IDProvider, chann string, logger mglog.Logger) Service { +func New(publisher messaging.Publisher, auth magistrala.AuthServiceClient, twins TwinRepository, tcache TwinCache, sr StateRepository, idp magistrala.IDProvider, chann string, logger *slog.Logger) Service { return &twinsService{ publisher: publisher, auth: auth, diff --git a/users/api/clients.go b/users/api/clients.go index a9f11d0ede..c3b45a490e 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -6,13 +6,13 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "strings" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" - mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/users" @@ -22,7 +22,7 @@ import ( ) // MakeHandler returns a HTTP handler for API endpoints. -func clientsHandler(svc users.Service, r *chi.Mux, logger mglog.Logger) http.Handler { +func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/users/api/groups.go b/users/api/groups.go index 5e68f9477c..d7d72524d6 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -6,13 +6,13 @@ package api import ( "context" "encoding/json" + "log/slog" "net/http" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" gapi "github.com/absmach/magistrala/internal/groups/api" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/groups" "github.com/go-chi/chi/v5" @@ -22,7 +22,7 @@ import ( ) // MakeHandler returns a HTTP handler for Groups API endpoints. -func groupsHandler(svc groups.Service, r *chi.Mux, logger mglog.Logger) http.Handler { +func groupsHandler(svc groups.Service, r *chi.Mux, logger *slog.Logger) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/users/api/logging.go b/users/api/logging.go index 5ce93aa2e5..ac60fea545 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -6,10 +6,10 @@ package api import ( "context" "fmt" + "log/slog" "time" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/users" ) @@ -17,12 +17,12 @@ import ( var _ users.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc users.Service } // LoggingMiddleware adds logging facilities to the clients service. -func LoggingMiddleware(svc users.Service, logger mglog.Logger) users.Service { +func LoggingMiddleware(svc users.Service, logger *slog.Logger) users.Service { return &loggingMiddleware{logger, svc} } diff --git a/users/api/transport.go b/users/api/transport.go index 2bb91595d3..af6255833b 100644 --- a/users/api/transport.go +++ b/users/api/transport.go @@ -4,10 +4,10 @@ package api import ( + "log/slog" "net/http" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" @@ -15,7 +15,7 @@ import ( ) // MakeHandler returns a HTTP handler for Users and Groups API endpoints. -func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger mglog.Logger, instanceID string) http.Handler { +func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { clientsHandler(cls, mux, logger) groupsHandler(grps, mux, logger) diff --git a/ws/api/logging.go b/ws/api/logging.go index 47d9680618..c04e932501 100644 --- a/ws/api/logging.go +++ b/ws/api/logging.go @@ -6,21 +6,21 @@ package api import ( "context" "fmt" + "log/slog" "time" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/ws" ) var _ ws.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { - logger mglog.Logger + logger *slog.Logger svc ws.Service } // LoggingMiddleware adds logging facilities to the websocket service. -func LoggingMiddleware(svc ws.Service, logger mglog.Logger) ws.Service { +func LoggingMiddleware(svc ws.Service, logger *slog.Logger) ws.Service { return &loggingMiddleware{logger, svc} } diff --git a/ws/api/transport.go b/ws/api/transport.go index 3e2335b07b..1398d20664 100644 --- a/ws/api/transport.go +++ b/ws/api/transport.go @@ -6,10 +6,10 @@ package api import ( "context" "errors" + "log/slog" "net/http" "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/ws" "github.com/go-chi/chi/v5" "github.com/gorilla/websocket" @@ -32,11 +32,11 @@ var ( WriteBufferSize: readwriteBufferSize, CheckOrigin: func(r *http.Request) bool { return true }, } - logger mglog.Logger + logger *slog.Logger ) // MakeHandler returns http handler with handshake endpoint. -func MakeHandler(ctx context.Context, svc ws.Service, l mglog.Logger, instanceID string) http.Handler { +func MakeHandler(ctx context.Context, svc ws.Service, l *slog.Logger, instanceID string) http.Handler { logger = l mux := chi.NewRouter() diff --git a/ws/handler.go b/ws/handler.go index f608e9e338..855454294b 100644 --- a/ws/handler.go +++ b/ws/handler.go @@ -6,6 +6,7 @@ package ws import ( "context" "fmt" + "log/slog" "net/url" "regexp" "strings" @@ -13,7 +14,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -57,11 +57,11 @@ var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?] type handler struct { pubsub messaging.PubSub auth magistrala.AuthzServiceClient - logger mglog.Logger + logger *slog.Logger } // NewHandler creates new Handler entity. -func NewHandler(pubsub messaging.PubSub, logger mglog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { +func NewHandler(pubsub messaging.PubSub, logger *slog.Logger, authClient magistrala.AuthzServiceClient) session.Handler { return &handler{ logger: logger, pubsub: pubsub, From 08ab338ba8da8fe3d68c6ea36fb842faeba4ce9a Mon Sep 17 00:00:00 2001 From: Felix Gateru Date: Thu, 18 Jan 2024 17:39:09 +0300 Subject: [PATCH 18/71] NOISSUE - Improve tests in things service (#200) Signed-off-by: felix.gateru --- internal/api/common.go | 8 +- invitations/api/endpoint_test.go | 14 +- things/api/grpc/endpoint_test.go | 179 +++ things/api/http/endpoints_test.go | 1985 +++++++++++++++++++++++++++++ things/api/http/requests.go | 3 - things/api/http/requests_test.go | 933 ++++++++++++++ things/cache/setup_test.go | 54 + things/cache/things.go | 10 +- things/cache/things_test.go | 178 +++ things/postgres/clients.go | 54 +- things/postgres/clients_test.go | 84 +- things/service.go | 5 +- things/service_test.go | 1915 ++++++++++++++++++++-------- 13 files changed, 4865 insertions(+), 557 deletions(-) create mode 100644 things/api/grpc/endpoint_test.go create mode 100644 things/api/http/endpoints_test.go create mode 100644 things/api/http/requests_test.go create mode 100644 things/cache/setup_test.go create mode 100644 things/cache/things_test.go diff --git a/internal/api/common.go b/internal/api/common.go index a3c92ac118..0f5193f164 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -115,7 +115,13 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrMissingMemberKind), errors.Contains(err, apiutil.ErrLimitSize), errors.Contains(err, apiutil.ErrBearerKey), - errors.Contains(err, apiutil.ErrNameSize): + errors.Contains(err, apiutil.ErrNameSize), + errors.Contains(err, svcerr.ErrInvalidStatus), + errors.Contains(err, apiutil.ErrInvalidIDFormat), + errors.Contains(err, apiutil.ErrInvalidQueryParams), + errors.Contains(err, apiutil.ErrInvalidStatus), + errors.Contains(err, apiutil.ErrMissingRelation), + errors.Contains(err, apiutil.ErrValidation): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, errors.ErrAuthentication), diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go index 9aab6f0499..fbf0cd4d07 100644 --- a/invitations/api/endpoint_test.go +++ b/invitations/api/endpoint_test.go @@ -170,7 +170,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid offset", token: validToken, query: "offset=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -186,7 +186,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid limit", token: validToken, query: "limit=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -202,7 +202,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate user_id", token: validToken, query: "user_id=1&user_id=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -218,7 +218,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate invited_by", token: validToken, query: "invited_by=1&invited_by=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -234,7 +234,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate relation", token: validToken, query: "relation=1&relation=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -250,7 +250,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate domain_id", token: validToken, query: "domain_id=1&domain_id=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -274,7 +274,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate state", token: validToken, query: "state=all&state=all", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, diff --git a/things/api/grpc/endpoint_test.go b/things/api/grpc/endpoint_test.go new file mode 100644 index 0000000000..fb979b19c9 --- /dev/null +++ b/things/api/grpc/endpoint_test.go @@ -0,0 +1,179 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc_test + +import ( + "context" + "fmt" + "net" + "testing" + "time" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/pkg/errors" + grpcapi "github.com/absmach/magistrala/things/api/grpc" + "github.com/absmach/magistrala/things/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" +) + +const port = 7000 + +var ( + thingID = "testID" + channelID = "testID" + invalid = "invalid" + valid = "valid" +) + +func startGRPCServer(svc *mocks.Service, port int) { + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(fmt.Sprintf("failed to obtain port: %s", err)) + } + server := grpc.NewServer() + magistrala.RegisterAuthzServiceServer(server, grpcapi.NewServer(svc)) + go func() { + if err := server.Serve(listener); err != nil { + panic(fmt.Sprintf("failed to serve: %s", err)) + } + }() +} + +func TestAuthorize(t *testing.T) { + svc := new(mocks.Service) + startGRPCServer(svc, port) + authAddr := fmt.Sprintf("localhost:%d", port) + conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + authorizeReq *magistrala.AuthorizeReq + res *magistrala.AuthorizeRes + thingID string + authorizeErr error + err error + code codes.Code + }{ + { + desc: "authorize successfully", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: thingID, + SubjectKind: auth.ThingsKind, + Permission: valid, + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + thingID: thingID, + res: &magistrala.AuthorizeRes{Authorized: true, Id: thingID}, + err: nil, + }, + { + desc: "authorize with invalid id", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: invalid, + SubjectKind: auth.ThingsKind, + Permission: "publish", + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthentication, + }, + { + desc: "authorize with missing ID", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: "", + SubjectKind: auth.ThingsKind, + Permission: valid, + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: apiutil.ErrMissingID, + }, + { + desc: "authorize with unauthorized id", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: invalid, + SubjectKind: auth.ThingsKind, + Permission: valid, + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthorization, + }, + { + desc: "authorize with invalid permission", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: thingID, + SubjectKind: auth.ThingsKind, + Permission: invalid, + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthorization, + }, + { + desc: "authorize with invalid channel ID", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: thingID, + SubjectKind: auth.ThingsKind, + Permission: valid, + SubjectType: auth.ThingType, + Object: invalid, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthorization, + }, + { + desc: "authorize with empty channel ID", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: thingID, + SubjectKind: auth.ThingsKind, + Permission: valid, + SubjectType: auth.ThingType, + Object: "", + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthorization, + }, + { + desc: "authorize with empty permission", + authorizeReq: &magistrala.AuthorizeReq{ + Subject: thingID, + SubjectKind: auth.ThingsKind, + Permission: "", + SubjectType: auth.ThingType, + Object: channelID, + ObjectType: auth.GroupType, + }, + res: &magistrala.AuthorizeRes{}, + err: errors.ErrAuthorization, + }, + } + + for _, tc := range cases { + svcCall := svc.On("Authorize", mock.Anything, tc.authorizeReq).Return(tc.thingID, tc.err) + res, err := client.Authorize(context.Background(), tc.authorizeReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.res, res, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.res, res)) + svcCall.Unset() + } +} diff --git a/things/api/http/endpoints_test.go b/things/api/http/endpoints_test.go new file mode 100644 index 0000000000..463160aabd --- /dev/null +++ b/things/api/http/endpoints_test.go @@ -0,0 +1,1985 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http_test + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/0x6flab/namegenerator" + authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/internal/testsutil" + mglog "github.com/absmach/magistrala/logger" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + "github.com/absmach/magistrala/pkg/uuid" + httpapi "github.com/absmach/magistrala/things/api/http" + "github.com/absmach/magistrala/things/mocks" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + idProvider = uuid.New() + secret = "strongsecret" + validCMetadata = mgclients.Metadata{"role": "client"} + ID = testsutil.GenerateUUID(&testing.T{}) + client = mgclients.Client{ + ID: ID, + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: mgclients.Credentials{Identity: "clientidentity", Secret: secret}, + Metadata: validCMetadata, + Status: mgclients.EnabledStatus, + } + validToken = "token" + inValidToken = "invalid" + inValid = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + namesgen = namegenerator.NewNameGenerator() +) + +const contentType = "application/json" + +type testRequest struct { + client *http.Client + method string + url string + contentType string + token string + body io.Reader +} + +func (tr testRequest) make() (*http.Response, error) { + req, err := http.NewRequest(tr.method, tr.url, tr.body) + if err != nil { + return nil, err + } + + if tr.token != "" { + req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) + } + + if tr.contentType != "" { + req.Header.Set("Content-Type", tr.contentType) + } + + req.Header.Set("Referer", "http://localhost") + + return tr.client.Do(req) +} + +func toJSON(data interface{}) string { + jsonData, err := json.Marshal(data) + if err != nil { + return "" + } + return string(jsonData) +} + +func newThingsServer() (*httptest.Server, *mocks.Service) { + gRepo := new(gmocks.Repository) + auth := new(authmocks.Service) + + svc := new(mocks.Service) + gsvc := groups.NewService(gRepo, idProvider, auth) + + logger := mglog.NewMock() + mux := chi.NewRouter() + httpapi.MakeHandler(svc, gsvc, mux, logger, "") + + return httptest.NewServer(mux), svc +} + +func TestCreateThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + client mgclients.Client + token string + contentType string + status int + err error + }{ + { + desc: "register a new thing with a valid token", + client: client, + token: validToken, + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "register an existing thing", + client: client, + token: validToken, + contentType: contentType, + status: http.StatusConflict, + err: errors.ErrConflict, + }, + { + desc: "register a new thing with an empty token", + client: client, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "register a thing with an invalid ID", + client: mgclients.Client{ + ID: inValid, + Credentials: mgclients.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "register a thing that can't be marshalled", + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: errors.ErrMalformedEntity, + }, + { + desc: "register thing with invalid status", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Credentials: mgclients.Credentials{ + Identity: "newclientwithinvalidstatus@example.com", + Secret: secret, + }, + Status: mgclients.AllStatus, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "create thing with invalid contentype", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Credentials: mgclients.Credentials{ + Identity: "example@example.com", + Secret: secret, + }, + }, + + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/", ts.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(data), + } + + svcCall := svc.On("CreateThings", mock.Anything, tc.token, tc.client).Return([]mgclients.Client{tc.client}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestCreateThings(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + num := 3 + var items []mgclients.Client + for i := 0; i < num; i++ { + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: mgclients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: secret, + }, + Metadata: mgclients.Metadata{}, + Status: mgclients.EnabledStatus, + } + items = append(items, client) + } + + cases := []struct { + desc string + client []mgclients.Client + token string + contentType string + status int + err error + len int + }{ + { + desc: "create things with valid token", + client: items, + token: validToken, + contentType: contentType, + status: http.StatusOK, + err: nil, + len: 3, + }, + { + desc: "create things with invalid token", + client: items, + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + len: 0, + }, + { + desc: "create things with empty token", + client: items, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + len: 0, + }, + { + desc: "create things with empty request", + client: []mgclients.Client{}, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrEmptyList, + len: 0, + }, + { + desc: "create things with invalid IDs", + client: []mgclients.Client{ + { + ID: inValid, + }, + { + ID: validID, + }, + { + ID: validID, + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrInvalidIDFormat, + }, + { + desc: "create thing with invalid contentype", + client: []mgclients.Client{ + { + ID: testsutil.GenerateUUID(t), + }, + }, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "register a thing that can't be marshalled", + client: []mgclients.Client{ + { + ID: testsutil.GenerateUUID(t), + Credentials: mgclients.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/bulk", ts.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(data), + } + + svcCall := svc.On("CreateThings", mock.Anything, tc.token, mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + + var bodyRes respBody + err = json.NewDecoder(res.Body).Decode(&bodyRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if bodyRes.Err != "" || bodyRes.Message != "" { + err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.len, bodyRes.Total, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.len, bodyRes.Total)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestListThings(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + query string + token string + listThingsResponse mgclients.ClientsPage + status int + err error + }{ + { + desc: "list things with valid token", + token: validToken, + status: http.StatusOK, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list things with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list things with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list things with offset", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with limit", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with limit greater than max", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with owner_id", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with duplicate owner_id", + token: validToken, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with invalid owner_id", + token: validToken, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with name", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid name", + token: validToken, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with status", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with tags", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid tags", + token: validToken, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with metadata", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with permissions", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid permissions", + token: validToken, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list things with list perms", + token: validToken, + listThingsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "list_perms=true", + status: http.StatusOK, + err: nil, + }, + { + desc: "list things with invalid list perms", + token: validToken, + query: "list_perms=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list things with duplicate list perms", + token: validToken, + query: "list_perms=true&listPerms=true", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: ts.URL + "/things?" + tc.query, + contentType: contentType, + token: tc.token, + } + + svcCall := svc.On("ListClients", mock.Anything, tc.token, "", mock.Anything).Return(tc.listThingsResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + + var bodyRes respBody + err = json.NewDecoder(res.Body).Decode(&bodyRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if bodyRes.Err != "" || bodyRes.Message != "" { + err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestViewThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + token string + id string + status int + err error + }{ + { + desc: "view client with valid token", + token: validToken, + id: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "view client with invalid token", + token: inValidToken, + id: client.ID, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "view client with empty token", + token: "", + id: client.ID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id), + token: tc.token, + } + + svcCall := svc.On("ViewClient", mock.Anything, tc.token, tc.id).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestViewThingPerms(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + token string + thingID string + response []string + status int + err error + }{ + { + desc: "view thing permissions with valid token", + token: validToken, + thingID: client.ID, + response: []string{"view", "delete", "membership"}, + status: http.StatusOK, + err: nil, + }, + { + desc: "view thing permissions with invalid token", + token: inValidToken, + thingID: client.ID, + response: []string{}, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "view thing permissions with empty token", + token: "", + thingID: client.ID, + response: []string{}, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "view thing permissions with invalid id", + token: validToken, + thingID: inValid, + response: []string{}, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/things/%s/permissions", ts.URL, tc.thingID), + token: tc.token, + } + + svcCall := svc.On("ViewClientPerms", mock.Anything, tc.token, tc.thingID).Return(tc.response, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.Equal(t, len(tc.response), len(resBody.Permissions), fmt.Sprintf("%s: expected %d got %d", tc.desc, len(tc.response), len(resBody.Permissions))) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUpdateThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + newName := "newname" + newTag := "newtag" + newMetadata := mgclients.Metadata{"newkey": "newvalue"} + + cases := []struct { + desc string + id string + data string + clientResponse mgclients.Client + token string + contentType string + status int + err error + }{ + { + desc: "update thing with valid token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + clientResponse: mgclients.Client{ + ID: client.ID, + Name: newName, + Tags: []string{newTag}, + Metadata: newMetadata, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "update thing with invalid token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update thing with empty token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update thing with invalid id", + id: inValid, + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "update thing with invalid contentype", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update thing with malformed data", + id: client.ID, + data: fmt.Sprintf(`{"name":%s}`, "invalid"), + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "update thing with empty id", + id: " ", + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("UpdateClient", mock.Anything, tc.token, mock.Anything).Return(tc.clientResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + + if err == nil { + assert.Equal(t, tc.clientResponse.ID, resBody.ID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.clientResponse, resBody.ID)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUpdateThingsTags(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + newTag := "newtag" + + cases := []struct { + desc string + id string + data string + contentType string + clientResponse mgclients.Client + token string + status int + err error + }{ + { + desc: "update thing tags with valid token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + clientResponse: mgclients.Client{ + ID: client.ID, + Tags: []string{newTag}, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "update thing tags with empty token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update thing tags with invalid token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update thing tags with invalid id", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "update thing tags with invalid contentype", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update things tags with empty id", + id: "", + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "update things with malfomed data", + id: client.ID, + data: fmt.Sprintf(`{"tags":[%s]}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/things/%s/tags", ts.URL, tc.id), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("UpdateClientTags", mock.Anything, tc.token, mock.Anything).Return(tc.clientResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUpdateClientSecret(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + data string + client mgclients.Client + contentType string + token string + status int + err error + }{ + { + desc: "update thing secret with valid token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "update thing secret with empty token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update thing secret with invalid token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: inValid, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update thing secret with empty id", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: "", + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "update thing secret with empty secret", + data: fmt.Sprintf(`{"secret": "%s"}`, ""), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrBearerKey, + }, + { + desc: "update thing secret with invalid contentype", + data: fmt.Sprintf(`{"secret": "%s"}`, ""), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update thing secret with malformed data", + data: fmt.Sprintf(`{"secret": %s}`, "invalid"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/things/%s/secret", ts.URL, tc.client.ID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("UpdateClientSecret", mock.Anything, tc.token, tc.client.ID, mock.Anything).Return(tc.client, tc.err) + + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestEnableThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + client mgclients.Client + response mgclients.Client + token string + status int + err error + }{ + { + desc: "enable thing with valid token", + client: client, + response: mgclients.Client{ + ID: client.ID, + Status: mgclients.EnabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "enable thing with invalid token", + client: client, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "enable thing with empty id", + client: mgclients.Client{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "enable thing with invalid id", + client: mgclients.Client{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/%s/enable", ts.URL, tc.client.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + + svcCall := svc.On("EnableClient", mock.Anything, tc.token, tc.client.ID).Return(tc.response, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + if err == nil { + assert.Equal(t, tc.response.Status, resBody.Status, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.response.Status, resBody.Status)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestDisableThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + client mgclients.Client + response mgclients.Client + token string + status int + err error + }{ + { + desc: "disable thing with valid token", + client: client, + response: mgclients.Client{ + ID: client.ID, + Status: mgclients.DisabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "disable thing with invalid token", + client: client, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "disable thing with empty id", + client: mgclients.Client{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "disable thing with invalid id", + client: mgclients.Client{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/%s/disable", ts.URL, tc.client.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + + svcCall := svc.On("DisableClient", mock.Anything, tc.token, tc.client.ID).Return(tc.response, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + if err == nil { + assert.Equal(t, tc.response.Status, resBody.Status, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.response.Status, resBody.Status)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestShareThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + data string + thingID string + token string + contentType string + status int + err error + }{ + { + desc: "share thing with valid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "share thing with invalid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "share thing with empty token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "share thing with empty id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: " ", + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "share thing with missing relation", + data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingRelation, + }, + { + desc: "share thing with malformed data", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "share thing with empty thing id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: "", + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "share thing with empty relation", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingRelation, + }, + { + desc: "share thing with empty user ids", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "share thing with invalid content type", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/%s/share", ts.URL, tc.thingID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("Share", mock.Anything, tc.token, tc.thingID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUnShareThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + data string + thingID string + token string + contentType string + status int + err error + }{ + { + desc: "unshare thing with valid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusNoContent, + err: nil, + }, + { + desc: "unshare thing with invalid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "unshare thing with empty token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "unshare thing with empty id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: " ", + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "unshare thing with missing relation", + data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingRelation, + }, + { + desc: "unshare thing with malformed data", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "unshare thing with empty thing id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: "", + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "unshare thing with empty relation", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrMissingRelation, + }, + { + desc: "unshare thing with empty user ids", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), + thingID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "unshare thing with invalid content type", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + thingID: client.ID, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/things/%s/unshare", ts.URL, tc.thingID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("Unshare", mock.Anything, tc.token, tc.thingID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestDeleteThing(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + id string + token string + status int + err error + }{ + { + desc: "delete thing with valid token", + id: client.ID, + token: validToken, + status: http.StatusNoContent, + err: nil, + }, + { + desc: "delete thing with invalid token", + id: client.ID, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "delete thing with empty token", + id: client.ID, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "delete thing with empty id", + id: " ", + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "delete thing with invalid id", + id: "invalid", + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodDelete, + url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id), + token: tc.token, + } + + svcCall := svc.On("DeleteClient", mock.Anything, tc.token, tc.id).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestListMembers(t *testing.T) { + ts, svc := newThingsServer() + defer ts.Close() + + cases := []struct { + desc string + query string + token string + listMembersResponse mgclients.MembersPage + status int + err error + groupdID string + }{ + { + desc: "list members with valid token", + token: validToken, + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with empty token", + token: "", + groupdID: client.ID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list members with invalid token", + token: inValidToken, + groupdID: client.ID, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list members with offset", + token: validToken, + query: "offset=1", + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid offset", + token: validToken, + query: "offset=invalid", + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with limit", + token: validToken, + query: "limit=1", + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid limit", + token: validToken, + query: "limit=invalid", + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with limit greater than 100", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with channel_id", + token: validToken, + query: fmt.Sprintf("channel_id=%s", validID), + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid channel_id", + token: validToken, + query: "channel_id=invalid", + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate channel_id", + token: validToken, + query: fmt.Sprintf("channel_id=%s&channel_id=%s", validID, validID), + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with connected set", + token: validToken, + query: "connected=true", + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid connected set", + token: validToken, + query: "connected=invalid", + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate connected set", + token: validToken, + query: "connected=true&connected=false", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with invalid group id", + token: validToken, + query: "", + groupdID: "invalid", + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "list members with empty group id", + token: validToken, + query: "", + groupdID: "", + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "list members with status", + query: fmt.Sprintf("status=%s", mgclients.EnabledStatus), + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + token: validToken, + groupdID: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid status", + query: "status=invalid", + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate status", + query: fmt.Sprintf("status=%s&status=%s", mgclients.EnabledStatus, mgclients.DisabledStatus), + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with metadata", + token: validToken, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + groupdID: client.ID, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid metadata", + query: "metadata=invalid", + groupdID: client.ID, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate metadata", + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + groupdID: client.ID, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list members with permission", + query: fmt.Sprintf("permission=%s", "read"), + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + token: validToken, + groupdID: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid permission", + query: "permission=invalid", + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate permission", + query: fmt.Sprintf("permission=%s&permission=%s", "read", "write"), + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with list permission", + query: "list_perms=true", + token: validToken, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + groupdID: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "list members with invalid list permission", + query: "list_perms=invalid", + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with duplicate list permission", + query: "list_perms=true&list_perms=false", + token: validToken, + groupdID: client.ID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list members with all query params", + query: fmt.Sprintf("offset=1&limit=1&channel_id=%s&connected=true&status=%s&metadata=%s&permission=%s&list_perms=true", validID, mgclients.EnabledStatus, "%7B%22domain%22%3A%20%22example.com%22%7D", "read"), + token: validToken, + groupdID: client.ID, + listMembersResponse: mgclients.MembersPage{ + Page: mgclients.Page{ + Offset: 1, + Limit: 1, + Total: 1, + }, + Members: []mgclients.Client{client}, + }, + status: http.StatusOK, + err: nil, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: ts.URL + fmt.Sprintf("/channels/%s/things?", tc.groupdID) + tc.query, + contentType: contentType, + token: tc.token, + } + + svcCall := svc.On("ListClientsByGroup", mock.Anything, tc.token, mock.Anything, mock.Anything).Return(tc.listMembersResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + + var bodyRes respBody + err = json.NewDecoder(res.Body).Decode(&bodyRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if bodyRes.Err != "" || bodyRes.Message != "" { + err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +type respBody struct { + Err string `json:"error"` + Message string `json:"message"` + Total int `json:"total"` + Permissions []string `json:"permissions"` + ID string `json:"id"` + Tags []string `json:"tags"` + Status mgclients.Status `json:"status"` +} diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 7aab9565e4..c1f0a10496 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -110,9 +110,6 @@ func (req listClientsReq) validate() error { req.visibility != api.SharedVisibility { return apiutil.ErrInvalidVisibilityType } - if req.limit > api.MaxLimitSize || req.limit < 1 { - return apiutil.ErrLimitSize - } if len(req.name) > api.MaxNameSize { return apiutil.ErrNameSize } diff --git a/things/api/http/requests_test.go b/things/api/http/requests_test.go new file mode 100644 index 0000000000..1ff0c2ed54 --- /dev/null +++ b/things/api/http/requests_test.go @@ -0,0 +1,933 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "strings" + "testing" + + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + "github.com/stretchr/testify/assert" +) + +const ( + valid = "valid" + invalid = "invalid" +) + +var validID = testsutil.GenerateUUID(&testing.T{}) + +func TestCreateThingReqValidate(t *testing.T) { + cases := []struct { + desc string + req createClientReq + err error + }{ + { + desc: "valid request", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: valid, + }, + }, + err: nil, + }, + { + desc: "empty token", + req: createClientReq{ + token: "", + client: mgclients.Client{ + ID: validID, + Name: valid, + }, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "name too long", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + }, + err: apiutil.ErrNameSize, + }, + { + desc: "invalid id", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: invalid, + Name: valid, + }, + }, + err: apiutil.ErrInvalidIDFormat, + }, + } + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err) + } +} + +func TestCreateThingsReqValidate(t *testing.T) { + cases := []struct { + desc string + req createClientsReq + err error + }{ + { + desc: "valid request", + req: createClientsReq{ + token: valid, + Clients: []mgclients.Client{ + { + ID: validID, + Name: valid, + }, + }, + }, + err: nil, + }, + { + desc: "empty token", + req: createClientsReq{ + token: "", + Clients: []mgclients.Client{ + { + ID: validID, + Name: valid, + }, + }, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty list", + req: createClientsReq{ + token: valid, + Clients: []mgclients.Client{}, + }, + err: apiutil.ErrEmptyList, + }, + { + desc: "name too long", + req: createClientsReq{ + token: valid, + Clients: []mgclients.Client{ + { + ID: validID, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + }, + }, + err: apiutil.ErrNameSize, + }, + { + desc: "invalid id", + req: createClientsReq{ + token: valid, + Clients: []mgclients.Client{ + { + ID: invalid, + Name: valid, + }, + }, + }, + err: apiutil.ErrInvalidIDFormat, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestViewClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req viewClientReq + err error + }{ + { + desc: "valid request", + req: viewClientReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: viewClientReq{ + token: "", + id: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: viewClientReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestViewClientPermsReq(t *testing.T) { + cases := []struct { + desc string + req viewClientPermsReq + err error + }{ + { + desc: "valid request", + req: viewClientPermsReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: viewClientPermsReq{ + token: "", + id: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: viewClientPermsReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestListClientsReqValidate(t *testing.T) { + cases := []struct { + desc string + req listClientsReq + err error + }{ + { + desc: "valid request", + req: listClientsReq{ + token: valid, + limit: 10, + }, + err: nil, + }, + { + desc: "limit too big", + req: listClientsReq{ + token: valid, + limit: api.MaxLimitSize + 1, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "limit too small", + req: listClientsReq{ + token: valid, + limit: 0, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "invalid visibility", + req: listClientsReq{ + token: valid, + limit: 10, + visibility: "invalid", + }, + err: apiutil.ErrInvalidVisibilityType, + }, + { + desc: "name too long", + req: listClientsReq{ + token: valid, + limit: 10, + name: strings.Repeat("a", api.MaxNameSize+1), + }, + err: apiutil.ErrNameSize, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestListMembersReqValidate(t *testing.T) { + cases := []struct { + desc string + req listMembersReq + err error + }{ + { + desc: "valid request", + req: listMembersReq{ + token: valid, + groupID: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: listMembersReq{ + token: "", + groupID: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: listMembersReq{ + token: valid, + groupID: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientReq + err error + }{ + { + desc: "valid request", + req: updateClientReq{ + token: valid, + id: validID, + Name: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientReq{ + token: "", + id: validID, + Name: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientReq{ + token: valid, + id: "", + Name: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "name too long", + req: updateClientReq{ + token: valid, + id: validID, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + err: apiutil.ErrNameSize, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientTagsReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientTagsReq + err error + }{ + { + desc: "valid request", + req: updateClientTagsReq{ + token: valid, + id: validID, + Tags: []string{"tag1", "tag2"}, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientTagsReq{ + token: "", + id: validID, + Tags: []string{"tag1", "tag2"}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientTagsReq{ + token: valid, + id: "", + Tags: []string{"tag1", "tag2"}, + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientCredentialsReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientCredentialsReq + err error + }{ + { + desc: "valid request", + req: updateClientCredentialsReq{ + token: valid, + id: validID, + Secret: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientCredentialsReq{ + token: "", + id: validID, + Secret: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientCredentialsReq{ + token: valid, + id: "", + Secret: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty secret", + req: updateClientCredentialsReq{ + token: valid, + id: validID, + Secret: "", + }, + err: apiutil.ErrBearerKey, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestChangeClientStatusReqValidate(t *testing.T) { + cases := []struct { + desc string + req changeClientStatusReq + err error + }{ + { + desc: "valid request", + req: changeClientStatusReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty id", + req: changeClientStatusReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestAssignUsersRequestValidate(t *testing.T) { + cases := []struct { + desc string + req assignUsersRequest + err error + }{ + { + desc: "valid request", + req: assignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: assignUsersRequest{ + token: "", + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: assignUsersRequest{ + token: valid, + groupID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty users", + req: assignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: apiutil.ErrEmptyList, + }, + { + desc: "empty relation", + req: assignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: apiutil.ErrMissingRelation, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUnassignUsersRequestValidate(t *testing.T) { + cases := []struct { + desc string + req unassignUsersRequest + err error + }{ + { + desc: "valid request", + req: unassignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: unassignUsersRequest{ + token: "", + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: unassignUsersRequest{ + token: valid, + groupID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty users", + req: unassignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: apiutil.ErrEmptyList, + }, + { + desc: "empty relation", + req: unassignUsersRequest{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: apiutil.ErrMissingRelation, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestAssignUserGroupsRequestValidate(t *testing.T) { + cases := []struct { + desc string + req assignUserGroupsRequest + err error + }{ + { + desc: "valid request", + req: assignUserGroupsRequest{ + token: valid, + groupID: validID, + UserGroupIDs: []string{validID}, + }, + err: nil, + }, + { + desc: "empty token", + req: assignUserGroupsRequest{ + token: "", + groupID: validID, + UserGroupIDs: []string{validID}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty group id", + req: assignUserGroupsRequest{ + token: valid, + groupID: "", + UserGroupIDs: []string{validID}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty user group ids", + req: assignUserGroupsRequest{ + token: valid, + groupID: validID, + UserGroupIDs: []string{}, + }, + err: apiutil.ErrEmptyList, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUnassignUserGroupsRequestValidate(t *testing.T) { + cases := []struct { + desc string + req unassignUserGroupsRequest + err error + }{ + { + desc: "valid request", + req: unassignUserGroupsRequest{ + token: valid, + groupID: validID, + UserGroupIDs: []string{validID}, + }, + err: nil, + }, + { + desc: "empty token", + req: unassignUserGroupsRequest{ + token: "", + groupID: validID, + UserGroupIDs: []string{validID}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty group id", + req: unassignUserGroupsRequest{ + token: valid, + groupID: "", + UserGroupIDs: []string{validID}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty user group ids", + req: unassignUserGroupsRequest{ + token: valid, + groupID: validID, + UserGroupIDs: []string{}, + }, + err: apiutil.ErrEmptyList, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestConnectChannelThingRequestValidate(t *testing.T) { + cases := []struct { + desc string + req connectChannelThingRequest + err error + }{ + { + desc: "valid request", + req: connectChannelThingRequest{ + token: valid, + ChannelID: validID, + ThingID: validID, + }, + err: nil, + }, + { + desc: "empty channel id", + req: connectChannelThingRequest{ + token: valid, + ChannelID: "", + ThingID: validID, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "empty thing id", + req: connectChannelThingRequest{ + token: valid, + ChannelID: validID, + ThingID: "", + }, + err: errors.ErrCreateEntity, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestDisconnectChannelThingRequestValidate(t *testing.T) { + cases := []struct { + desc string + req disconnectChannelThingRequest + err error + }{ + { + desc: "valid request", + req: disconnectChannelThingRequest{ + token: valid, + ChannelID: validID, + ThingID: validID, + }, + err: nil, + }, + { + desc: "empty channel id", + req: disconnectChannelThingRequest{ + token: valid, + ChannelID: "", + ThingID: validID, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "empty thing id", + req: disconnectChannelThingRequest{ + token: valid, + ChannelID: validID, + ThingID: "", + }, + err: errors.ErrCreateEntity, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestThingShareRequestValidate(t *testing.T) { + cases := []struct { + desc string + req thingShareRequest + err error + }{ + { + desc: "valid request", + req: thingShareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty thing id", + req: thingShareRequest{ + token: valid, + thingID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: errors.ErrMalformedEntity, + }, + { + desc: "empty user ids", + req: thingShareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "empty relation", + req: thingShareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: errors.ErrCreateEntity, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestThingUnshareRequestValidate(t *testing.T) { + cases := []struct { + desc string + req thingUnshareRequest + err error + }{ + { + desc: "valid request", + req: thingUnshareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty thing id", + req: thingUnshareRequest{ + token: valid, + thingID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: errors.ErrMalformedEntity, + }, + { + desc: "empty user ids", + req: thingUnshareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "empty relation", + req: thingUnshareRequest{ + token: valid, + thingID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: errors.ErrCreateEntity, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestDeleteClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req deleteClientReq + err error + }{ + { + desc: "valid request", + req: deleteClientReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: deleteClientReq{ + token: "", + id: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: deleteClientReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} diff --git a/things/cache/setup_test.go b/things/cache/setup_test.go new file mode 100644 index 0000000000..435f2cdf36 --- /dev/null +++ b/things/cache/setup_test.go @@ -0,0 +1,54 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package cache_test + +import ( + "context" + "fmt" + "log" + "os" + "testing" + + "github.com/go-redis/redis/v8" + "github.com/ory/dockertest/v3" +) + +var ( + redisClient *redis.Client + redisURL string +) + +func TestMain(m *testing.M) { + pool, err := dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + container, err := pool.Run("redis", "7.2.0-alpine", nil) + if err != nil { + log.Fatalf("Could not start container: %s", err) + } + + redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp")) + opts, err := redis.ParseURL(redisURL) + if err != nil { + log.Fatalf("Could not parse redis URL: %s", err) + } + + if err := pool.Retry(func() error { + redisClient = redis.NewClient(opts) + + return redisClient.Ping(context.Background()).Err() + }); err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + code := m.Run() + + if err := pool.Purge(container); err != nil { + log.Fatalf("Could not purge container: %s", err) + } + + os.Exit(code) +} diff --git a/things/cache/things.go b/things/cache/things.go index c631fe8818..f4d1a2d667 100644 --- a/things/cache/things.go +++ b/things/cache/things.go @@ -34,6 +34,9 @@ func NewCache(client *redis.Client, duration time.Duration) things.Cache { } func (tc *thingCache) Save(ctx context.Context, thingKey, thingID string) error { + if thingKey == "" || thingID == "" { + return errors.Wrap(errors.ErrCreateEntity, errors.New("thing key or thing id is empty")) + } tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) if err := tc.client.Set(ctx, tkey, thingID, tc.keyDuration).Err(); err != nil { return errors.Wrap(errors.ErrCreateEntity, err) @@ -48,14 +51,15 @@ func (tc *thingCache) Save(ctx context.Context, thingKey, thingID string) error } func (tc *thingCache) ID(ctx context.Context, thingKey string) (string, error) { + if thingKey == "" { + return "", errors.ErrNotFound + } + tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) thingID, err := tc.client.Get(ctx, tkey).Result() if err != nil { return "", errors.Wrap(errors.ErrNotFound, err) } - if thingID == "" { - return "", errors.ErrNotFound - } return thingID, nil } diff --git a/things/cache/things_test.go b/things/cache/things_test.go new file mode 100644 index 0000000000..61821cb19e --- /dev/null +++ b/things/cache/things_test.go @@ -0,0 +1,178 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package cache_test + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/things/cache" + "github.com/stretchr/testify/assert" +) + +const ( + testKey = "testKey" + testID = "testID" + testKey2 = "testKey2" + testID2 = "testID2" +) + +func TestSave(t *testing.T) { + redisClient.FlushAll(context.Background()) + tscache := cache.NewCache(redisClient, 1*time.Minute) + ctx := context.Background() + + cases := []struct { + desc string + key string + id string + err error + }{ + { + desc: "Save thing to cache", + key: testKey, + id: testID, + err: nil, + }, + { + desc: "Save already cached thing to cache", + key: testKey, + id: testID, + err: nil, + }, + { + desc: "Save another thing to cache", + key: testKey2, + id: testID2, + err: nil, + }, + { + desc: "Save thing with long key ", + key: strings.Repeat("a", 513*1024*1024), + id: testID, + err: errors.ErrCreateEntity, + }, + { + desc: "Save thing with long id ", + key: testKey, + id: strings.Repeat("a", 513*1024*1024), + err: errors.ErrCreateEntity, + }, + { + desc: "Save thing with empty key", + key: "", + id: testID, + err: errors.ErrCreateEntity, + }, + { + desc: "Save thing with empty id", + key: testKey, + id: "", + err: errors.ErrCreateEntity, + }, + { + desc: "Save thing with empty key and id", + key: "", + id: "", + err: errors.ErrCreateEntity, + }, + } + + for _, tc := range cases { + err := tscache.Save(ctx, tc.key, tc.id) + if err == nil { + id, _ := tscache.ID(ctx, tc.key) + assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) + } +} + +func TestID(t *testing.T) { + redisClient.FlushAll(context.Background()) + tscache := cache.NewCache(redisClient, 1*time.Minute) + ctx := context.Background() + + err := tscache.Save(ctx, testKey, testID) + assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err)) + + cases := []struct { + desc string + key string + id string + err error + }{ + { + desc: "Get thing ID from cache", + key: testKey, + id: testID, + err: nil, + }, + { + desc: "Get thing ID from cache for non existing thing", + key: "nonExistingKey", + id: "", + err: errors.ErrNotFound, + }, + { + desc: "Get thing ID from cache for empty key", + key: "", + id: "", + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + id, err := tscache.ID(ctx, tc.key) + if err == nil { + assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestRemove(t *testing.T) { + redisClient.FlushAll(context.Background()) + tscache := cache.NewCache(redisClient, 1*time.Minute) + ctx := context.Background() + + err := tscache.Save(ctx, testKey, testID) + assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err)) + + cases := []struct { + desc string + key string + err error + }{ + { + desc: "Remove existing thing from cache", + key: testID, + err: nil, + }, + { + desc: "Remove non existing thing from cache", + key: testID2, + err: nil, + }, + { + desc: "Remove thing with empty ID from cache", + key: "", + err: nil, + }, + { + desc: "Remove thing with long id from cache", + key: strings.Repeat("a", 513*1024*1024), + err: errors.ErrRemoveEntity, + }, + } + + for _, tc := range cases { + err := tscache.Remove(ctx, tc.key) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} diff --git a/things/postgres/clients.go b/things/postgres/clients.go index e03f947906..a9ca6b94ad 100644 --- a/things/postgres/clients.go +++ b/things/postgres/clients.go @@ -5,7 +5,6 @@ package postgres import ( "context" - "database/sql" "fmt" "github.com/absmach/magistrala/internal/postgres" @@ -73,17 +72,19 @@ func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgcl } defer row.Close() - row.Next() - dbcli = pgclients.DBClient{} - if err := row.StructScan(&dbcli); err != nil { - return []mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - client, err := pgclients.ToClient(dbcli) - if err != nil { - return []mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + if row.Next() { + dbcli = pgclients.DBClient{} + if err := row.StructScan(&dbcli); err != nil { + return []mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + client, err := pgclients.ToClient(dbcli) + if err != nil { + return []mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + clients = append(clients, client) } - clients = append(clients, client) } if err = tx.Commit(); err != nil { return []mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) @@ -95,26 +96,45 @@ func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgcl func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mgclients.Client, error) { q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients - WHERE secret = $1 AND status = %d`, mgclients.EnabledStatus) + WHERE secret = :secret AND status = %d`, mgclients.EnabledStatus) dbc := pgclients.DBClient{ Secret: key, } - if err := repo.DB.QueryRowxContext(ctx, q, key).StructScan(&dbc); err != nil { - if err == sql.ErrNoRows { - return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + rows, err := repo.DB.NamedQueryContext(ctx, q, dbc) + if err != nil { + return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + dbc = pgclients.DBClient{} + if rows.Next() { + if err = rows.StructScan(&dbc); err != nil { + return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) } - return mgclients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + + client, err := pgclients.ToClient(dbc) + if err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + return client, nil } - return pgclients.ToClient(dbc) + return mgclients.Client{}, repoerr.ErrNotFound } func (repo clientRepo) Delete(ctx context.Context, id string) error { q := "DELETE FROM clients AS c WHERE c.id = $1 ;" - if _, err := repo.DB.ExecContext(ctx, q, id); err != nil { + + result, err := repo.DB.ExecContext(ctx, q, id) + if err != nil { return postgres.HandleError(repoerr.ErrRemoveEntity, err) } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil } diff --git a/things/postgres/clients_test.go b/things/postgres/clients_test.go index d718a2aaa3..2e6b834213 100644 --- a/things/postgres/clients_test.go +++ b/things/postgres/clients_test.go @@ -9,9 +9,11 @@ import ( "strings" "testing" + "github.com/0x6flab/namegenerator" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/things/postgres" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,6 +26,7 @@ var ( clientIdentity = "client-identity@example.com" clientName = "client name" invalidClientID = "invalidClientID" + namesgen = namegenerator.NewNameGenerator() ) func TestClientsSave(t *testing.T) { @@ -149,6 +152,21 @@ func TestClientsSave(t *testing.T) { }, err: nil, }, + { + desc: "add a client with invalid metadata", + client: clients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: clients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: testsutil.GenerateUUID(t), + }, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + }, + err: errors.ErrMalformedEntity, + }, } for _, tc := range cases { rClient, err := repo.Save(context.Background(), tc.client) @@ -174,23 +192,43 @@ func TestClientsRetrieveBySecret(t *testing.T) { Identity: clientIdentity, Secret: testsutil.GenerateUUID(t), }, - Status: clients.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, } _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - cases := map[string]struct { - secret string - err error + cases := []struct { + desc string + secret string + response clients.Client + err error }{ - "retrieve existing client": {client.Credentials.Secret, nil}, - "retrieve non-existing client": {"non-exsistent", errors.ErrNotFound}, + { + desc: "retrieve client by secret successfully", + secret: client.Credentials.Secret, + response: client, + err: nil, + }, + { + desc: "retrieve client by invalid secret", + secret: "non-existent-secret", + response: clients.Client{}, + err: errors.ErrNotFound, + }, + { + desc: "retrieve client by empty secret", + secret: "", + response: clients.Client{}, + err: errors.ErrNotFound, + }, } - for desc, tc := range cases { - _, err := repo.RetrieveBySecret(context.Background(), tc.secret) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) + for _, tc := range cases { + res, err := repo.RetrieveBySecret(context.Background(), tc.secret) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, res, tc.response, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, res)) } } @@ -214,16 +252,30 @@ func TestDelete(t *testing.T) { _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - cases := map[string]struct { - id string - err error + cases := []struct { + desc string + id string + err error }{ - "delete client id": {client.ID, nil}, - "delete invalid client id ": {invalidClientID, nil}, + { + desc: "delete client successfully", + id: client.ID, + err: nil, + }, + { + desc: "delete client with invalid id", + id: invalidClientID, + err: repoerr.ErrNotFound, + }, + { + desc: "delete client with empty id", + id: "", + err: repoerr.ErrNotFound, + }, } - for desc, tc := range cases { + for _, tc := range cases { err := repo.Delete(context.Background(), tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) } } diff --git a/things/service.go b/things/service.go index e8a0284226..1a5c3b5d3b 100644 --- a/things/service.go +++ b/things/service.go @@ -185,9 +185,6 @@ func (svc service) ListClients(ctx context.Context, token, reqUserID string, pm err := svc.checkSuperAdmin(ctx, res.GetUserId()) switch { case err == nil: - if res.GetDomainId() == "" { - return mgclients.ClientsPage{}, errors.ErrDomainAuthorization - } pm.Owner = res.GetDomainId() default: // If domain is disabled , then this authorization will fail for all non-admin domain users @@ -425,7 +422,7 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid func (svc service) Unshare(ctx context.Context, token, id, relation string, userids ...string) error { user, err := svc.identify(ctx, token) if err != nil { - return nil + return err } if _, err := svc.authorize(ctx, user.GetDomainId(), auth.UserType, auth.UsersKind, user.GetId(), auth.DeletePermission, auth.ThingType, id); err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) diff --git a/things/service_test.go b/things/service_test.go index 14d91a5095..8e92c29c45 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/absmach/magistrala" + authsvc "github.com/absmach/magistrala/auth" authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/testsutil" mgclients "github.com/absmach/magistrala/pkg/clients" @@ -36,11 +37,15 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - adminEmail = "admin@example.com" - myKey = "mine" - validToken = "token" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - wrongID = testsutil.GenerateUUID(&testing.T{}) + adminEmail = "admin@example.com" + validToken = "token" + inValidToken = invalid + valid = "valid" + invalid = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + wrongID = testsutil.GenerateUUID(&testing.T{}) + errAddPolicies = errors.New("failed to add policies") + errRemovePolicies = errors.New("failed to remove the policies") ) func newService() (things.Service, *mocks.Repository, *authmocks.Service, *mocks.Cache) { @@ -53,30 +58,39 @@ func newService() (things.Service, *mocks.Repository, *authmocks.Service, *mocks return things.NewService(auth, cRepo, gRepo, thingCache, idProvider), cRepo, auth, thingCache } -func TestRegisterClient(t *testing.T) { +func TestCreateThings(t *testing.T) { svc, cRepo, auth, _ := newService() cases := []struct { - desc string - client mgclients.Client - token string - err error + desc string + thing mgclients.Client + token string + authResponse *magistrala.AuthorizeRes + addPolicyResponse *magistrala.AddPoliciesRes + authorizeErr error + identifyErr error + addPolicyErr error + saveErr error + err error }{ { - desc: "register new client", - client: client, - token: validToken, - err: nil, + desc: "create a new thing successfully", + thing: client, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, }, { - desc: "register existing client", - client: client, - token: validToken, - err: svcerr.ErrConflict, + desc: "create a an existing thing", + thing: client, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + saveErr: errors.ErrConflict, + err: svcerr.ErrConflict, }, { - desc: "register a new enabled client with name", - client: mgclients.Client{ + desc: "create a new enabled thing with name", + thing: mgclients.Client{ Name: "clientWithName", Credentials: mgclients.Credentials{ Identity: "newclientwithname@example.com", @@ -84,24 +98,28 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new disabled client with name", - client: mgclients.Client{ + desc: "create a new disabled thing with name", + thing: mgclients.Client{ Name: "clientWithName", Credentials: mgclients.Credentials{ Identity: "newclientwithname@example.com", Secret: secret, }, }, - err: nil, - token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + token: validToken, + err: nil, }, { - desc: "register a new enabled client with tags", - client: mgclients.Client{ + desc: "create a new enabled thing with tags", + thing: mgclients.Client{ Tags: []string{"tag1", "tag2"}, Credentials: mgclients.Credentials{ Identity: "newclientwithtags@example.com", @@ -109,12 +127,14 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + token: validToken, + err: nil, }, { - desc: "register a new disabled client with tags", - client: mgclients.Client{ + desc: "create a new disabled thing with tags", + thing: mgclients.Client{ Tags: []string{"tag1", "tag2"}, Credentials: mgclients.Credentials{ Identity: "newclientwithtags@example.com", @@ -122,12 +142,14 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.DisabledStatus, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new enabled client with metadata", - client: mgclients.Client{ + desc: "create a new enabled thing with metadata", + thing: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "newclientwithmetadata@example.com", Secret: secret, @@ -135,47 +157,55 @@ func TestRegisterClient(t *testing.T) { Metadata: validCMetadata, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new disabled client with metadata", - client: mgclients.Client{ + desc: "create a new disabled thing with metadata", + thing: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "newclientwithmetadata@example.com", Secret: secret, }, Metadata: validCMetadata, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new disabled client", - client: mgclients.Client{ + desc: "create a new disabled thing", + thing: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "newclientwithvalidstatus@example.com", Secret: secret, }, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new client with valid disabled status", - client: mgclients.Client{ + desc: "create a new thing with valid disabled status", + thing: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "newclientwithvalidstatus@example.com", Secret: secret, }, Status: mgclients.DisabledStatus, }, - err: nil, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new client with all fields", - client: mgclients.Client{ + desc: "create a new thing with all fields", + thing: mgclients.Client{ Name: "newclientwithallfields", Tags: []string{"tag1", "tag2"}, Credentials: mgclients.Credentials{ @@ -187,72 +217,113 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, - }, - { - desc: "register a new client with missing identity", - client: mgclients.Client{ - Name: "clientWithMissingIdentity", - Credentials: mgclients.Credentials{ - Secret: secret, - }, - }, - err: repoerr.ErrMalformedEntity, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, }, { - desc: "register a new client with invalid owner", - client: mgclients.Client{ + desc: "create a new thing with invalid owner", + thing: mgclients.Client{ Owner: wrongID, Credentials: mgclients.Credentials{ Identity: "newclientwithinvalidowner@example.com", Secret: secret, }, }, - err: repoerr.ErrMalformedEntity, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + saveErr: repoerr.ErrMalformedEntity, + err: repoerr.ErrCreateEntity, }, { - desc: "register a new client with empty secret", - client: mgclients.Client{ + desc: "create a new thing with empty secret", + thing: mgclients.Client{ Owner: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{ Identity: "newclientwithemptysecret@example.com", }, }, - err: repoerr.ErrMissingSecret, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + saveErr: repoerr.ErrMissingSecret, + err: repoerr.ErrCreateEntity, }, { - desc: "register a new client with invalid status", - client: mgclients.Client{ + desc: "create a new thing with invalid status", + thing: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "newclientwithinvalidstatus@example.com", Secret: secret, }, Status: mgclients.AllStatus, }, - err: svcerr.ErrInvalidStatus, - token: validToken, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "create a new thing with invalid token", + thing: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "newclientwithinvalidtoken@example.com", + Secret: secret, + }, + Status: mgclients.EnabledStatus, + }, + token: inValidToken, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "create a new thing by unathorized user", + thing: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "newclientwithunathorizeduser@example.com", + Secret: secret, + }, + Status: mgclients.EnabledStatus, + }, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "create a new thing with failed add policy response", + thing: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "newclientwithfailedpolicy@example.com", + Secret: secret, + }, + Status: mgclients.EnabledStatus, + }, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: false}, + addPolicyErr: svcerr.ErrInvalidPolicy, + err: svcerr.ErrInvalidPolicy, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) - repoCall2 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall3 := cRepo.On("Save", context.Background(), mock.Anything).Return([]mgclients.Client{tc.client}, tc.err) - expected, err := svc.CreateThings(context.Background(), tc.token, tc.client) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authResponse, tc.authorizeErr) + repoCall2 := cRepo.On("Save", context.Background(), mock.Anything).Return([]mgclients.Client{tc.thing}, tc.saveErr) + repoCall3 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPolicyResponse, tc.addPolicyErr) + expected, err := svc.CreateThings(context.Background(), tc.token, tc.thing) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if err == nil { - tc.client.ID = expected[0].ID - tc.client.CreatedAt = expected[0].CreatedAt - tc.client.UpdatedAt = expected[0].UpdatedAt - tc.client.Credentials.Secret = expected[0].Credentials.Secret - tc.client.Owner = expected[0].Owner - tc.client.UpdatedBy = expected[0].UpdatedBy - assert.Equal(t, tc.client, expected[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected[0])) + tc.thing.ID = expected[0].ID + tc.thing.CreatedAt = expected[0].CreatedAt + tc.thing.UpdatedAt = expected[0].UpdatedAt + tc.thing.Credentials.Secret = expected[0].Credentials.Secret + tc.thing.Owner = expected[0].Owner + tc.thing.UpdatedBy = expected[0].UpdatedBy + assert.Equal(t, tc.thing, expected[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.thing, expected[0])) } repoCall.Unset() repoCall1.Unset() @@ -265,47 +336,54 @@ func TestViewClient(t *testing.T) { svc, cRepo, auth, _ := newService() cases := []struct { - desc string - token string - clientID string - response mgclients.Client - err error + desc string + token string + clientID string + response mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + authorizeErr error + retrieveErr error + err error }{ { - desc: "view client successfully", - response: client, - token: validToken, - clientID: client.ID, - err: nil, + desc: "view client successfully", + response: client, + token: validToken, + clientID: client.ID, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, }, { - desc: "view client with an invalid token", - response: mgclients.Client{}, - token: authmocks.InvalidValue, - clientID: "", - err: svcerr.ErrAuthorization, + desc: "view client with an invalid token", + response: mgclients.Client{}, + token: authmocks.InvalidValue, + clientID: "", + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { - desc: "view client with valid token and invalid client id", - response: mgclients.Client{}, - token: validToken, - clientID: wrongID, - err: svcerr.ErrNotFound, + desc: "view client with valid token and invalid client id", + response: mgclients.Client{}, + token: validToken, + clientID: wrongID, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "view client with an invalid token and invalid client id", - response: mgclients.Client{}, - token: authmocks.InvalidValue, - clientID: wrongID, - err: svcerr.ErrAuthorization, + desc: "view client with an invalid token and invalid client id", + response: mgclients.Client{}, + token: inValidToken, + clientID: wrongID, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.err) - if tc.token == authmocks.InvalidValue { - repoCall = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) - } + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) rClient, err := svc.ViewClient(context.Background(), tc.token, tc.clientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -318,276 +396,402 @@ func TestViewClient(t *testing.T) { func TestListClients(t *testing.T) { svc, cRepo, auth, _ := newService() - nClients := uint64(200) - aClients := []mgclients.Client{} - OwnerID := testsutil.GenerateUUID(t) - for i := uint64(1); i < nClients; i++ { - identity := fmt.Sprintf("TestListClients_%d@example.com", i) - client := mgclients.Client{ - Name: identity, - Credentials: mgclients.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: mgclients.Metadata{"role": "client"}, - } - if i%50 == 0 { - client.Owner = OwnerID - client.Owner = testsutil.GenerateUUID(t) - } - aClients = append(aClients, client) - } + adminID := testsutil.GenerateUUID(t) + domainID := testsutil.GenerateUUID(t) + nonAdminID := testsutil.GenerateUUID(t) + client.Permissions = []string{"read", "write"} cases := []struct { - desc string - token string - page mgclients.Page - response mgclients.ClientsPage - size uint64 - err error + desc string + userKind string + token string + page mgclients.Page + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + authorizeResponse1 *magistrala.AuthorizeRes + listObjectsResponse *magistrala.ListObjectsRes + listObjectsResponse1 *magistrala.ListObjectsRes + retrieveAllResponse mgclients.ClientsPage + listPermissionsResponse *magistrala.ListPermissionsRes + response mgclients.ClientsPage + id string + size uint64 + identifyErr error + authorizeErr error + authorizeErr1 error + listObjectsErr error + retrieveAllErr error + listPermissionsErr error + err error }{ { - desc: "list clients with authorized token", - token: validToken, - + desc: "list all clients successfully as non admin", + userKind: "non-admin", + token: validToken, + id: nonAdminID, page: mgclients.Page{ - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 0, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 0, + Total: 2, Offset: 0, - Limit: 0, + Limit: 100, }, - Clients: []mgclients.Client(nil), + Clients: []mgclients.Client{client, client}, }, - err: nil, - }, - { - desc: "list clients with an invalid token", - token: authmocks.InvalidValue, - page: mgclients.Page{ - Status: mgclients.AllStatus, + listPermissionsResponse: &magistrala.ListPermissionsRes{ + Permissions: []string{"read", "write"}, }, - size: 0, response: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 0, + Total: 2, Offset: 0, - Limit: 0, + Limit: 100, }, + Clients: []mgclients.Client{client, client}, }, - err: svcerr.ErrAuthentication, + err: nil, }, { - desc: "list clients that are shared with me", - token: validToken, + desc: "list all clients as non admin with invalid token", + userKind: "non-admin", + id: nonAdminID, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Status: mgclients.EnabledStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 4, + token: inValidToken, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { - desc: "list clients that are shared with me with a specific name", - token: validToken, + desc: "list all clients as non admin with empty domain id", + userKind: "non-admin", + id: nonAdminID, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Name: "TestListClients3", - Status: mgclients.EnabledStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 4, + token: validToken, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: ""}, + err: errors.ErrDomainAuthorization, }, { - desc: "list clients that are shared with me with an invalid name", - token: validToken, + desc: "list all clients as non admin with failed to retrieve all", + userKind: "non-admin", + token: validToken, + id: nonAdminID, page: mgclients.Page{ - Limit: nClients, - Name: "notpresentclient", - Status: mgclients.EnabledStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Limit: nClients, - }, - Clients: []mgclients.Client(nil), + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 0, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveAllResponse: mgclients.ClientsPage{}, + response: mgclients.ClientsPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients that I own", - token: validToken, + desc: "list all clients as non admin with failed to list permissions", + userKind: "non-admin", + token: validToken, + id: nonAdminID, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Status: mgclients.EnabledStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 4, + Total: 2, Offset: 0, - Limit: 0, + Limit: 100, }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Clients: []mgclients.Client{client, client}, }, - size: 4, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + response: mgclients.ClientsPage{}, + listPermissionsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients that I own with a specific name", - token: validToken, + desc: "list all clients as non admin with failed super admin", + userKind: "non-admin", + token: validToken, + id: nonAdminID, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "TestListClients3", - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeResponse1: &magistrala.AuthorizeRes{Authorized: true}, + response: mgclients.ClientsPage{}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + err: nil, + }, + { + desc: " list all clients as non admin with failed super admin and failed authorization", + userKind: "non-admin", + token: validToken, + id: nonAdminID, + page: mgclients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 4, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeResponse1: &magistrala.AuthorizeRes{Authorized: false}, + response: mgclients.ClientsPage{}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + err: svcerr.ErrAuthorization, }, { - desc: "list clients that I own with an invalid name", - token: validToken, + desc: "list all clients as non admin with failed to list objects", + userKind: "non-admin", + token: validToken, + id: nonAdminID, page: mgclients.Page{ - Limit: nClients, - Owner: myKey, - Name: "notpresentclient", - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeResponse1: &magistrala.AuthorizeRes{Authorized: true}, + response: mgclients.ClientsPage{}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + authorizeCall := auth.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + Subject: tc.identifyResponse.UserId, + Permission: authsvc.AdminPermission, + ObjectType: authsvc.PlatformType, + Object: authsvc.MagistralaObject, + }).Return(tc.authorizeResponse, tc.authorizeErr) + authorizeCall2 := auth.On("Authorize", context.Background(), &magistrala.AuthorizeReq{ + Domain: "", + SubjectType: authsvc.UserType, + SubjectKind: authsvc.UsersKind, + Subject: tc.identifyResponse.UserId, + Permission: "membership", + ObjectType: "domain", + Object: tc.identifyResponse.DomainId, + }).Return(tc.authorizeResponse1, tc.authorizeErr1) + listAllObjectsCall := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(tc.listObjectsResponse, tc.listObjectsErr) + retrieveAllCall := cRepo.On("RetrieveAllByIDs", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) + listPermissionsCall := auth.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) + + page, err := svc.ListClients(context.Background(), tc.token, tc.id, tc.page) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) + repoCall.Unset() + authorizeCall.Unset() + authorizeCall2.Unset() + listAllObjectsCall.Unset() + retrieveAllCall.Unset() + listPermissionsCall.Unset() + } + + cases2 := []struct { + desc string + userKind string + token string + page mgclients.Page + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + listObjectsResponse *magistrala.ListObjectsRes + listObjectsResponse1 *magistrala.ListObjectsRes + retrieveAllResponse mgclients.ClientsPage + listPermissionsResponse *magistrala.ListPermissionsRes + response mgclients.ClientsPage + id string + size uint64 + identifyErr error + authorizeErr error + listObjectsErr error + listObjectsErr1 error + retrieveAllErr error + listPermissionsErr error + err error + }{ + { + desc: "list all clients as admin successfully", + userKind: "admin", + id: adminID, + token: validToken, + page: mgclients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{Policies: []string{"test", "test"}}, + listObjectsResponse1: &magistrala.ListObjectsRes{Policies: []string{"test", "test"}}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 0, + Total: 2, Offset: 0, - Limit: nClients, + Limit: 100, }, - Clients: []mgclients.Client(nil), + Clients: []mgclients.Client{client, client}, }, - size: 0, - }, - { - desc: "list clients that I own and are shared with me", - token: validToken, - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Status: mgclients.AllStatus, + listPermissionsResponse: &magistrala.ListPermissionsRes{ + Permissions: []string{"read", "write"}, }, response: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 4, + Total: 2, Offset: 0, - Limit: 0, + Limit: 100, }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Clients: []mgclients.Client{client, client}, }, - size: 4, + err: nil, }, { - desc: "list clients that I own and are shared with me with a specific name", - token: validToken, + desc: "list all clients as admin with unauthorized user", + userKind: "admin", + id: adminID, + token: validToken, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "TestListClients3", - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: svcerr.ErrAuthorization, + }, + { + desc: "list all clients as admin with failed to retrieve all", + userKind: "admin", + id: adminID, + token: validToken, + page: mgclients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: 4, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsResponse1: &magistrala.ListObjectsRes{}, + retrieveAllResponse: mgclients.ClientsPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients that I own and are shared with me with an invalid name", - token: validToken, + desc: "list all clients as admin with failed to list permissions", + userKind: "admin", + id: adminID, + token: validToken, page: mgclients.Page{ - Limit: nClients, - Owner: myKey, - Name: "notpresentclient", - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsResponse1: &magistrala.ListObjectsRes{}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 0, + Total: 2, Offset: 0, - Limit: nClients, + Limit: 100, }, - Clients: []mgclients.Client(nil), + Clients: []mgclients.Client{client, client}, }, - size: 0, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + listPermissionsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients with offset and limit", - token: validToken, - + desc: "list all clients as admin with failed to list clients", + userKind: "admin", + id: adminID, + token: validToken, page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Status: mgclients.AllStatus, + Offset: 0, + Limit: 100, + ListPerms: true, }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: nClients - 6, - Offset: 0, - Limit: 0, - }, - Clients: aClients[6:nClients], + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsResponse1: &magistrala.ListObjectsRes{}, + listObjectsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list all clients as admin with failed to list things", + userKind: "admin", + id: adminID, + token: validToken, + page: mgclients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, }, - size: nClients - 6, + identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: domainID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsResponse1: &magistrala.ListObjectsRes{}, + listObjectsErr1: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, } - for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: getIDs(tc.response.Clients)}, nil) - if tc.token == authmocks.InvalidValue { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: authmocks.InvalidValue}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) - repoCall2 = auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{}, svcerr.ErrAuthorization) - } - repoCall3 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, tc.err) - page, err := svc.ListClients(context.Background(), tc.token, "", tc.page) + for _, tc := range cases2 { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + authorizeCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + listAllObjectsCall := auth.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: authsvc.UserType, + Subject: tc.identifyResponse.DomainId + "_" + adminID, + Permission: "", + ObjectType: authsvc.ThingType, + }).Return(tc.listObjectsResponse, tc.listObjectsErr) + listAllObjectsCall2 := auth.On("ListAllObjects", context.Background(), &magistrala.ListObjectsReq{ + SubjectType: authsvc.UserType, + Subject: tc.identifyResponse.Id, + Permission: "", + ObjectType: authsvc.ThingType, + }).Return(tc.listObjectsResponse1, tc.listObjectsErr1) + retrieveAllCall := cRepo.On("RetrieveAllByIDs", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) + listPermissionsCall := auth.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) + + page, err := svc.ListClients(context.Background(), tc.token, tc.id, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() + authorizeCall.Unset() + listAllObjectsCall.Unset() + listAllObjectsCall2.Unset() + retrieveAllCall.Unset() + listPermissionsCall.Unset() } } @@ -600,25 +804,30 @@ func TestUpdateClient(t *testing.T) { client2.Metadata = mgclients.Metadata{"role": "test"} cases := []struct { - desc string - client mgclients.Client - response mgclients.Client - token string - err error + desc string + client mgclients.Client + updateResponse mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + authorizeErr error + updateErr error + token string + err error }{ { - desc: "update client name with valid token", - client: client1, - response: client1, - token: validToken, - err: nil, + desc: "update client name successfully", + client: client1, + updateResponse: client1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + token: validToken, + err: nil, }, { - desc: "update client name with invalid token", - client: client1, - response: mgclients.Client{}, - token: "non-existent", - err: svcerr.ErrAuthentication, + desc: "update client name with invalid token", + client: client1, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: inValidToken, + err: svcerr.ErrAuthorization, }, { desc: "update client name with invalid ID", @@ -626,38 +835,58 @@ func TestUpdateClient(t *testing.T) { ID: wrongID, Name: "Updated Client", }, - response: mgclients.Client{}, - token: validToken, - err: svcerr.ErrNotFound, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: svcerr.ErrAuthorization, }, { - desc: "update client metadata with valid token", - client: client2, - response: client2, - token: validToken, - err: nil, + desc: "update client metadata with valid token", + client: client2, + updateResponse: client2, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + token: validToken, + err: nil, + }, + { + desc: "update client metadata with invalid token", + client: client2, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + token: inValidToken, + err: svcerr.ErrAuthorization, }, { - desc: "update client metadata with invalid token", - client: client2, - response: mgclients.Client{}, - token: "non-existent", - err: svcerr.ErrAuthentication, + desc: "update client metadata with invalid ID", + client: mgclients.Client{ + ID: wrongID, + Metadata: mgclients.Metadata{"role": "test"}, + }, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: svcerr.ErrAuthorization, + }, + { + desc: "update client with failed to update repo", + client: client1, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateErr: repoerr.ErrMalformedEntity, + token: validToken, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(mgclients.Client{}, tc.err) - repoCall3 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) updatedClient, err := svc.UpdateClient(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedClient)) repoCall.Unset() repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() } } @@ -667,50 +896,63 @@ func TestUpdateClientTags(t *testing.T) { client.Tags = []string{"updated"} cases := []struct { - desc string - client mgclients.Client - response mgclients.Client - token string - err error + desc string + client mgclients.Client + updateResponse mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + authorizeErr error + updateErr error + token string + err error }{ { - desc: "update client tags with valid token", - client: client, - token: validToken, - response: client, - err: nil, + desc: "update client tags successfully", + client: client, + updateResponse: client, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + token: validToken, + err: nil, }, { - desc: "update client tags with invalid token", - client: client, - token: "non-existent", - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, + desc: "update client tags with invalid token", + client: client, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + token: inValidToken, + err: svcerr.ErrAuthorization, }, { - desc: "update client name with invalid ID", + desc: "update client tags with invalid ID", client: mgclients.Client{ ID: wrongID, Name: "Updated name", }, - response: mgclients.Client{}, - token: validToken, - err: svcerr.ErrNotFound, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + token: validToken, + err: svcerr.ErrAuthorization, + }, + { + desc: "update client tags with failed to update repo", + client: client, + updateResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateErr: repoerr.ErrMalformedEntity, + token: validToken, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(mgclients.Client{}, tc.err) - repoCall3 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall1 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) updatedClient, err := svc.UpdateClientTags(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedClient)) repoCall.Unset() repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() } } @@ -718,41 +960,73 @@ func TestUpdateClientSecret(t *testing.T) { svc, cRepo, auth, _ := newService() cases := []struct { - desc string - id string - newSecret string - token string - response mgclients.Client - err error + desc string + client mgclients.Client + newSecret string + updateSecretResponse mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + token string + updateErr error + authorizeErr error + err error }{ { - desc: "update client secret with valid token", - id: client.ID, + desc: "update client secret successfully", + client: client, newSecret: "newSecret", - token: validToken, - response: client, - err: nil, + updateSecretResponse: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: client.Credentials.Identity, + Secret: "newSecret", + }, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + token: validToken, + err: nil, + }, + { + desc: "update client secret with invalid token", + client: client, + newSecret: "newSecret", + updateSecretResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: inValidToken, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { - desc: "update client secret with invalid token", - id: client.ID, - newSecret: "newPassword", - token: "non-existent", - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, + desc: "update client secret with invalid ID", + client: mgclients.Client{ + ID: wrongID, + }, + newSecret: "newSecret", + updateSecretResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "update client secret with failed to update repo", + client: client, + newSecret: "newSecret", + updateSecretResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateErr: repoerr.ErrMalformedEntity, + token: validToken, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClientSecret(context.Background(), tc.token, tc.id, tc.newSecret) + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall1 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateErr) + updatedClient, err := svc.UpdateClientSecret(context.Background(), tc.token, tc.client.ID, tc.newSecret) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateSecretResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateSecretResponse, updatedClient)) repoCall.Unset() repoCall1.Unset() - repoCall2.Unset() } } @@ -765,50 +1039,83 @@ func TestEnableClient(t *testing.T) { endisabledClient1.Status = mgclients.EnabledStatus cases := []struct { - desc string - id string - token string - client mgclients.Client - response mgclients.Client - err error + desc string + id string + token string + client mgclients.Client + changeStatusResponse mgclients.Client + retrieveByIDResponse mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + changeStatusErr error + retrieveIDErr error + authorizeErr error + err error }{ { - desc: "enable disabled client", - id: disabledClient1.ID, - token: validToken, - client: disabledClient1, - response: endisabledClient1, - err: nil, + desc: "enable disabled client", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + changeStatusResponse: endisabledClient1, + retrieveByIDResponse: disabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, }, { - desc: "enable enabled client", - id: enabledClient1.ID, - token: validToken, - client: enabledClient1, - response: enabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, + desc: "enable disabled client with failed to update repo", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: disabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, { - desc: "enable non-existing client", - id: wrongID, - token: validToken, - client: mgclients.Client{}, - response: mgclients.Client{}, - err: repoerr.ErrNotFound, + desc: "enable enabled client", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + changeStatusResponse: enabledClient1, + retrieveByIDResponse: enabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + changeStatusErr: mgclients.ErrStatusAlreadyAssigned, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "enable non-existing client", + id: wrongID, + token: validToken, + client: mgclients.Client{}, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "enable client with invalid token", + id: enabledClient1.ID, + token: inValidToken, + client: enabledClient1, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.client, tc.err) - repoCall3 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) + repoCall2 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) _, err := svc.EnableClient(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() repoCall1.Unset() repoCall2.Unset() - repoCall3.Unset() } cases2 := []struct { @@ -888,52 +1195,97 @@ func TestDisableClient(t *testing.T) { disenabledClient1.Status = mgclients.DisabledStatus cases := []struct { - desc string - id string - token string - client mgclients.Client - response mgclients.Client - err error + desc string + id string + token string + client mgclients.Client + changeStatusResponse mgclients.Client + retrieveByIDResponse mgclients.Client + authorizeResponse *magistrala.AuthorizeRes + changeStatusErr error + retrieveIDErr error + authorizeErr error + removeErr error + err error }{ { - desc: "disable enabled client", - id: enabledClient1.ID, - token: validToken, - client: enabledClient1, - response: disenabledClient1, - err: nil, + desc: "disable enabled client", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + changeStatusResponse: disenabledClient1, + retrieveByIDResponse: enabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, }, { - desc: "disable disabled client", - id: disabledClient1.ID, - token: validToken, - client: disabledClient1, - response: mgclients.Client{}, - err: mgclients.ErrStatusAlreadyAssigned, + desc: "disable client with failed to update repo", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: enabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, { - desc: "disable non-existing client", - id: wrongID, - client: mgclients.Client{}, - token: validToken, - response: mgclients.Client{}, - err: repoerr.ErrNotFound, + desc: "disable disabled client", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: disabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + changeStatusErr: mgclients.ErrStatusAlreadyAssigned, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "disable non-existing client", + id: wrongID, + client: mgclients.Client{}, + token: validToken, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "disable client with invalid token", + id: disabledClient1.ID, + token: inValidToken, + client: disabledClient1, + changeStatusResponse: mgclients.Client{}, + retrieveByIDResponse: mgclients.Client{}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "disable client with failed to remove from cache", + id: enabledClient1.ID, + token: validToken, + client: disabledClient1, + changeStatusResponse: disenabledClient1, + retrieveByIDResponse: enabledClient1, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + removeErr: svcerr.ErrRemoveEntity, + err: svcerr.ErrRemoveEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.client, tc.err) - repoCall3 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.response, tc.err) - repoCall4 := cache.On("Remove", mock.Anything, mock.Anything).Return(nil) + repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) + repoCall2 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) + repoCall3 := cache.On("Remove", mock.Anything, mock.Anything).Return(tc.removeErr) _, err := svc.DisableClient(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() repoCall1.Unset() repoCall2.Unset() repoCall3.Unset() - repoCall4.Unset() } cases2 := []struct { @@ -1027,22 +1379,45 @@ func TestListMembers(t *testing.T) { } aClients = append(aClients, client) } + aClients[0].Permissions = []string{"admin"} cases := []struct { - desc string - token string - groupID string - page mgclients.Page - response mgclients.MembersPage - err error + desc string + token string + groupID string + page mgclients.Page + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + listObjectsResponse *magistrala.ListObjectsRes + listPermissionsResponse *magistrala.ListPermissionsRes + retreiveAllByIDsResponse mgclients.ClientsPage + response mgclients.MembersPage + identifyErr error + authorizeErr error + listObjectsErr error + listPermissionsErr error + retreiveAllByIDsErr error + err error }{ { - desc: "list clients with authorized token", + desc: "list members with authorized token", token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ Owner: adminEmail, }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + retreiveAllByIDsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 0, + }, + Clients: []mgclients.Client{}, + }, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 0, @@ -1054,7 +1429,7 @@ func TestListMembers(t *testing.T) { err: nil, }, { - desc: "list clients with offset and limit", + desc: "list members with offset and limit", token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ @@ -1063,20 +1438,32 @@ func TestListMembers(t *testing.T) { Status: mgclients.AllStatus, Owner: adminEmail, }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + retreiveAllByIDsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: nClients - 6 - 1, + }, + Clients: aClients[6 : nClients-1], + }, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: nClients - 6 - 1, }, Members: aClients[6 : nClients-1], }, + err: nil, }, { - desc: "list clients with an invalid token", + desc: "list members with an invalid token", token: authmocks.InvalidValue, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ Owner: adminEmail, }, + identifyResponse: &magistrala.IdentityRes{}, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 0, @@ -1084,15 +1471,21 @@ func TestListMembers(t *testing.T) { Limit: 0, }, }, - err: svcerr.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { - desc: "list clients with an invalid id", + desc: "list members with an invalid id", token: validToken, groupID: wrongID, page: mgclients.Page{ Owner: adminEmail, }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + retreiveAllByIDsResponse: mgclients.ClientsPage{}, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 0, @@ -1100,15 +1493,26 @@ func TestListMembers(t *testing.T) { Limit: 0, }, }, - err: svcerr.ErrNotFound, + retreiveAllByIDsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients for an owner", + desc: "list members for an owner", token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ Owner: adminEmail, }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + retreiveAllByIDsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + }, + Clients: []mgclients.Client{aClients[0], aClients[3], aClients[6], aClients[9]}, + }, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 4, @@ -1117,13 +1521,89 @@ func TestListMembers(t *testing.T) { }, err: nil, }, + { + desc: "list members for an owner with permissions", + token: validToken, + groupID: testsutil.GenerateUUID(t), + page: mgclients.Page{ + Owner: adminEmail, + ListPerms: true, + }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{Permissions: []string{"admin"}}, + retreiveAllByIDsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{aClients[0]}, + }, + response: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + }, + Members: []mgclients.Client{aClients[0]}, + }, + err: nil, + }, + { + desc: "list members with unauthorized user", + token: validToken, + groupID: testsutil.GenerateUUID(t), + page: mgclients.Page{ + Owner: adminEmail, + ListPerms: true, + }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "list members with failed to list objects", + token: validToken, + groupID: testsutil.GenerateUUID(t), + page: mgclients.Page{ + Owner: adminEmail, + ListPerms: true, + }, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listObjectsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list members with failed to list permissions", + token: validToken, + groupID: testsutil.GenerateUUID(t), + page: mgclients.Page{ + Owner: adminEmail, + ListPerms: true, + }, + retreiveAllByIDsResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{aClients[0]}, + }, + response: mgclients.MembersPage{}, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listObjectsResponse: &magistrala.ListObjectsRes{}, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + listPermissionsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{}, nil) - repoCall3 := cRepo.On("RetrieveAllByIDs", context.Background(), tc.page).Return(mgclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(tc.listObjectsResponse, tc.listObjectsErr) + repoCall3 := cRepo.On("RetrieveAllByIDs", context.Background(), tc.page).Return(tc.retreiveAllByIDsResponse, tc.retreiveAllByIDsErr) + repoCall4 := auth.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) page, err := svc.ListClientsByGroup(context.Background(), tc.token, tc.groupID, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) @@ -1131,6 +1611,7 @@ func TestListMembers(t *testing.T) { repoCall1.Unset() repoCall2.Unset() repoCall3.Unset() + repoCall4.Unset() } } @@ -1150,57 +1631,126 @@ func TestDeleteClient(t *testing.T) { invalidClientID := "invalidClientID" _ = invalidClientID cases := []struct { - desc string - token string - clientID string - err error + desc string + token string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + deletePolicyResponse *magistrala.DeletePolicyRes + deletePolicyResponse1 *magistrala.DeletePolicyRes + deletePolicyResponse2 *magistrala.DeletePolicyRes + clientID string + identifyErr error + authorizeErr error + removeErr error + deleteErr error + deletePolicyErr error + deletePolicyErr1 error + deletePolicyErr2 error + err error }{ { - desc: "Delete client with authorized token", - token: validToken, - clientID: client.ID, - err: nil, + desc: "Delete client with authorized token", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse1: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse2: &magistrala.DeletePolicyRes{Deleted: true}, + err: nil, }, { - desc: "Delete client with unauthorized token", - token: authmocks.InvalidValue, - clientID: client.ID, - err: errors.ErrAuthentication, + desc: "Delete client with unauthorized token", + token: authmocks.InvalidValue, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: errors.ErrAuthentication, }, { - desc: "Delete invalid client", - token: validToken, - clientID: authmocks.InvalidValue, - err: errors.ErrAuthorization, + desc: "Delete invalid client", + token: validToken, + clientID: authmocks.InvalidValue, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: errors.ErrAuthorization, }, { - desc: "Delete repo error ", - token: validToken, - clientID: client.ID, - err: errors.ErrRemoveEntity, + desc: "Delete client with repo error ", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse1: &magistrala.DeletePolicyRes{Deleted: true}, + deleteErr: errors.ErrRemoveEntity, + err: errors.ErrRemoveEntity, }, { - desc: "Delete policy error ", - token: validToken, - clientID: client.ID, - err: errors.ErrUnidentified, + desc: "Delete client with cache error ", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + removeErr: svcerr.ErrRemoveEntity, + err: errors.ErrRemoveEntity, + }, + { + desc: "Delete client with failed to delete groups policy", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, + deletePolicyErr: errRemovePolicies, + err: errRemovePolicies, + }, + { + desc: "Delete client with failed to delete domains policy", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse1: &magistrala.DeletePolicyRes{Deleted: false}, + deletePolicyErr1: errRemovePolicies, + err: errRemovePolicies, + }, + { + desc: "Delete client with failed to delete users policy", + token: validToken, + clientID: client.ID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse1: &magistrala.DeletePolicyRes{Deleted: true}, + deletePolicyResponse2: &magistrala.DeletePolicyRes{Deleted: false}, + deletePolicyErr2: errRemovePolicies, + err: errRemovePolicies, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) - repoCall3 := cache.On("Remove", mock.Anything, tc.clientID).Return(nil) - repoCall4 := cRepo.On("Delete", context.Background(), tc.clientID).Return(nil) - if tc.err == errors.ErrRemoveEntity { - repoCall4.Unset() - repoCall4 = cRepo.On("Delete", context.Background(), tc.clientID).Return(errors.ErrRemoveEntity) - } - if tc.err == errors.ErrUnidentified { - repoCall2.Unset() - repoCall2 = auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: false}, errors.ErrUnidentified) - } + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cache.On("Remove", mock.Anything, tc.clientID).Return(tc.removeErr) + repoCall3 := auth.On("DeletePolicy", context.Background(), &magistrala.DeletePolicyReq{ + SubjectType: authsvc.GroupType, + Object: tc.clientID, + ObjectType: authsvc.ThingType, + }).Return(tc.deletePolicyResponse, tc.deletePolicyErr) + repoCall4 := auth.On("DeletePolicy", mock.Anything, &magistrala.DeletePolicyReq{ + SubjectType: authsvc.DomainType, + Object: tc.clientID, + ObjectType: authsvc.ThingType, + }).Return(tc.deletePolicyResponse1, tc.deletePolicyErr1) + repoCall5 := cRepo.On("Delete", context.Background(), tc.clientID).Return(tc.deleteErr) + repoCall6 := auth.On("DeletePolicy", mock.Anything, &magistrala.DeletePolicyReq{ + SubjectType: authsvc.UserType, + Object: tc.clientID, + ObjectType: authsvc.ThingType, + }).Return(tc.deletePolicyResponse2, tc.deletePolicyErr2) err := svc.DeleteClient(context.Background(), tc.token, tc.clientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() @@ -1208,6 +1758,359 @@ func TestDeleteClient(t *testing.T) { repoCall2.Unset() repoCall3.Unset() repoCall4.Unset() + repoCall5.Unset() + repoCall6.Unset() + } +} + +func TestShare(t *testing.T) { + svc, _, auth, _ := newService() + + clientID := "clientID" + + cases := []struct { + desc string + token string + clientID string + relation string + userID string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + addPoliciesResponse *magistrala.AddPoliciesRes + identifyErr error + authorizeErr error + addPoliciesErr error + err error + }{ + { + desc: "share thing successfully", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, + }, + { + desc: "share thing with invalid token", + token: inValidToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "share thing with invalid ID", + token: validToken, + clientID: invalid, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "share thing with failed to add policies", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{}, + addPoliciesErr: svcerr.ErrInvalidPolicy, + err: errAddPolicies, + }, + { + desc: "share thing with failed authorization from add policies", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: false}, + err: nil, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesErr) + err := svc.Share(context.Background(), tc.token, tc.clientID, tc.relation, tc.userID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestUnShare(t *testing.T) { + svc, _, auth, _ := newService() + + clientID := "clientID" + + cases := []struct { + desc string + token string + clientID string + relation string + userID string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + deletePoliciesResponse *magistrala.DeletePoliciesRes + identifyErr error + authorizeErr error + deletePoliciesErr error + err error + }{ + { + desc: "unshare thing successfully", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: nil, + }, + { + desc: "unshare thing with invalid token", + token: inValidToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "unshare thing with invalid ID", + token: validToken, + clientID: invalid, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "share thing with failed to delete policies", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{}, + deletePoliciesErr: svcerr.ErrInvalidPolicy, + err: errRemovePolicies, + }, + { + desc: "share thing with failed delete from delete policies", + token: validToken, + clientID: clientID, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, + err: nil, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesErr) + err := svc.Unshare(context.Background(), tc.token, tc.clientID, tc.relation, tc.userID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestViewClientPerms(t *testing.T) { + svc, _, auth, _ := newService() + + validID := valid + + cases := []struct { + desc string + token string + thingID string + permissions []string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + listPermResponse *magistrala.ListPermissionsRes + identifyErr error + authorizeErr error + listPermErr error + err error + }{ + { + desc: "view client permissions successfully", + token: validToken, + thingID: validID, + permissions: []string{"admin"}, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listPermResponse: &magistrala.ListPermissionsRes{Permissions: []string{"admin"}}, + err: nil, + }, + { + desc: "view client permissions with invalid token", + token: inValidToken, + thingID: validID, + permissions: []string{"admin"}, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "view client permissions with invalid ID", + token: validToken, + thingID: inValidToken, + permissions: []string{}, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + authorizeErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "view permissions with failed retrieve list permissions response", + token: validToken, + thingID: validID, + permissions: []string{}, + identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listPermResponse: &magistrala.ListPermissionsRes{}, + listPermErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := auth.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermResponse, tc.listPermErr) + _, err := svc.ViewClientPerms(context.Background(), tc.token, tc.thingID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestIdentify(t *testing.T) { + svc, cRepo, _, cache := newService() + + valid := valid + + cases := []struct { + desc string + key string + cacheIDResponse string + cacheIDErr error + repoIDResponse mgclients.Client + retrieveBySecretErr error + saveErr error + err error + }{ + { + desc: "identify client with valid key from cache", + key: valid, + cacheIDResponse: client.ID, + err: nil, + }, + { + desc: "identify client with valid key from repo", + key: valid, + cacheIDResponse: "", + cacheIDErr: errors.ErrNotFound, + repoIDResponse: client, + err: nil, + }, + { + desc: "identify client with invalid key", + key: invalid, + cacheIDResponse: "", + cacheIDErr: errors.ErrNotFound, + repoIDResponse: mgclients.Client{}, + retrieveBySecretErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + { + desc: "identify client with failed to save to cache", + key: valid, + cacheIDResponse: "", + cacheIDErr: errors.ErrNotFound, + repoIDResponse: client, + saveErr: errors.ErrMalformedEntity, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := cache.On("ID", mock.Anything, tc.key).Return(tc.cacheIDResponse, tc.cacheIDErr) + repoCall1 := cRepo.On("RetrieveBySecret", mock.Anything, mock.Anything).Return(tc.repoIDResponse, tc.retrieveBySecretErr) + repoCall2 := cache.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(tc.saveErr) + _, err := svc.Identify(context.Background(), tc.key) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestAuthorize(t *testing.T) { + svc, cRepo, auth, cache := newService() + + cases := []struct { + desc string + token string + key string + clientID string + request *magistrala.AuthorizeReq + response *magistrala.AuthorizeRes + err error + identifyErr error + authErr error + }{ + { + desc: "authorize client with valid key", + key: valid, + clientID: valid, + request: &magistrala.AuthorizeReq{Subject: valid, Object: valid, Permission: "admin"}, + response: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, + }, + { + desc: "authorize client with invalid key", + key: invalid, + request: &magistrala.AuthorizeReq{Subject: invalid, Object: inValidToken, Permission: "admin"}, + response: &magistrala.AuthorizeRes{Authorized: false}, + identifyErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + { + desc: "authorize with invalid token", + key: valid, + request: &magistrala.AuthorizeReq{Subject: valid, Object: inValidToken, Permission: "admin"}, + response: &magistrala.AuthorizeRes{Authorized: false}, + authErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthorization, + }, + { + desc: "authorize with valid failed authorize response", + key: valid, + request: &magistrala.AuthorizeReq{Subject: valid, Object: valid, Permission: "view"}, + response: &magistrala.AuthorizeRes{Authorized: false}, + authErr: nil, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall := cache.On("ID", mock.Anything, mock.Anything).Return(mock.Anything, tc.identifyErr) + repoCall1 := cRepo.On("RetrieveBySecret", mock.Anything, mock.Anything).Return(mgclients.Client{ID: tc.clientID}, tc.identifyErr) + repoCall2 := cache.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(nil) + repoCall3 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.response, tc.authErr) + _, err := svc.Authorize(context.Background(), tc.request) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() } } From e89cd592c690ec7d77442144dca45868363493ff Mon Sep 17 00:00:00 2001 From: Felix Gateru Date: Thu, 18 Jan 2024 19:14:46 +0300 Subject: [PATCH 19/71] NOISSUE - Improve tests in users service (#194) Signed-off-by: felix.gateru --- api/openapi/users.yml | 18 - users/api/clients.go | 28 +- users/api/endpoint_test.go | 3097 ++++++++++++++++++++++++++++++++ users/api/requests.go | 30 +- users/api/requests_test.go | 853 +++++++++ users/postgres/clients.go | 42 +- users/postgres/clients_test.go | 566 ++++++ users/service_test.go | 2558 +++++++++++++++++--------- 8 files changed, 6297 insertions(+), 895 deletions(-) create mode 100644 users/api/endpoint_test.go create mode 100644 users/api/requests_test.go diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 8aac147eba..6cb178605f 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -1458,15 +1458,6 @@ components: required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Visibility: - name: visibility - description: The visibility specifier when listing users. Either all, shared or mine. - in: path - schema: - type: string - required: true - example: all - UserName: name: name description: User's name. @@ -1505,15 +1496,6 @@ components: required: false example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - UserVisibility: - name: visibility - description: visibility to list either users I own or users that are shared with me or both users I own and shared with me - in: query - schema: - type: string - required: false - example: shared - Status: name: status description: User account status. diff --git a/users/api/clients.go b/users/api/clients.go index c3b45a490e..0f9075c3e5 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -189,7 +189,6 @@ func decodeViewProfile(_ context.Context, r *http.Request) (interface{}, error) } func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { - var sharedID, ownerID string s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -222,10 +221,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, err } - visibility, err := apiutil.ReadStringQuery(r, api.VisibilityKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -234,18 +229,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - switch visibility { - case api.MyVisibility: - ownerID = api.MyVisibility - case api.SharedVisibility: - sharedID = api.MyVisibility - case api.AllVisibility: - sharedID = api.MyVisibility - ownerID = api.MyVisibility - } - if oid != "" { - ownerID = oid - } st, err := mgclients.ToStatus(s) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -259,8 +242,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) name: n, identity: i, tag: t, - sharedBy: sharedID, - owner: ownerID, + owner: oid, order: order, dir: dir, } @@ -473,13 +455,7 @@ func decodeListMembersByDomain(_ context.Context, r *http.Request) (interface{}, if err != nil { return nil, err } - // For domains default permission in membership, In "queryPageParams" default is view, - // so overwriting the permission given by queryPageParams function with default membership permission. - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, auth.MembershipPermission) - if err != nil { - return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - page.Permission = p + req := listMembersByObjectReq{ token: apiutil.ExtractBearerToken(r), Page: page, diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go new file mode 100644 index 0000000000..4a0d4d663c --- /dev/null +++ b/users/api/endpoint_test.go @@ -0,0 +1,3097 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api_test + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/absmach/magistrala" + authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/internal/testsutil" + mglog "github.com/absmach/magistrala/logger" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + "github.com/absmach/magistrala/pkg/uuid" + httpapi "github.com/absmach/magistrala/users/api" + "github.com/absmach/magistrala/users/mocks" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + idProvider = uuid.New() + secret = "strongsecret" + validCMetadata = mgclients.Metadata{"role": "client"} + client = mgclients.Client{ + ID: testsutil.GenerateUUID(&testing.T{}), + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: mgclients.Credentials{Identity: "clientidentity@example.com", Secret: secret}, + Metadata: validCMetadata, + Status: mgclients.EnabledStatus, + } + validToken = "valid" + inValidToken = "invalid" + inValid = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + ErrPasswordFormat = errors.New("password does not meet the requirements") +) + +const contentType = "application/json" + +type testRequest struct { + client *http.Client + method string + url string + contentType string + token string + body io.Reader +} + +func (tr testRequest) make() (*http.Response, error) { + req, err := http.NewRequest(tr.method, tr.url, tr.body) + if err != nil { + return nil, err + } + + if tr.token != "" { + req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) + } + + if tr.contentType != "" { + req.Header.Set("Content-Type", tr.contentType) + } + + req.Header.Set("Referer", "http://localhost") + + return tr.client.Do(req) +} + +func newUsersServer() (*httptest.Server, *mocks.Service) { + gRepo := new(gmocks.Repository) + auth := new(authmocks.Service) + + svc := new(mocks.Service) + gsvc := groups.NewService(gRepo, idProvider, auth) + + logger := mglog.NewMock() + mux := chi.NewRouter() + httpapi.MakeHandler(svc, gsvc, mux, logger, "") + + return httptest.NewServer(mux), svc +} + +func toJSON(data interface{}) string { + jsonData, err := json.Marshal(data) + if err != nil { + return "" + } + return string(jsonData) +} + +func TestRegisterClient(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + client mgclients.Client + token string + contentType string + status int + err error + }{ + { + desc: "register a new user with a valid token", + client: client, + token: validToken, + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "register an existing user", + client: client, + token: validToken, + contentType: contentType, + status: http.StatusConflict, + err: errors.ErrConflict, + }, + { + desc: "register a new user with an empty token", + client: client, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "register a user with an invalid ID", + client: mgclients.Client{ + ID: inValid, + Credentials: mgclients.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "register a user that can't be marshalled", + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "register user with invalid status", + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "newclientwithinvalidstatus@example.com", + Secret: secret, + }, + Status: mgclients.AllStatus, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "register a user with name too long", + client: mgclients.Client{ + Name: strings.Repeat("a", 1025), + Credentials: mgclients.Credentials{ + Identity: "newclientwithinvalidname@example.com", + Secret: secret, + }, + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "register user with invalid content type", + client: client, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "register user with empty request body", + client: mgclients.Client{}, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/", us.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(data), + } + + repoCall := svc.On("RegisterClient", mock.Anything, tc.token, tc.client).Return(tc.client, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestViewClient(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + id string + status int + err error + }{ + { + desc: "view user with valid token", + token: validToken, + id: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "view user with invalid token", + token: inValidToken, + id: client.ID, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "view user with empty token", + token: "", + id: client.ID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/users/%s", us.URL, tc.id), + token: tc.token, + } + + repoCall := svc.On("ViewClient", mock.Anything, tc.token, tc.id).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestViewProfile(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + id string + status int + err error + }{ + { + desc: "view profile with valid token", + token: validToken, + id: client.ID, + status: http.StatusOK, + err: nil, + }, + { + desc: "view profile with invalid token", + token: inValidToken, + id: client.ID, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "view profile with empty token", + token: "", + id: client.ID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/users/profile", us.URL), + token: tc.token, + } + + repoCall := svc.On("ViewProfile", mock.Anything, tc.token).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestListClients(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + query string + token string + listUsersResponse mgclients.ClientsPage + status int + err error + }{ + { + desc: "list users with valid token", + token: validToken, + status: http.StatusOK, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list users with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list users with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users with offset", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit greater than max", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with owner_id", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with duplicate owner_id", + token: validToken, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid owner_id", + token: validToken, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with name", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid name", + token: validToken, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with status", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with tags", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid tags", + token: validToken, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with metadata", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with permissions", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid permissions", + token: validToken, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with list perms", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "list_perms=true", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid list perms", + token: validToken, + query: "list_perms=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate list perms", + token: validToken, + query: "list_perms=true&list_perms=true", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with identity", + token: validToken, + query: fmt.Sprintf("identity=%s", client.Credentials.Identity), + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid identity", + token: validToken, + query: "identity=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate identity", + token: validToken, + query: "identity=1&identity=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with order", + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + token: validToken, + query: "order=name", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid order", + token: validToken, + query: "order=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate order", + token: validToken, + query: "order=name&order=name", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid order direction", + token: validToken, + query: "dir=invalid", + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate order direction", + token: validToken, + query: "dir=asc&dir=asc", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: us.URL + "/users?" + tc.query, + contentType: contentType, + token: tc.token, + } + + repoCall := svc.On("ListClients", mock.Anything, tc.token, mock.Anything, mock.Anything).Return(tc.listUsersResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var bodyRes respBody + err = json.NewDecoder(res.Body).Decode(&bodyRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if bodyRes.Err != "" || bodyRes.Message != "" { + err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestUpdateClient(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + newName := "newname" + newMetadata := mgclients.Metadata{"newkey": "newvalue"} + + cases := []struct { + desc string + id string + data string + clientResponse mgclients.Client + token string + contentType string + status int + err error + }{ + { + desc: "update user with valid token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + clientResponse: mgclients.Client{ + ID: client.ID, + Name: newName, + Metadata: newMetadata, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "update user with invalid token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update user with empty token", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update user with invalid id", + id: inValid, + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "update user with invalid contentype", + id: client.ID, + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update user with malformed data", + id: client.ID, + data: fmt.Sprintf(`{"name":%s}`, "invalid"), + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "update user with empty id", + id: " ", + data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/users/%s", us.URL, tc.id), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + repoCall := svc.On("UpdateClient", mock.Anything, tc.token, mock.Anything).Return(tc.clientResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestUpdateClientTags(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + newTag := "newtag" + + cases := []struct { + desc string + id string + data string + contentType string + clientResponse mgclients.Client + token string + status int + err error + }{ + { + desc: "update user tags with valid token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + clientResponse: mgclients.Client{ + ID: client.ID, + Tags: []string{newTag}, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "update user tags with empty token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update user tags with invalid token", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update user tags with invalid id", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "update user tags with invalid contentype", + id: client.ID, + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update user tags with empty id", + id: "", + data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "update user with malfomed data", + id: client.ID, + data: fmt.Sprintf(`{"tags":%s}`, newTag), + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/users/%s/tags", us.URL, tc.id), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("UpdateClientTags", mock.Anything, tc.token, mock.Anything).Return(tc.clientResponse, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + if err == nil { + assert.Equal(t, tc.clientResponse.Tags, resBody.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.clientResponse.Tags, resBody.Tags)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestUpdateClientIdentity(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + data string + client mgclients.Client + contentType string + token string + status int + err error + }{ + { + desc: "update user identity with valid token", + data: fmt.Sprintf(`{"identity": "%s"}`, "newclientidentity@example.com"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "newclientidentity@example.com", + Secret: "secret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "update user identity with empty token", + data: fmt.Sprintf(`{"identity": "%s"}`, "newclientidentity@example.com"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "newclientidentity@example.com", + Secret: "secret", + }, + }, + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update user identity with invalid token", + data: fmt.Sprintf(`{"identity": "%s"}`, "newclientidentity@example.com"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "newclientidentity@example.com", + Secret: "secret", + }, + }, + contentType: contentType, + token: inValid, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update user identity with empty id", + data: fmt.Sprintf(`{"identity": "%s"}`, "newclientidentity@example.com"), + client: mgclients.Client{ + ID: "", + Credentials: mgclients.Credentials{ + Identity: "newclientidentity@example.com", + Secret: "secret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "update user identity with invalid contentype", + data: fmt.Sprintf(`{"identity": "%s"}`, ""), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "newclientidentity@example.com", + Secret: "secret", + }, + }, + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update user identity with malformed data", + data: fmt.Sprintf(`{"identity": %s}`, "invalid"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "", + Secret: "secret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/users/%s/identity", us.URL, tc.client.ID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("UpdateClientIdentity", mock.Anything, tc.token, mock.Anything, mock.Anything).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestPasswordResetRequest(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + testemail := "test@example.com" + testhost := "example.com" + + cases := []struct { + desc string + data string + contentType string + status int + err error + }{ + { + desc: "password reset request with valid email", + data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, testhost), + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "password reset request with empty email", + data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "", testhost), + contentType: contentType, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "password reset request with empty host", + data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, ""), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "password reset request with invalid email", + data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "invalid", testhost), + contentType: contentType, + status: http.StatusNotFound, + err: errors.ErrNotFound, + }, + { + desc: "password reset with malformed data", + data: fmt.Sprintf(`{"email": %s, "host": %s}`, testemail, testhost), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "password reset with invalid contentype", + data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, testhost), + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/password/reset-request", us.URL), + contentType: tc.contentType, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("GenerateResetToken", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestPasswordReset(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + strongPass := "StrongPassword" + + cases := []struct { + desc string + data string + token string + contentType string + status int + err error + }{ + { + desc: "password reset with valid token", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, strongPass, strongPass), + token: validToken, + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "password reset with invalid token", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, inValidToken, strongPass, strongPass), + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "password reset to weak password", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "weak", "weak"), + token: validToken, + contentType: contentType, + status: http.StatusInternalServerError, + err: ErrPasswordFormat, + }, + { + desc: "password reset with empty token", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, "", strongPass, strongPass), + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "password reset with empty password", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "", ""), + token: validToken, + contentType: contentType, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "password reset with malformed data", + data: fmt.Sprintf(`{"token": "%s", "password": %s, "confirm_password": %s}`, validToken, strongPass, strongPass), + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "password reset with invalid contentype", + data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, strongPass, strongPass), + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPut, + url: fmt.Sprintf("%s/users/password/reset", us.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("ResetSecret", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestUpdateClientRole(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + data string + clientID string + token string + contentType string + status int + err error + }{ + { + desc: "update client role with valid token", + data: fmt.Sprintf(`{"role": "%s"}`, "admin"), + clientID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusOK, + err: nil, + }, + { + desc: "update client role with invalid token", + data: fmt.Sprintf(`{"role": "%s"}`, "admin"), + clientID: client.ID, + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client role with invalid id", + data: fmt.Sprintf(`{"role": "%s"}`, "admin"), + clientID: inValid, + token: validToken, + contentType: contentType, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "update client role with empty token", + data: fmt.Sprintf(`{"role": "%s"}`, "admin"), + clientID: client.ID, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update client with invalid role", + data: fmt.Sprintf(`{"role": "%s"}`, "invalid"), + clientID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusInternalServerError, + err: svcerr.ErrInvalidRole, + }, + { + desc: "update client with invalid contentype", + data: fmt.Sprintf(`{"role": "%s"}`, "admin"), + clientID: client.ID, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update client with malformed data", + data: fmt.Sprintf(`{"role": %s}`, "admin"), + clientID: client.ID, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/users/%s/role", us.URL, tc.clientID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("UpdateClientRole", mock.Anything, tc.token, mock.Anything).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestUpdateClientSecret(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + data string + client mgclients.Client + contentType string + token string + status int + err error + }{ + { + desc: "update user secret with valid token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "update user secret with empty token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update user secret with invalid token", + data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "strongersecret", + }, + }, + contentType: contentType, + token: inValid, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + + { + desc: "update user secret with empty secret", + data: fmt.Sprintf(`{"secret": "%s"}`, ""), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrBearerKey, + }, + { + desc: "update user secret with invalid contentype", + data: fmt.Sprintf(`{"secret": "%s"}`, ""), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + { + desc: "update user secret with malformed data", + data: fmt.Sprintf(`{"secret": %s}`, "invalid"), + client: mgclients.Client{ + ID: client.ID, + Credentials: mgclients.Credentials{ + Identity: "clientname", + Secret: "", + }, + }, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/users/secret", us.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("UpdateClientSecret", mock.Anything, tc.token, mock.Anything, mock.Anything).Return(tc.client, tc.err) + + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestIssueToken(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + validIdentity := "valid" + + cases := []struct { + desc string + data string + contentType string + status int + err error + }{ + { + desc: "issue token with valid identity and secret", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, validIdentity, secret, validID), + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "issue token with empty identity", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, "", secret, validID), + contentType: contentType, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "issue token with empty secret", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, validIdentity, "", validID), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "issue token with empty domain", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, validIdentity, secret, ""), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "issue token with invalid identity", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, "invalid", secret, validID), + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "issues token with malformed data", + data: fmt.Sprintf(`{"identity": %s, "secret": %s, "domainID": %s}`, validIdentity, secret, validID), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "issue token with invalid contentype", + data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, "invalid", secret, validID), + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/tokens/issue", us.URL), + contentType: tc.contentType, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("IssueToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&magistrala.Token{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if tc.err != nil { + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestRefreshToken(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + data string + contentType string + status int + err error + }{ + { + desc: "refresh token with valid token", + data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, validID), + contentType: contentType, + status: http.StatusCreated, + err: nil, + }, + { + desc: "refresh token with invalid token", + data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, inValidToken, validID), + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "refresh token with empty token", + data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, "", validID), + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrValidation, + }, + { + desc: "refresh token with invalid domain", + data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, "invalid"), + contentType: contentType, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "refresh token with malformed data", + data: fmt.Sprintf(`{"refresh_token": %s, "domain_id": %s}`, validToken, validID), + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "refresh token with invalid contentype", + data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, validID), + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/tokens/refresh", us.URL), + contentType: tc.contentType, + body: strings.NewReader(tc.data), + } + + repoCall := svc.On("RefreshToken", mock.Anything, mock.Anything, mock.Anything).Return(&magistrala.Token{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if tc.err != nil { + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestEnableClient(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + cases := []struct { + desc string + client mgclients.Client + response mgclients.Client + token string + status int + err error + }{ + { + desc: "enable client with valid token", + client: client, + response: mgclients.Client{ + ID: client.ID, + Status: mgclients.EnabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "enable client with invalid token", + client: client, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "enable client with empty id", + client: mgclients.Client{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "enable client with invalid id", + client: mgclients.Client{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/%s/enable", us.URL, tc.client.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + + repoCall := svc.On("EnableClient", mock.Anything, mock.Anything, mock.Anything).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if tc.err != nil { + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestDisableClient(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + client mgclients.Client + response mgclients.Client + token string + status int + err error + }{ + { + desc: "disable user with valid token", + client: client, + response: mgclients.Client{ + ID: client.ID, + Status: mgclients.DisabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "disable user with invalid token", + client: client, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "disable user with empty id", + client: mgclients.Client{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "disable user with invalid id", + client: mgclients.Client{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.client) + req := testRequest{ + client: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/%s/disable", us.URL, tc.client.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + + repoCall := svc.On("DisableClient", mock.Anything, mock.Anything, mock.Anything).Return(mgclients.Client{}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestListUsersByUserGroupId(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + groupID string + page mgclients.Page + status int + query string + listUsersResponse mgclients.ClientsPage + err error + }{ + { + desc: "list users with valid token", + token: validToken, + groupID: validID, + status: http.StatusOK, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list users with empty id", + token: validToken, + groupID: "", + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "list users with empty token", + token: "", + groupID: validID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list users with invalid token", + token: inValidToken, + groupID: validID, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users with offset", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid offset", + token: validToken, + groupID: validID, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid limit", + token: validToken, + groupID: validID, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit greater than max", + token: validToken, + groupID: validID, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with owner_id", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with duplicate owner_id", + token: validToken, + groupID: validID, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid owner_id", + token: validToken, + groupID: validID, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with name", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid name", + token: validToken, + groupID: validID, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate name", + token: validToken, + groupID: validID, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with status", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid status", + token: validToken, + groupID: validID, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate status", + token: validToken, + groupID: validID, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with tags", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid tags", + token: validToken, + groupID: validID, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate tags", + token: validToken, + groupID: validID, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with metadata", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid metadata", + token: validToken, + groupID: validID, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate metadata", + token: validToken, + groupID: validID, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with permissions", + token: validToken, + groupID: validID, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid permissions", + token: validToken, + groupID: validID, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate permissions", + token: validToken, + groupID: validID, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with identity", + token: validToken, + groupID: validID, + query: fmt.Sprintf("identity=%s", client.Credentials.Identity), + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid identity", + token: validToken, + groupID: validID, + query: "identity=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate identity", + token: validToken, + groupID: validID, + query: "identity=1&identity=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/groups/%s/users?", us.URL, tc.groupID) + tc.query, + token: tc.token, + } + + repoCall := svc.On("ListMembers", mock.Anything, tc.token, mock.Anything, mock.Anything, mock.Anything).Return( + mgclients.MembersPage{ + Page: tc.listUsersResponse.Page, + Members: tc.listUsersResponse.Clients, + }, + tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestListUsersByChannelID(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + groupID string + page mgclients.Page + status int + query string + listUsersResponse mgclients.ClientsPage + err error + }{ + { + desc: "list users with valid token", + token: validToken, + status: http.StatusOK, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list users with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list users with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users with offset", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit greater than max", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with owner_id", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with duplicate owner_id", + token: validToken, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid owner_id", + token: validToken, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with name", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid name", + token: validToken, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with status", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with tags", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid tags", + token: validToken, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with metadata", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with permissions", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid permissions", + token: validToken, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with identity", + token: validToken, + query: fmt.Sprintf("identity=%s", client.Credentials.Identity), + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid identity", + token: validToken, + query: "identity=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate identity", + token: validToken, + query: "identity=1&identity=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with list_perms", + token: validToken, + query: "list_perms=true", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid list_perms", + token: validToken, + query: "list_perms=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate list_perms", + token: validToken, + query: "list_perms=true&list_perms=false", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/channels/%s/users?", us.URL, validID) + tc.query, + token: tc.token, + } + + repoCall := svc.On("ListMembers", mock.Anything, tc.token, mock.Anything, mock.Anything, mock.Anything).Return( + mgclients.MembersPage{ + Page: tc.listUsersResponse.Page, + Members: tc.listUsersResponse.Clients, + }, + tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + repoCall.Unset() + } +} + +func TestListUsersByDomainID(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + groupID string + page mgclients.Page + status int + query string + listUsersResponse mgclients.ClientsPage + err error + }{ + { + desc: "list users with valid token", + token: validToken, + status: http.StatusOK, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list users with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list users with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users with offset", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit greater than max", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with owner_id", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with duplicate owner_id", + token: validToken, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid owner_id", + token: validToken, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with name", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid name", + token: validToken, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with status", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with tags", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid tags", + token: validToken, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with metadata", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with permissions", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=membership", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid permissions", + token: validToken, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with identity", + token: validToken, + query: fmt.Sprintf("identity=%s", client.Credentials.Identity), + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid identity", + token: validToken, + query: "identity=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate identity", + token: validToken, + query: "identity=1&identity=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users wiith list permissions", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + query: "list_perms=true", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid list_perms", + token: validToken, + query: "list_perms=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate list_perms", + token: validToken, + query: "list_perms=true&list_perms=false", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/domains/%s/users?", us.URL, validID) + tc.query, + token: tc.token, + } + + repoCall := svc.On("ListMembers", mock.Anything, tc.token, mock.Anything, mock.Anything, mock.Anything).Return( + mgclients.MembersPage{ + Page: tc.listUsersResponse.Page, + Members: tc.listUsersResponse.Clients, + }, + tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode) + repoCall.Unset() + } +} + +func TestListUsersByThingID(t *testing.T) { + us, svc := newUsersServer() + defer us.Close() + + cases := []struct { + desc string + token string + groupID string + page mgclients.Page + status int + query string + listUsersResponse mgclients.ClientsPage + err error + }{ + { + desc: "list users with valid token", + token: validToken, + status: http.StatusOK, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list users with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list users with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users with offset", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Offset: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Limit: 1, + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with limit greater than max", + token: validToken, + query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with owner_id", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: fmt.Sprintf("owner_id=%s", validID), + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with duplicate owner_id", + token: validToken, + query: "owner_id=1&owner_id=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with invalid owner_id", + token: validToken, + query: "owner_id=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with name", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "name=clientname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid name", + token: validToken, + query: "name=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with status", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with tags", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid tags", + token: validToken, + query: "tag=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with metadata", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with permissions", + token: validToken, + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{client}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid permissions", + token: validToken, + query: "permission=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list users with identity", + token: validToken, + query: fmt.Sprintf("identity=%s", client.Credentials.Identity), + listUsersResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + }, + Clients: []mgclients.Client{ + client, + }, + }, + status: http.StatusOK, + err: nil, + }, + { + desc: "list users with invalid identity", + token: validToken, + query: "identity=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list users with duplicate identity", + token: validToken, + query: "identity=1&identity=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: us.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/things/%s/users?", us.URL, validID) + tc.query, + token: tc.token, + } + + repoCall := svc.On("ListMembers", mock.Anything, tc.token, mock.Anything, mock.Anything, mock.Anything).Return( + mgclients.MembersPage{ + Page: tc.listUsersResponse.Page, + Members: tc.listUsersResponse.Clients, + }, + tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode) + repoCall.Unset() + } +} + +type respBody struct { + Err string `json:"error"` + Message string `json:"message"` + Total int `json:"total"` + ID string `json:"id"` + Tags []string `json:"tags"` + Role mgclients.Role `json:"role"` + Status mgclients.Status `json:"status"` +} diff --git a/users/api/requests.go b/users/api/requests.go index dfd51147c5..9c52064f3e 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -53,19 +53,17 @@ func (req viewProfileReq) validate() error { } type listClientsReq struct { - token string - status mgclients.Status - offset uint64 - limit uint64 - name string - tag string - identity string - visibility string - owner string - sharedBy string - metadata mgclients.Metadata - order string - dir string + token string + status mgclients.Status + offset uint64 + limit uint64 + name string + tag string + identity string + owner string + metadata mgclients.Metadata + order string + dir string } func (req listClientsReq) validate() error { @@ -75,12 +73,6 @@ func (req listClientsReq) validate() error { if req.limit > maxLimitSize || req.limit < 1 { return apiutil.ErrLimitSize } - if req.visibility != "" && - req.visibility != api.AllVisibility && - req.visibility != api.MyVisibility && - req.visibility != api.SharedVisibility { - return apiutil.ErrInvalidVisibilityType - } if req.dir != "" && (req.dir != api.AscDir && req.dir != api.DescDir) { return apiutil.ErrInvalidDirection } diff --git a/users/api/requests_test.go b/users/api/requests_test.go new file mode 100644 index 0000000000..571233ae86 --- /dev/null +++ b/users/api/requests_test.go @@ -0,0 +1,853 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "strings" + "testing" + + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/stretchr/testify/assert" +) + +const ( + valid = "valid" + invalid = "invalid" +) + +var validID = testsutil.GenerateUUID(&testing.T{}) + +func TestCreateClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req createClientReq + err error + }{ + { + desc: "valid request", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: valid, + Credentials: mgclients.Credentials{ + Identity: "example@example.com", + Secret: valid, + }, + }, + }, + err: nil, + }, + { + desc: "empty token", + req: createClientReq{ + token: "", + client: mgclients.Client{ + ID: validID, + Name: valid, + Credentials: mgclients.Credentials{ + Identity: "example@example.com", + Secret: valid, + }, + }, + }, + }, + { + desc: "name too long", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + }, + err: apiutil.ErrNameSize, + }, + } + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err) + } +} + +func TestViewClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req viewClientReq + err error + }{ + { + desc: "valid request", + req: viewClientReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: viewClientReq{ + token: "", + id: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: viewClientReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestViewProfileReqValidate(t *testing.T) { + cases := []struct { + desc string + req viewProfileReq + err error + }{ + { + desc: "valid request", + req: viewProfileReq{ + token: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: viewProfileReq{ + token: "", + }, + err: apiutil.ErrBearerToken, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err) + } +} + +func TestListClientsReqValidate(t *testing.T) { + cases := []struct { + desc string + req listClientsReq + err error + }{ + { + desc: "valid request", + req: listClientsReq{ + token: valid, + limit: 10, + }, + err: nil, + }, + { + desc: "empty token", + req: listClientsReq{ + token: "", + limit: 10, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "limit too big", + req: listClientsReq{ + token: valid, + limit: api.MaxLimitSize + 1, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "limit too small", + req: listClientsReq{ + token: valid, + limit: 0, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "invalid direction", + req: listClientsReq{ + token: valid, + limit: 10, + dir: "invalid", + }, + err: apiutil.ErrInvalidDirection, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestListMembersByObjectReqValidate(t *testing.T) { + cases := []struct { + desc string + req listMembersByObjectReq + err error + }{ + { + desc: "valid request", + req: listMembersByObjectReq{ + token: valid, + objectKind: "group", + objectID: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: listMembersByObjectReq{ + token: "", + objectKind: "group", + objectID: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty object kind", + req: listMembersByObjectReq{ + token: valid, + objectKind: "", + objectID: validID, + }, + err: apiutil.ErrMissingMemberKind, + }, + { + desc: "empty object id", + req: listMembersByObjectReq{ + token: valid, + objectKind: "group", + objectID: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err) + } +} + +func TestUpdateClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientReq + err error + }{ + { + desc: "valid request", + req: updateClientReq{ + token: valid, + id: validID, + Name: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientReq{ + token: "", + id: validID, + Name: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientReq{ + token: valid, + id: "", + Name: valid, + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientTagsReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientTagsReq + err error + }{ + { + desc: "valid request", + req: updateClientTagsReq{ + token: valid, + id: validID, + Tags: []string{"tag1", "tag2"}, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientTagsReq{ + token: "", + id: validID, + Tags: []string{"tag1", "tag2"}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientTagsReq{ + token: valid, + id: "", + Tags: []string{"tag1", "tag2"}, + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientRoleReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientRoleReq + err error + }{ + { + desc: "valid request", + req: updateClientRoleReq{ + token: valid, + id: validID, + Role: "admin", + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientRoleReq{ + token: "", + id: validID, + Role: "admin", + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientRoleReq{ + token: valid, + id: "", + Role: "admin", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientIdentityReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientIdentityReq + err error + }{ + { + desc: "valid request", + req: updateClientIdentityReq{ + token: valid, + id: validID, + Identity: "example@example.com", + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientIdentityReq{ + token: "", + id: validID, + Identity: "example@example.com", + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: updateClientIdentityReq{ + token: valid, + id: "", + Identity: "example@example.com", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUpdateClientSecretReqValidate(t *testing.T) { + cases := []struct { + desc string + req updateClientSecretReq + err error + }{ + { + desc: "valid request", + req: updateClientSecretReq{ + token: valid, + OldSecret: valid, + NewSecret: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: updateClientSecretReq{ + token: "", + OldSecret: valid, + NewSecret: valid, + }, + err: apiutil.ErrBearerToken, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err) + } +} + +func TestChangeClientStatusReqValidate(t *testing.T) { + cases := []struct { + desc string + req changeClientStatusReq + err error + }{ + { + desc: "valid request", + req: changeClientStatusReq{ + token: valid, + id: validID, + }, + err: nil, + }, + { + desc: "empty token", + req: changeClientStatusReq{ + token: "", + id: validID, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: changeClientStatusReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestLoginClientReqValidate(t *testing.T) { + cases := []struct { + desc string + req loginClientReq + err error + }{ + { + desc: "valid request", + req: loginClientReq{ + Identity: "eaxmple,example.com", + Secret: valid, + }, + err: nil, + }, + { + desc: "empty identity", + req: loginClientReq{ + Identity: "", + Secret: valid, + }, + err: apiutil.ErrMissingIdentity, + }, + { + desc: "empty secret", + req: loginClientReq{ + Identity: "eaxmple,example.com", + Secret: "", + }, + err: apiutil.ErrMissingSecret, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestTokenReqValidate(t *testing.T) { + cases := []struct { + desc string + req tokenReq + err error + }{ + { + desc: "valid request", + req: tokenReq{ + RefreshToken: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: tokenReq{ + RefreshToken: "", + }, + err: apiutil.ErrBearerToken, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestPasswResetReqValidate(t *testing.T) { + cases := []struct { + desc string + req passwResetReq + err error + }{ + { + desc: "valid request", + req: passwResetReq{ + Email: "example@example.com", + Host: "example.com", + }, + err: nil, + }, + { + desc: "empty email", + req: passwResetReq{ + Email: "", + Host: "example.com", + }, + err: apiutil.ErrMissingEmail, + }, + { + desc: "empty host", + req: passwResetReq{ + Email: "example@example.com", + Host: "", + }, + err: apiutil.ErrMissingHost, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestResetTokenReqValidate(t *testing.T) { + cases := []struct { + desc string + req resetTokenReq + err error + }{ + { + desc: "valid request", + req: resetTokenReq{ + Token: valid, + Password: valid, + ConfPass: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: resetTokenReq{ + Token: "", + Password: valid, + ConfPass: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty password", + req: resetTokenReq{ + Token: valid, + Password: "", + ConfPass: valid, + }, + err: apiutil.ErrMissingPass, + }, + { + desc: "empty confpass", + req: resetTokenReq{ + Token: valid, + Password: valid, + ConfPass: "", + }, + err: apiutil.ErrMissingConfPass, + }, + { + desc: "mismatching password and confpass", + req: resetTokenReq{ + Token: valid, + Password: "valid2", + ConfPass: valid, + }, + err: apiutil.ErrInvalidResetPass, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err) + } +} + +func TestAssignUsersRequestValidate(t *testing.T) { + cases := []struct { + desc string + req assignUsersReq + err error + }{ + { + desc: "valid request", + req: assignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: assignUsersReq{ + token: "", + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: assignUsersReq{ + token: valid, + groupID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty users", + req: assignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: apiutil.ErrEmptyList, + }, + { + desc: "empty relation", + req: assignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: apiutil.ErrMissingRelation, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUnassignUsersRequestValidate(t *testing.T) { + cases := []struct { + desc string + req unassignUsersReq + err error + }{ + { + desc: "valid request", + req: unassignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: unassignUsersReq{ + token: "", + groupID: validID, + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: unassignUsersReq{ + token: valid, + groupID: "", + UserIDs: []string{validID}, + Relation: valid, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty users", + req: unassignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{}, + Relation: valid, + }, + err: apiutil.ErrEmptyList, + }, + { + desc: "empty relation", + req: unassignUsersReq{ + token: valid, + groupID: validID, + UserIDs: []string{validID}, + Relation: "", + }, + err: apiutil.ErrMissingRelation, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestAssignGroupsRequestValidate(t *testing.T) { + cases := []struct { + desc string + req assignGroupsReq + err error + }{ + { + desc: "valid request", + req: assignGroupsReq{ + token: valid, + groupID: validID, + GroupIDs: []string{validID}, + }, + err: nil, + }, + { + desc: "empty token", + req: assignGroupsReq{ + token: "", + groupID: validID, + GroupIDs: []string{validID}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty group id", + req: assignGroupsReq{ + token: valid, + groupID: "", + GroupIDs: []string{validID}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty user group ids", + req: assignGroupsReq{ + token: valid, + groupID: validID, + GroupIDs: []string{}, + }, + err: apiutil.ErrEmptyList, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} + +func TestUnassignGroupsRequestValidate(t *testing.T) { + cases := []struct { + desc string + req unassignGroupsReq + err error + }{ + { + desc: "valid request", + req: unassignGroupsReq{ + token: valid, + groupID: validID, + GroupIDs: []string{validID}, + }, + err: nil, + }, + { + desc: "empty token", + req: unassignGroupsReq{ + token: "", + groupID: validID, + GroupIDs: []string{validID}, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty group id", + req: unassignGroupsReq{ + token: valid, + groupID: "", + GroupIDs: []string{validID}, + }, + err: apiutil.ErrMissingID, + }, + { + desc: "empty user group ids", + req: unassignGroupsReq{ + token: valid, + groupID: validID, + GroupIDs: []string{}, + }, + err: apiutil.ErrEmptyList, + }, + } + for _, c := range cases { + err := c.req.validate() + assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) + } +} diff --git a/users/postgres/clients.go b/users/postgres/clients.go index e22dad596d..dd1abfae9c 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -5,7 +5,6 @@ package postgres import ( "context" - "database/sql" "fmt" "github.com/absmach/magistrala/internal/postgres" @@ -79,19 +78,17 @@ func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) erro q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2" rows, err := repo.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole) if err != nil { - if err == sql.ErrNoRows { - return errors.ErrAuthorization - } return errors.Wrap(errors.ErrAuthorization, err) } defer rows.Close() - if !rows.Next() { - return errors.ErrAuthorization - } - if err := rows.Err(); err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + + if rows.Next() { + if err := rows.Err(); err != nil { + return errors.Wrap(errors.ErrAuthorization, err) + } + return nil } - return nil + return errors.ErrAuthorization } func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.Client, error) { @@ -102,22 +99,27 @@ func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.C ID: id, } - row, err := repo.DB.NamedQueryContext(ctx, q, dbc) + rows, err := repo.DB.NamedQueryContext(ctx, q, dbc) if err != nil { - if err == sql.ErrNoRows { - return mgclients.Client{}, errors.Wrap(errors.ErrNotFound, err) - } - return mgclients.Client{}, errors.Wrap(errors.ErrViewEntity, err) + return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) } + defer rows.Close() - defer row.Close() - row.Next() dbc = pgclients.DBClient{} - if err := row.StructScan(&dbc); err != nil { - return mgclients.Client{}, errors.Wrap(errors.ErrNotFound, err) + if rows.Next() { + if err = rows.StructScan(&dbc); err != nil { + return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + client, err := pgclients.ToClient(dbc) + if err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + return client, nil } - return pgclients.ToClient(dbc) + return mgclients.Client{}, repoerr.ErrNotFound } func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) { diff --git a/users/postgres/clients_test.go b/users/postgres/clients_test.go index e4670cf546..4a5d7f0455 100644 --- a/users/postgres/clients_test.go +++ b/users/postgres/clients_test.go @@ -192,7 +192,23 @@ func TestClientsSave(t *testing.T) { }, err: nil, }, + { + desc: "add a client with invalid metadata", + client: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: mgclients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: password, + }, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + }, + err: errors.ErrMalformedEntity, + }, } + for _, tc := range cases { rClient, err := repo.Save(context.Background(), tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -254,3 +270,553 @@ func TestIsPlatformAdmin(t *testing.T) { assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) } } + +func TestRetrieveByID(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + repo := cpostgres.NewRepository(database) + + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: mgclients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: password, + }, + Metadata: mgclients.Metadata{}, + Status: mgclients.EnabledStatus, + } + + _, err := repo.Save(context.Background(), client) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID)) + + cases := []struct { + desc string + clientID string + err error + }{ + { + desc: "retrieve existing client", + clientID: client.ID, + err: nil, + }, + { + desc: "retrieve non-existing client", + clientID: invalidName, + err: errors.ErrNotFound, + }, + { + desc: "retrieve with empty client id", + clientID: "", + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + _, err := repo.RetrieveByID(context.Background(), tc.clientID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestRetrieveAll(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + + repo := cpostgres.NewRepository(database) + + ownerID := testsutil.GenerateUUID(t) + + num := 200 + var items, enabledClients []mgclients.Client + for i := 0; i < num; i++ { + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: mgclients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: "", + }, + Metadata: mgclients.Metadata{}, + Status: mgclients.EnabledStatus, + Tags: []string{"tag1"}, + } + if i%50 == 0 { + client.Owner = ownerID + client.Metadata = map[string]interface{}{ + "key": "value", + } + client.Role = mgclients.AdminRole + client.Status = mgclients.DisabledStatus + } + _, err := repo.Save(context.Background(), client) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID)) + items = append(items, client) + if client.Status == mgclients.EnabledStatus { + enabledClients = append(enabledClients, client) + } + } + + cases := []struct { + desc string + pageMeta mgclients.Page + page mgclients.ClientsPage + err error + }{ + { + desc: "retrieve first page of clients", + pageMeta: mgclients.Page{ + Offset: 0, + Limit: 50, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 0, + Limit: 50, + }, + Clients: items[0:50], + }, + err: nil, + }, + { + desc: "retrieve second page of clients", + pageMeta: mgclients.Page{ + Offset: 50, + Limit: 200, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 50, + Limit: 200, + }, + Clients: items[50:200], + }, + err: nil, + }, + { + desc: "retrieve clients with limit", + pageMeta: mgclients.Page{ + Offset: 0, + Limit: 50, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: uint64(num), + Offset: 0, + Limit: 50, + }, + Clients: items[:50], + }, + }, + { + desc: "retrieve with offset out of range", + pageMeta: mgclients.Page{ + Offset: 1000, + Limit: 200, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 1000, + Limit: 200, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve with limit out of range", + pageMeta: mgclients.Page{ + Offset: 0, + Limit: 1000, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 0, + Limit: 1000, + }, + Clients: items, + }, + err: nil, + }, + { + desc: "retrieve with empty page", + pageMeta: mgclients.Page{}, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 196, // No of enabled clients. + Offset: 0, + Limit: 0, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve with client id", + pageMeta: mgclients.Page{ + IDs: []string{items[0].ID}, + Offset: 0, + Limit: 3, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 3, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "retrieve with invalid client id", + pageMeta: mgclients.Page{ + IDs: []string{invalidName}, + Offset: 0, + Limit: 3, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 3, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve with client name", + pageMeta: mgclients.Page{ + Name: items[0].Name, + Offset: 0, + Limit: 3, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 3, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + { + desc: "retrieve with enabled status", + pageMeta: mgclients.Page{ + Status: mgclients.EnabledStatus, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 196, + Offset: 0, + Limit: 200, + }, + Clients: enabledClients, + }, + err: nil, + }, + { + desc: "retrieve with disabled status", + pageMeta: mgclients.Page{ + Status: mgclients.DisabledStatus, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{items[0], items[50], items[100], items[150]}, + }, + }, + { + desc: "retrieve with all status", + pageMeta: mgclients.Page{ + Status: mgclients.AllStatus, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 0, + Limit: 200, + }, + Clients: items, + }, + }, + { + desc: "retrieve with owner id", + pageMeta: mgclients.Page{ + Owner: ownerID, + Offset: 0, + Limit: 5, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: 5, + }, + Clients: []mgclients.Client{items[0], items[50], items[100], items[150]}, + }, + err: nil, + }, + { + desc: "retrieve with invalid owner id", + pageMeta: mgclients.Page{ + Owner: invalidName, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve by tags", + pageMeta: mgclients.Page{ + Tag: "tag1", + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 200, + Offset: 0, + Limit: 200, + }, + Clients: items, + }, + err: nil, + }, + { + desc: "retrieve with invalid client name", + pageMeta: mgclients.Page{ + Name: invalidName, + Offset: 0, + Limit: 3, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 3, + }, + Clients: []mgclients.Client{}, + }, + }, + { + desc: "retrieve with metadata", + pageMeta: mgclients.Page{ + Metadata: map[string]interface{}{ + "key": "value", + }, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{items[0], items[50], items[100], items[150]}, + }, + err: nil, + }, + { + desc: "retrieve with invalid metadata", + pageMeta: mgclients.Page{ + Metadata: map[string]interface{}{ + "key": "value1", + }, + Offset: 0, + Limit: 200, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve with role", + pageMeta: mgclients.Page{ + Role: mgclients.AdminRole, + Offset: 0, + Limit: 200, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 4, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{items[0], items[50], items[100], items[150]}, + }, + err: nil, + }, + { + desc: "retrieve with invalid role", + pageMeta: mgclients.Page{ + Role: mgclients.AdminRole + 2, + Offset: 0, + Limit: 200, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 200, + }, + Clients: []mgclients.Client{}, + }, + err: nil, + }, + { + desc: "retrieve with identity", + pageMeta: mgclients.Page{ + Identity: items[0].Credentials.Identity, + Offset: 0, + Limit: 3, + Role: mgclients.AllRole, + Status: mgclients.AllStatus, + }, + page: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 3, + }, + Clients: []mgclients.Client{items[0]}, + }, + err: nil, + }, + } + + for _, tc := range cases { + page, err := repo.RetrieveAll(context.Background(), tc.pageMeta) + assert.Equal(t, tc.page.Total, page.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, page.Total)) + assert.Equal(t, tc.page.Offset, page.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Offset, page.Offset)) + assert.Equal(t, tc.page.Limit, page.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Limit, page.Limit)) + assert.Equal(t, tc.page.Page, page.Page, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page, page)) + assert.ElementsMatch(t, tc.page.Clients, page.Clients, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page.Clients, page.Clients)) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestUpdateRole(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM clients") + require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) + }) + + repo := cpostgres.NewRepository(database) + + client := mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Name: namesgen.Generate(), + Credentials: mgclients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), + Secret: password, + }, + Metadata: mgclients.Metadata{}, + Status: mgclients.EnabledStatus, + Role: mgclients.UserRole, + } + + _, err := repo.Save(context.Background(), client) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID)) + + cases := []struct { + desc string + client mgclients.Client + newRole mgclients.Role + err error + }{ + { + desc: "update role to admin", + client: client, + newRole: mgclients.AdminRole, + err: nil, + }, + { + desc: "update role to user", + client: client, + newRole: mgclients.UserRole, + err: nil, + }, + { + desc: "update role with invalid client id", + client: mgclients.Client{ID: invalidName}, + newRole: mgclients.AdminRole, + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + tc.client.Role = tc.newRole + client, err := repo.UpdateRole(context.Background(), tc.client) + if err != nil { + assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected error %v, got %v", tc.desc, tc.err, err)) + } else { + assert.Equal(t, tc.newRole, client.Role, fmt.Sprintf("%s: expected role %v, got %v", tc.desc, tc.newRole, client.Role)) + } + } +} diff --git a/users/service_test.go b/users/service_test.go index 5c52482987..fefc6d63ac 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -7,9 +7,11 @@ import ( "context" "fmt" "regexp" + "strings" "testing" "github.com/absmach/magistrala" + authsvc "github.com/absmach/magistrala/auth" authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/testsutil" mgclients "github.com/absmach/magistrala/pkg/clients" @@ -39,38 +41,54 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - passRegex = regexp.MustCompile("^.{8,}$") - myKey = "mine" - validToken = "token" - inValidToken = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - domainID = testsutil.GenerateUUID(&testing.T{}) - wrongID = testsutil.GenerateUUID(&testing.T{}) + passRegex = regexp.MustCompile("^.{8,}$") + validToken = "token" + inValidToken = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + wrongID = testsutil.GenerateUUID(&testing.T{}) + errHashPassword = errors.New("generate hash from password failed") + errAddPolicies = errors.New("failed to add policies") + errDeletePolicies = errors.New("failed to delete policies") ) -func TestRegisterClient(t *testing.T) { +func newService(selfRegister bool) (users.Service, *mocks.Repository, *authmocks.Service, users.Emailer) { cRepo := new(mocks.Repository) auth := new(authmocks.Service) e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + return users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, selfRegister), cRepo, auth, e +} + +func TestRegisterClient(t *testing.T) { + svc, cRepo, auth, _ := newService(true) cases := []struct { - desc string - client mgclients.Client - token string - err error + desc string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + addPoliciesResponse *magistrala.AddPoliciesRes + deletePoliciesResponse *magistrala.DeletePoliciesRes + token string + identifyErr error + addPoliciesResponseErr error + deletePoliciesResponseErr error + saveErr error + err error }{ { - desc: "register new client", - client: client, - token: validToken, - err: nil, + desc: "register new client successfully", + client: client, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + token: validToken, + err: nil, }, { - desc: "register existing client", - client: client, - token: validToken, - err: errors.ErrConflict, + desc: "register existing client", + client: client, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + token: validToken, + saveErr: repoerr.ErrConflict, + err: errors.ErrConflict, }, { desc: "register a new enabled client with name", @@ -82,8 +100,9 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, + token: validToken, }, { desc: "register a new disabled client with name", @@ -94,156 +113,250 @@ func TestRegisterClient(t *testing.T) { Secret: secret, }, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, + token: validToken, }, { - desc: "register a new enabled client with tags", + desc: "register a new client with all fields", client: mgclients.Client{ + Name: "newclientwithallfields", Tags: []string{"tag1", "tag2"}, Credentials: mgclients.Credentials{ - Identity: "newclientwithtags@example.com", + Identity: "newclientwithallfields@example.com", Secret: secret, }, + Metadata: mgclients.Metadata{ + "name": "newclientwithallfields", + }, Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + err: nil, + token: validToken, }, { - desc: "register a new disabled client with tags", + desc: "register a new client with missing identity", client: mgclients.Client{ - Tags: []string{"tag1", "tag2"}, + Name: "clientWithMissingIdentity", Credentials: mgclients.Credentials{ - Identity: "newclientwithtags@example.com", - Secret: secret, + Secret: secret, }, - Status: mgclients.DisabledStatus, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + saveErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + token: validToken, }, { - desc: "register a new enabled client with metadata", + desc: "register a new client with missing secret", client: mgclients.Client{ + Name: "clientWithMissingSecret", Credentials: mgclients.Credentials{ - Identity: "newclientwithmetadata@example.com", - Secret: secret, + Identity: "clientwithmissingsecret@example.com", + Secret: "", }, - Metadata: validCMetadata, - Status: mgclients.EnabledStatus, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: repoerr.ErrMissingSecret, }, { - desc: "register a new disabled client with metadata", + desc: "register a new client with a weak secret", client: mgclients.Client{ + Name: "clientWithWeakSecret", Credentials: mgclients.Credentials{ - Identity: "newclientwithmetadata@example.com", - Secret: secret, + Identity: "clientwithweaksecret@example.com", + Secret: "weak", }, - Metadata: validCMetadata, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: nil, }, { - desc: "register a new disabled client", + desc: " register a client with a secret that is too long", client: mgclients.Client{ + Name: "clientWithLongSecret", Credentials: mgclients.Credentials{ - Identity: "newclientwithvalidstatus@example.com", - Secret: secret, + Identity: "clientwithlongsecret@example.com", + Secret: strings.Repeat("a", 73), }, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: repoerr.ErrMalformedEntity, }, { - desc: "register a new client with valid disabled status", + desc: "register a new client with invalid status", client: mgclients.Client{ + Name: "clientWithInvalidStatus", Credentials: mgclients.Credentials{ - Identity: "newclientwithvalidstatus@example.com", + Identity: "client with invalid status", Secret: secret, }, - Status: mgclients.DisabledStatus, + Status: mgclients.AllStatus, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: svcerr.ErrInvalidStatus, }, { - desc: "register a new client with all fields", + desc: "register a new client with invalid role", client: mgclients.Client{ - Name: "newclientwithallfields", - Tags: []string{"tag1", "tag2"}, + Name: "clientWithInvalidRole", Credentials: mgclients.Credentials{ - Identity: "newclientwithallfields@example.com", + Identity: "clientwithinvalidrole@example.com", Secret: secret, }, - Metadata: mgclients.Metadata{ - "name": "newclientwithallfields", - }, - Status: mgclients.EnabledStatus, + Role: 2, }, - err: nil, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, + err: svcerr.ErrInvalidRole, }, { - desc: "register a new client with missing identity", + desc: "register a new client with failed to authorize add policies", client: mgclients.Client{ - Name: "clientWithMissingIdentity", + Name: "clientWithFailedToAddPolicies", Credentials: mgclients.Credentials{ - Secret: secret, + Identity: "clientwithfailedpolicies@example.com", + Secret: secret, }, + Role: mgclients.AdminRole, }, - err: errors.ErrMalformedEntity, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: false}, + err: errors.ErrAuthorization, }, { - desc: "register a new client with invalid owner", + desc: "register a new client with failed to add policies with err", client: mgclients.Client{ - Owner: wrongID, + Name: "clientWithFailedToAddPolicies", Credentials: mgclients.Credentials{ - Identity: "newclientwithinvalidowner@example.com", + Identity: "clientwithfailedpolicies@example.com", Secret: secret, }, + Role: mgclients.AdminRole, }, - err: errors.ErrMalformedEntity, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponseErr: errAddPolicies, + err: errAddPolicies, }, { - desc: "register a new client with empty secret", + desc: "register a new client with failed to delete policies with err", client: mgclients.Client{ - Owner: testsutil.GenerateUUID(t), + Name: "clientWithFailedToDeletePolicies", Credentials: mgclients.Credentials{ - Identity: "newclientwithemptysecret@example.com", + Identity: "clientwithfailedtodelete@example.com", + Secret: secret, }, + Role: mgclients.AdminRole, }, - err: repoerr.ErrMissingSecret, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, + deletePoliciesResponseErr: errDeletePolicies, + saveErr: repoerr.ErrConflict, + err: errDeletePolicies, }, { - desc: "register a new client with invalid status", + desc: "register a new client with failed to delete policies with failed to delete", client: mgclients.Client{ + Name: "clientWithFailedToDeletePolicies", Credentials: mgclients.Credentials{ - Identity: "newclientwithinvalidstatus@example.com", + Identity: "clientwithfailedtodelete@example.com", Secret: secret, }, - Status: mgclients.AllStatus, + Role: mgclients.AdminRole, }, - err: svcerr.ErrInvalidStatus, - token: validToken, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, + saveErr: repoerr.ErrConflict, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) + repoCall := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesResponseErr) + repoCall1 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesResponseErr) + repoCall2 := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.client, tc.saveErr) + expected, err := svc.RegisterClient(context.Background(), tc.token, tc.client) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + if err == nil { + tc.client.ID = expected.ID + tc.client.CreatedAt = expected.CreatedAt + tc.client.UpdatedAt = expected.UpdatedAt + tc.client.Credentials.Secret = expected.Credentials.Secret + tc.client.Owner = expected.Owner + tc.client.UpdatedBy = expected.UpdatedBy + assert.Equal(t, tc.client, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected)) + ok := repoCall2.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) } - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) - repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(&magistrala.DeletePoliciesRes{Deleted: true}, nil) - repoCall3 := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.client, tc.err) + repoCall2.Unset() + repoCall1.Unset() + repoCall.Unset() + } + + svc, cRepo, auth, _ = newService(false) + + cases2 := []struct { + desc string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + addPoliciesResponse *magistrala.AddPoliciesRes + deletePoliciesResponse *magistrala.DeletePoliciesRes + token string + identifyErr error + authorizeErr error + addPoliciesResponseErr error + deletePoliciesResponseErr error + saveErr error + checkSuperAdminErr error + err error + }{ + { + desc: "register new client successfully as admin", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: validID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + token: validToken, + err: nil, + }, + { + desc: "register a new clinet as admin with invalid token", + client: client, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "register a new client as admin with failed to authorize", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "register a new client as admin with failed check on super admin", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: validID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + } + for _, tc := range cases2 { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesResponseErr) + repoCall4 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesResponseErr) + repoCall5 := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.client, tc.saveErr) expected, err := svc.RegisterClient(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if err == nil { @@ -254,9 +367,12 @@ func TestRegisterClient(t *testing.T) { tc.client.Owner = expected.Owner tc.client.UpdatedBy = expected.UpdatedBy assert.Equal(t, tc.client, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected)) - ok := repoCall3.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) + ok := repoCall5.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) } + + repoCall5.Unset() + repoCall4.Unset() repoCall3.Unset() repoCall2.Unset() repoCall1.Unset() @@ -265,66 +381,100 @@ func TestRegisterClient(t *testing.T) { } func TestViewClient(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) + adminID := testsutil.GenerateUUID(t) cases := []struct { - desc string - token string - clientID string - response mgclients.Client - err error + desc string + token string + clientID string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + retrieveByIDResponse mgclients.Client + response mgclients.Client + identifyErr error + authorizeErr error + retrieveByIDErr error + checkSuperAdminErr error + err error }{ { - desc: "view client successfully", - response: client, - token: validToken, - clientID: client.ID, - err: nil, + desc: "view client as normal user successfully", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + response: client, + token: validToken, + clientID: client.ID, + err: nil, }, { - desc: "view client with an invalid token", - response: mgclients.Client{}, - token: inValidToken, - clientID: client.ID, - err: svcerr.ErrAuthentication, + desc: "view client with an invalid token", + identifyResponse: &magistrala.IdentityRes{}, + response: mgclients.Client{}, + token: inValidToken, + err: svcerr.ErrAuthentication, }, { - desc: "view client with valid token and invalid client id", - response: mgclients.Client{}, - token: validToken, - clientID: wrongID, - err: svcerr.ErrNotFound, + desc: "view client as normal user with failed to retrieve client", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: mgclients.Client{}, + token: validToken, + clientID: client.ID, + retrieveByIDErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "view client as admin user successfully", + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: client, + response: client, + token: validToken, + clientID: client.ID, + err: nil, }, { - desc: "view client with an invalid token and invalid client id", - response: mgclients.Client{}, - token: inValidToken, - clientID: wrongID, - err: svcerr.ErrAuthentication, + desc: "view client as admin user with invalid token", + identifyResponse: &magistrala.IdentityRes{}, + token: inValidToken, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "view client as admin user with invalid ID", + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + clientID: client.ID, + err: errors.ErrAuthorization, + }, + { + desc: "view client as admin user with failed check on super admin", + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + clientID: client.ID, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) - } - repoCall2 := cRepo.On("RetrieveByID", context.Background(), tc.clientID).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("RetrieveByID", context.Background(), tc.clientID).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) rClient, err := svc.ViewClient(context.Background(), tc.token, tc.clientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) tc.response.Credentials.Secret = "" assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.clientID) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.clientID) assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) } + repoCall3.Unset() repoCall2.Unset() repoCall1.Unset() repoCall.Unset() @@ -332,358 +482,262 @@ func TestViewClient(t *testing.T) { } func TestListClients(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) - - nClients := uint64(200) - aClients := []mgclients.Client{} - OwnerID := testsutil.GenerateUUID(t) - for i := uint64(1); i < nClients; i++ { - identity := fmt.Sprintf("TestListClients_%d@example.com", i) - client := mgclients.Client{ - Name: identity, - Credentials: mgclients.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: mgclients.Metadata{"role": "client"}, - } - if i%50 == 0 { - client.Owner = OwnerID - client.Owner = testsutil.GenerateUUID(t) - } - aClients = append(aClients, client) - } + svc, cRepo, auth, _ := newService(true) cases := []struct { - desc string - token string - page mgclients.Page - response mgclients.ClientsPage - size uint64 - err error + desc string + token string + page mgclients.Page + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + retrieveAllResponse mgclients.ClientsPage + response mgclients.ClientsPage + size uint64 + identifyErr error + authorizeErr error + retrieveAllErr error + superAdminErr error + err error }{ { - desc: "list clients with authorized token", - token: validToken, - - page: mgclients.Page{ - Status: mgclients.AllStatus, - }, - size: 0, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{}, - }, - err: nil, - }, - { - desc: "list clients with an invalid token", - token: inValidToken, + desc: "list clients as admin successfully", page: mgclients.Page{ - Status: mgclients.AllStatus, + Total: 1, }, - size: 0, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 0, + Total: 1, }, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "list clients that are shared with me", - token: validToken, - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Status: mgclients.EnabledStatus, + Clients: []mgclients.Client{client}, }, response: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, + Total: 1, }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Clients: []mgclients.Client{client}, }, - size: 4, - }, - { - desc: "list clients that are shared with me with a specific name", token: validToken, - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Name: "TestListClients3", - Status: mgclients.EnabledStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, + err: nil, }, { - desc: "list clients that are shared with me with an invalid name", - token: validToken, + desc: "list clients as admin with invalid token", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Name: "notpresentclient", - Status: mgclients.EnabledStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{}, + Total: 1, }, - size: 0, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { - desc: "list clients that I own", - token: validToken, + desc: "list clients as admin with invalid ID", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Status: mgclients.EnabledStatus, + Total: 1, }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + authorizeErr: svcerr.ErrAuthorization, + err: nil, }, { - desc: "list clients that I own with a specific name", - token: validToken, + desc: "list clients as admin with failed to retrieve clients", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "TestListClients3", - Status: mgclients.AllStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Total: 1, }, - size: 4, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveAllResponse: mgclients.ClientsPage{}, + token: validToken, + retrieveAllErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "list clients that I own with an invalid name", - token: validToken, + desc: "list clients as admin with failed check on super admin", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "notpresentclient", - Status: mgclients.AllStatus, + Total: 1, }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{}, - }, - size: 0, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + superAdminErr: errors.ErrAuthorization, + err: nil, }, { - desc: "list clients that I own and are shared with me", - token: validToken, + desc: "list clients as normal user successfully", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Status: mgclients.AllStatus, + Total: 1, }, - response: mgclients.ClientsPage{ + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + retrieveAllResponse: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, + Total: 1, }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that I own and are shared with me with a specific name", - token: validToken, - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "TestListClients3", - Status: mgclients.AllStatus, + Clients: []mgclients.Client{client}, }, response: mgclients.ClientsPage{ Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 0, + Total: 1, }, - Clients: []mgclients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, + Clients: []mgclients.Client{client}, }, - size: 4, - }, - { - desc: "list clients that I own and are shared with me with an invalid name", token: validToken, - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Owner: myKey, - Name: "notpresentclient", - Status: mgclients.AllStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []mgclients.Client{}, - }, - size: 0, + err: nil, }, { - desc: "list clients with offset and limit", - token: validToken, - + desc: "list clients as normal user with failed to retrieve clients", page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Status: mgclients.AllStatus, - }, - response: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: nClients - 6, - Offset: 0, - Limit: 0, - }, - Clients: aClients[6:nClients], + Total: 1, }, - size: nClients - 6, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + retrieveAllResponse: mgclients.ClientsPage{}, + token: validToken, + retrieveAllErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) - } - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.superAdminErr) + repoCall3 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) page, err := svc.ListClients(context.Background(), tc.token, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "RetrieveAll", context.Background(), mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveAll", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() + repoCall3.Unset() } } func TestUpdateClient(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) client1 := client client2 := client client1.Name = "Updated client" client2.Metadata = mgclients.Metadata{"role": "test"} + adminID := testsutil.GenerateUUID(t) cases := []struct { - desc string - client mgclients.Client - response mgclients.Client - token string - err error + desc string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + updateResponse mgclients.Client + token string + identifyErr error + authorizeErr error + updateErr error + checkSuperAdminErr error + err error }{ { - desc: "update client name with valid token", - client: client1, - response: client1, - token: validToken, - err: nil, - }, - { - desc: "update client name with invalid token", - client: client1, - response: mgclients.Client{}, - token: inValidToken, - err: svcerr.ErrAuthentication, - }, - { - desc: "update client name with invalid ID", - client: mgclients.Client{ - ID: wrongID, - Name: "Updated Client", - }, - response: mgclients.Client{}, - token: inValidToken, - err: svcerr.ErrAuthentication, - }, - { - desc: "update client metadata with valid token", - client: client2, - response: client2, - token: validToken, - err: nil, - }, - { - desc: "update client metadata with invalid token", - client: client2, - response: mgclients.Client{}, - token: inValidToken, - err: svcerr.ErrAuthentication, + desc: "update client name successfully as normal user", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: client1.ID}, + updateResponse: client1, + token: validToken, + err: nil, + }, + { + desc: "update metadata successfully as normal user", + client: client2, + identifyResponse: &magistrala.IdentityRes{UserId: client2.ID}, + updateResponse: client2, + token: validToken, + err: nil, + }, + { + desc: "update client name as normal user with invalid token", + client: client1, + identifyResponse: &magistrala.IdentityRes{}, + token: inValidToken, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client name as normal user with repo error on update", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: client1.ID}, + updateResponse: mgclients.Client{}, + token: validToken, + updateErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + { + desc: "update client name as admin successfully", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateResponse: client1, + token: validToken, + err: nil, + }, + { + desc: "update client metadata as admin successfully", + client: client2, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateResponse: client2, + token: validToken, + err: nil, + }, + { + desc: "update client name as admin with invalid token", + client: client1, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "update cient name as admin with invalid ID", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client with failed check on super admin", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "update client name as admin with repo error on update", + client: client1, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateResponse: mgclients.Client{}, + token: validToken, + updateErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) - } - repoCall2 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateResponse, tc.err) updatedClient, err := svc.UpdateClient(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedClient)) + if tc.err == nil { ok := repoCall2.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) @@ -691,191 +745,381 @@ func TestUpdateClient(t *testing.T) { repoCall.Unset() repoCall1.Unset() repoCall2.Unset() + repoCall3.Unset() } } func TestUpdateClientTags(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) client.Tags = []string{"updated"} + adminID := testsutil.GenerateUUID(t) cases := []struct { - desc string - client mgclients.Client - response mgclients.Client - token string - err error + desc string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + updateClientTagsResponse mgclients.Client + token string + identifyErr error + authorizeErr error + updateClientTagsErr error + checkSuperAdminErr error + err error }{ { - desc: "update client tags with valid token", - client: client, - token: validToken, - response: client, - err: nil, - }, - { - desc: "update client tags with invalid token", - client: client, - token: inValidToken, - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "update client name with invalid ID", - client: mgclients.Client{ - ID: wrongID, - Name: "Updated name", - }, - response: mgclients.Client{}, - token: inValidToken, - err: svcerr.ErrAuthentication, + desc: "update client tags as normal user successfully", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + updateClientTagsResponse: client, + token: validToken, + err: nil, + }, + { + desc: "update client tags as normal user with invalid token", + client: client, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client tags as normal user with repo error on update", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + updateClientTagsResponse: mgclients.Client{}, + token: validToken, + updateClientTagsErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + { + desc: "update client tags as admin successfully", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + token: validToken, + err: nil, + }, + { + desc: "update client tags as admin with invalid token", + client: client, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client tags as admin with invalid ID", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client tags as admin with failed check on super admin", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + checkSuperAdminErr: errors.ErrAuthorization, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client tags as admin with repo error on update", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateClientTagsResponse: mgclients.Client{}, + token: validToken, + updateClientTagsErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) - } - repoCall2 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.updateClientTagsResponse, tc.updateClientTagsErr) updatedClient, err := svc.UpdateClientTags(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateClientTagsResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateClientTagsResponse, updatedClient)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "UpdateTags", context.Background(), mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "UpdateTags", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("UpdateTags was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() + repoCall3.Unset() } } func TestUpdateClientIdentity(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) client2 := client client2.Credentials.Identity = "updated@example.com" + adminID := testsutil.GenerateUUID(t) cases := []struct { - desc string - identity string - response mgclients.Client - token string - id string - err error + desc string + identity string + token string + id string + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + updateClientIdentityResponse mgclients.Client + identifyErr error + authorizeErr error + updateClientIdentityErr error + checkSuperAdminErr error + err error }{ { - desc: "update client identity with valid token", - identity: "updated@example.com", - token: validToken, - id: client.ID, - response: client2, - err: nil, - }, - { - desc: "update client identity with invalid id", - identity: "updated@example.com", - token: validToken, - id: wrongID, - response: mgclients.Client{}, - err: repoerr.ErrNotFound, - }, - { - desc: "update client identity with invalid token", - identity: "updated@example.com", - token: inValidToken, - id: client2.ID, - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, + desc: "update client as normal user successfully", + identity: "updated@example.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + updateClientIdentityResponse: client2, + err: nil, + }, + { + desc: "update client identity as normal user with invalid token", + identity: "updated@example.com", + token: inValidToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client identity as normal user with repo error on update", + identity: "updated@example.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + updateClientIdentityResponse: mgclients.Client{}, + updateClientIdentityErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + { + desc: "update client identity as admin successfully", + identity: "updated@example.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, + }, + { + desc: "update client identity as admin with invalid token", + identity: "updated@example.com", + token: inValidToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client identity as admin with invalid ID", + identity: "updated@example.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: errors.ErrAuthorization, + }, + { + desc: "update client identity as admin with failed check on super admin", + identity: "updated@example.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "update client identity as admin with repo error on update", + identity: "updated@exmaple.com", + token: validToken, + id: client.ID, + identifyResponse: &magistrala.IdentityRes{UserId: adminID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + updateClientIdentityResponse: mgclients.Client{}, + updateClientIdentityErr: errors.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) - } - repoCall2 := cRepo.On("UpdateIdentity", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("UpdateIdentity", context.Background(), mock.Anything).Return(tc.updateClientIdentityResponse, tc.updateClientIdentityErr) updatedClient, err := svc.UpdateClientIdentity(context.Background(), tc.token, tc.id, tc.identity) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateClientIdentityResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateClientIdentityResponse, updatedClient)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "UpdateIdentity", context.Background(), mock.Anything) + ok := repoCall3.Parent.AssertCalled(t, "UpdateIdentity", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("UpdateIdentity was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() + repoCall3.Unset() } } func TestUpdateClientRole(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) - - client.Role = mgclients.AdminRole - - cases := []struct { - desc string - client mgclients.Client - response mgclients.Client - token string - err error - }{ - { - desc: "update client role with valid token", - client: client, - token: validToken, - response: client, - err: nil, - }, - { - desc: "update client role with invalid token", - client: client, - token: inValidToken, - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, - }, + svc, cRepo, auth, _ := newService(true) + + client2 := client + client.Role = mgclients.AdminRole + client2.Role = mgclients.UserRole + + cases := []struct { + desc string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + deletePolicyResponse *magistrala.DeletePolicyRes + addPolicyResponse *magistrala.AddPolicyRes + updateRoleResponse mgclients.Client + token string + identifyErr error + authorizeErr error + deletePolicyErr error + addPolicyErr error + updateRoleErr error + checkSuperAdminErr error + err error + }{ { - desc: "update client role with invalid ID", - client: mgclients.Client{ - ID: wrongID, - Role: mgclients.AdminRole, - }, - response: mgclients.Client{}, - token: inValidToken, - err: svcerr.ErrAuthentication, + desc: "update client role successfully", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + updateRoleResponse: client, + token: validToken, + err: nil, + }, + { + desc: "update client role with invalid token", + client: client, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client role with invalid ID", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client role with failed check on super admin", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + checkSuperAdminErr: errors.ErrAuthorization, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client role with failed authorization on add policy", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Authorized: false}, + token: validToken, + err: errors.ErrAuthorization, + }, + { + desc: "update client role with failed to add policy", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{}, + addPolicyErr: errors.ErrMalformedEntity, + token: validToken, + err: errAddPolicies, + }, + { + desc: "update client role to user role successfully ", + client: client2, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + updateRoleResponse: client2, + token: validToken, + err: nil, + }, + { + desc: "update client role to user role with failed to delete policy", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, + updateRoleResponse: mgclients.Client{}, + token: validToken, + err: errDeletePolicies, + }, + { + desc: "update client role to user role with failed to delete policy with error", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, + updateRoleResponse: mgclients.Client{}, + token: validToken, + deletePolicyErr: svcerr.ErrMalformedEntity, + err: errDeletePolicies, + }, + { + desc: "Update client with failed repo update and roll back", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, + updateRoleResponse: mgclients.Client{}, + token: validToken, + updateRoleErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "Update client with failed repo update and failedroll back", + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, + updateRoleResponse: mgclients.Client{}, + token: validToken, + updateRoleErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) - repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) - } - repoCall4 := cRepo.On("UpdateRole", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(tc.addPolicyResponse, tc.addPolicyErr) + repoCall4 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(tc.deletePolicyResponse, tc.deletePolicyErr) + repoCall5 := cRepo.On("UpdateRole", context.Background(), mock.Anything).Return(tc.updateRoleResponse, tc.updateRoleErr) updatedClient, err := svc.UpdateClientRole(context.Background(), tc.token, tc.client) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) + assert.Equal(t, tc.updateRoleResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateRoleResponse, updatedClient)) if tc.err == nil { - ok := repoCall4.Parent.AssertCalled(t, "UpdateRole", context.Background(), mock.Anything) + ok := repoCall5.Parent.AssertCalled(t, "UpdateRole", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("UpdateRole was not called on %s", tc.desc)) } repoCall.Unset() @@ -883,61 +1127,128 @@ func TestUpdateClientRole(t *testing.T) { repoCall2.Unset() repoCall3.Unset() repoCall4.Unset() + repoCall5.Unset() } } func TestUpdateClientSecret(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) + newSecret := "newstrongSecret" rClient := client rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) + responseClient := client + responseClient.Credentials.Secret = newSecret cases := []struct { - desc string - oldSecret string - newSecret string - token string - response mgclients.Client - err error + desc string + oldSecret string + newSecret string + token string + identifyResponse *magistrala.IdentityRes + retrieveByIDResponse mgclients.Client + retrieveByIdentityResponse mgclients.Client + updateSecretResponse mgclients.Client + issueResponse *magistrala.Token + response mgclients.Client + identifyErr error + retrieveByIDErr error + retrieveByIdentityErr error + updateSecretErr error + issueErr error + err error }{ { - desc: "update client secret with valid token", - oldSecret: client.Credentials.Secret, - newSecret: "newSecret", - token: validToken, - response: rClient, - err: nil, - }, - { - desc: "update client secret with invalid token", - oldSecret: client.Credentials.Secret, - newSecret: "newPassword", - token: inValidToken, - response: mgclients.Client{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "update client secret with wrong old secret", - oldSecret: "oldSecret", - newSecret: "newSecret", - token: validToken, - response: mgclients.Client{}, - err: repoerr.ErrInvalidSecret, + desc: "update client secret with valid token", + oldSecret: client.Credentials.Secret, + newSecret: newSecret, + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIdentityResponse: rClient, + retrieveByIDResponse: client, + updateSecretResponse: responseClient, + issueResponse: &magistrala.Token{AccessToken: validToken}, + response: responseClient, + err: nil, + }, + { + desc: "update client secret with invalid token", + oldSecret: client.Credentials.Secret, + newSecret: newSecret, + token: inValidToken, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "update client secret with weak secret", + oldSecret: client.Credentials.Secret, + newSecret: "weak", + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + err: users.ErrPasswordFormat, + }, + { + desc: "update client secret with failed to retrieve client by ID", + oldSecret: client.Credentials.Secret, + newSecret: newSecret, + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: mgclients.Client{}, + retrieveByIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "update client secret with failed to retrieve client by identity", + oldSecret: client.Credentials.Secret, + newSecret: newSecret, + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + retrieveByIdentityResponse: mgclients.Client{}, + retrieveByIdentityErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "update client secret with invalod old secret", + oldSecret: "invalid", + newSecret: newSecret, + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + retrieveByIdentityResponse: rClient, + err: errors.ErrLogin, + }, + { + desc: "update client secret with too long new secret", + oldSecret: client.Credentials.Secret, + newSecret: strings.Repeat("a", 73), + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + retrieveByIdentityResponse: rClient, + err: repoerr.ErrMalformedEntity, + }, + { + desc: "update client secret with failed to update secret", + oldSecret: client.Credentials.Secret, + newSecret: newSecret, + token: validToken, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + retrieveByIdentityResponse: rClient, + updateSecretResponse: mgclients.Client{}, + updateSecretErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: client.ID}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) - } - repoCall1 := cRepo.On("RetrieveByID", context.Background(), client.ID).Return(tc.response, tc.err) - repoCall2 := cRepo.On("RetrieveByIdentity", context.Background(), client.Credentials.Identity).Return(tc.response, tc.err) - repoCall3 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.response, tc.err) - repoCall4 := auth.On("Issue", mock.Anything, mock.Anything).Return(&magistrala.Token{}, nil) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := cRepo.On("RetrieveByID", context.Background(), client.ID).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) + repoCall2 := cRepo.On("RetrieveByIdentity", context.Background(), client.Credentials.Identity).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) + repoCall3 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateSecretErr) + repoCall4 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) updatedClient, err := svc.UpdateClientSecret(context.Background(), tc.token, tc.oldSecret, tc.newSecret) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) @@ -958,10 +1269,7 @@ func TestUpdateClientSecret(t *testing.T) { } func TestEnableClient(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) enabledClient1 := mgclients.Client{ID: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: mgclients.EnabledStatus} disabledClient1 := mgclients.Client{ID: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: mgclients.DisabledStatus} @@ -969,56 +1277,116 @@ func TestEnableClient(t *testing.T) { endisabledClient1.Status = mgclients.EnabledStatus cases := []struct { - desc string - id string - token string - client mgclients.Client - response mgclients.Client - err error + desc string + id string + token string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + retrieveByIDResponse mgclients.Client + changeStatusResponse mgclients.Client + response mgclients.Client + identifyErr error + authorizeErr error + retrieveByIDErr error + changeStatusErr error + checkSuperAdminErr error + err error }{ { - desc: "enable disabled client", - id: disabledClient1.ID, - token: validToken, - client: disabledClient1, - response: endisabledClient1, - err: nil, - }, - { - desc: "enable enabled client", - id: enabledClient1.ID, - token: validToken, - client: enabledClient1, - response: enabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, - }, - { - desc: "enable non-existing client", - id: wrongID, - token: validToken, - client: mgclients.Client{}, - response: mgclients.Client{}, - err: repoerr.ErrNotFound, + desc: "enable disabled client", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: disabledClient1, + changeStatusResponse: endisabledClient1, + response: endisabledClient1, + err: nil, + }, + { + desc: "enable disabled client with invalid token", + id: disabledClient1.ID, + token: inValidToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "enable disabled client with failed to authorize", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: errors.ErrAuthorization, + }, + { + desc: "enable disabled client with normal user token", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: validID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "enable disabled client with failed to retrieve client by ID", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: mgclients.Client{}, + retrieveByIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "enable already enabled client", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: enabledClient1, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "enable disabled client with failed to change status", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: disabledClient1, + changeStatusResponse: mgclients.Client{}, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.client, tc.err) - repoCall3 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) + repoCall4 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) _, err := svc.EnableClient(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall3.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) + ok = repoCall4.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() repoCall3.Unset() + repoCall4.Unset() } cases2 := []struct { @@ -1088,10 +1456,7 @@ func TestEnableClient(t *testing.T) { } func TestDisableClient(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) enabledClient1 := mgclients.Client{ID: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: mgclients.EnabledStatus} disabledClient1 := mgclients.Client{ID: testsutil.GenerateUUID(t), Credentials: mgclients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: mgclients.DisabledStatus} @@ -1099,56 +1464,116 @@ func TestDisableClient(t *testing.T) { disenabledClient1.Status = mgclients.DisabledStatus cases := []struct { - desc string - id string - token string - client mgclients.Client - response mgclients.Client - err error + desc string + id string + token string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + authorizeResponse *magistrala.AuthorizeRes + retrieveByIDResponse mgclients.Client + changeStatusResponse mgclients.Client + response mgclients.Client + identifyErr error + authorizeErr error + retrieveByIDErr error + changeStatusErr error + checkSuperAdminErr error + err error }{ { - desc: "disable enabled client", - id: enabledClient1.ID, - token: validToken, - client: enabledClient1, - response: disenabledClient1, - err: nil, - }, - { - desc: "disable disabled client", - id: disabledClient1.ID, - token: validToken, - client: disabledClient1, - response: mgclients.Client{}, - err: mgclients.ErrStatusAlreadyAssigned, - }, - { - desc: "disable non-existing client", - id: wrongID, - client: mgclients.Client{}, - token: validToken, - response: mgclients.Client{}, - err: repoerr.ErrNotFound, + desc: "disable enabled client", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: enabledClient1, + changeStatusResponse: disenabledClient1, + response: disenabledClient1, + err: nil, + }, + { + desc: "disable enabled client with invalid token", + id: enabledClient1.ID, + token: inValidToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "disable enabled client with failed to authorize", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: errors.ErrAuthorization, + }, + { + desc: "disable enabled client with normal user token", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + checkSuperAdminErr: errors.ErrAuthorization, + err: errors.ErrAuthorization, + }, + { + desc: "disable enabled client with failed to retrieve client by ID", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: mgclients.Client{}, + retrieveByIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "disable already disabled client", + id: disabledClient1.ID, + token: validToken, + client: disabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: disabledClient1, + err: mgclients.ErrStatusAlreadyAssigned, + }, + { + desc: "disable enabled client with failed to change status", + id: enabledClient1.ID, + token: validToken, + client: enabledClient1, + identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + retrieveByIDResponse: enabledClient1, + changeStatusResponse: mgclients.Client{}, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.client, tc.err) - repoCall3 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.response, tc.err) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := cRepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr) + repoCall3 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) + repoCall4 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) _, err := svc.DisableClient(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if tc.err == nil { - ok := repoCall2.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) + ok := repoCall3.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall3.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) + ok = repoCall4.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) } repoCall.Unset() repoCall1.Unset() repoCall2.Unset() repoCall3.Unset() + repoCall4.Unset() } cases2 := []struct { @@ -1218,133 +1643,475 @@ func TestDisableClient(t *testing.T) { } func TestListMembers(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) - - nClients := uint64(10) - aClients := []mgclients.Client{} - owner := testsutil.GenerateUUID(t) - for i := uint64(0); i < nClients; i++ { - identity := fmt.Sprintf("member_%d@example.com", i) - client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: identity, - Credentials: mgclients.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: mgclients.Metadata{"role": "client"}, - } - if i%3 == 0 { - client.Owner = owner - } - aClients = append(aClients, client) - } + svc, cRepo, auth, _ := newService(true) + + validPolicy := fmt.Sprintf("%s_%s", validID, client.ID) + permissionsClient := client + permissionsClient.Permissions = []string{"read"} cases := []struct { - desc string - token string - groupID string - page mgclients.Page - response mgclients.MembersPage - err error + desc string + token string + groupID string + objectKind string + objectID string + page mgclients.Page + identifyResponse *magistrala.IdentityRes + authorizeReq *magistrala.AuthorizeReq + listAllSubjectsReq *magistrala.ListSubjectsReq + authorizeResponse *magistrala.AuthorizeRes + listAllSubjectsResponse *magistrala.ListSubjectsRes + retrieveAllResponse mgclients.ClientsPage + listPermissionsResponse *magistrala.ListPermissionsRes + response mgclients.MembersPage + authorizeErr error + listAllSubjectsErr error + retrieveAllErr error + identifyErr error + listPermissionErr error + err error }{ { - desc: "list clients with authorized token", - token: validToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - IDs: clientsToUUIDs(aClients), + desc: "list members with no policies successfully of the things kind", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 0, Offset: 0, - Limit: 10, + Limit: 100, }, - Members: aClients, }, err: nil, }, { - desc: "list clients with offset and limit", - token: validToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - Offset: 6, - Limit: nClients, - Status: mgclients.AllStatus, - IDs: clientsToUUIDs(aClients[6 : nClients-1]), + desc: "list members with policies successsfully of the things kind", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Clients: []mgclients.Client{client}, + }, + response: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Members: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list members with policies successsfully of the things kind with permissions", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Clients: []mgclients.Client{client}, }, + listPermissionsResponse: &magistrala.ListPermissionsRes{Permissions: []string{"read"}}, response: mgclients.MembersPage{ Page: mgclients.Page{ - Total: nClients - 6 - 1, + Total: 1, + Offset: 0, + Limit: 100, }, - Members: aClients[6 : nClients-1], + Members: []mgclients.Client{permissionsClient}, }, + err: nil, }, { - desc: "list clients with an invalid token", - token: inValidToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{}, + desc: "list members with policies of the things kind with permissionswith failed list permissions", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Clients: []mgclients.Client{client}, + }, + listPermissionsResponse: &magistrala.ListPermissionsRes{}, + response: mgclients.MembersPage{}, + listPermissionErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list members with of the things kind with failed to authorize", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: errors.ErrAuthorization, + }, + { + desc: "list members with of the things kind with failed to list all subjects", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsErr: errors.ErrNotFound, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{}, + err: errors.ErrNotFound, + }, + { + desc: "list members with of the things kind with failed to retrieve all", + token: validToken, + groupID: validID, + objectKind: authsvc.ThingsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.ThingType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.ThingType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{}, + response: mgclients.MembersPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "list members with no policies successfully of the domain kind", + token: validToken, + groupID: validID, + objectKind: authsvc.DomainsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.DomainType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.DomainType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, response: mgclients.MembersPage{ Page: mgclients.Page{ Total: 0, Offset: 0, - Limit: 0, + Limit: 100, }, }, - err: svcerr.ErrAuthentication, + err: nil, }, { - desc: "list clients for an owner", - token: validToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - IDs: clientsToUUIDs([]mgclients.Client{aClients[0], aClients[3], aClients[6], aClients[9]}), + desc: "list members with policies successsfully of the domains kind", + token: validToken, + groupID: validID, + objectKind: authsvc.DomainsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.DomainType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.DomainType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Clients: []mgclients.Client{client}, + }, + response: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Members: []mgclients.Client{client}, + }, + err: nil, + }, + { + desc: "list members with of the domains kind with failed to authorize", + token: validToken, + groupID: validID, + objectKind: authsvc.DomainsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.DomainType, + Object: validID, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: errors.ErrAuthorization, + }, + { + desc: "list members with no policies successfully of the groups kind", + token: validToken, + groupID: validID, + objectKind: authsvc.GroupsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.GroupType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.GroupType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + response: mgclients.MembersPage{ + Page: mgclients.Page{ + Total: 0, + Offset: 0, + Limit: 100, + }, + }, + err: nil, + }, + { + desc: "list members with policies successsfully of the domains kind", + token: validToken, + groupID: validID, + objectKind: authsvc.GroupsKind, + objectID: validID, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + authorizeReq: &magistrala.AuthorizeReq{ + SubjectType: authsvc.UserType, + SubjectKind: authsvc.TokenKind, + Subject: validToken, + Permission: "read", + ObjectType: authsvc.GroupType, + Object: validID, + }, + listAllSubjectsReq: &magistrala.ListSubjectsReq{ + SubjectType: authsvc.UserType, + Permission: "read", + Object: validID, + ObjectType: authsvc.GroupType, + }, + authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, + listAllSubjectsResponse: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + retrieveAllResponse: mgclients.ClientsPage{ + Page: mgclients.Page{ + Total: 1, + Offset: 0, + Limit: 100, + }, + Clients: []mgclients.Client{client}, }, response: mgclients.MembersPage{ Page: mgclients.Page{ - Total: 4, + Total: 1, + Offset: 0, + Limit: 100, }, - Members: []mgclients.Client{aClients[0], aClients[3], aClients[6], aClients[9]}, + Members: []mgclients.Client{client}, }, err: nil, }, + { + desc: "list members with invalid token", + token: inValidToken, + page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, nil) - if tc.token == inValidToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) - } - repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true, Id: validID}, nil) - - repoCall2 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&magistrala.ListSubjectsRes{Policies: prefixClientUUIDSWithDomain(tc.response.Members)}, nil) - repoCall3 := cRepo.On("RetrieveAll", context.Background(), tc.page).Return(mgclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) - page, err := svc.ListMembers(context.Background(), tc.token, "groups", tc.groupID, tc.page) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := auth.On("Authorize", mock.Anything, tc.authorizeReq).Return(tc.authorizeResponse, tc.authorizeErr) + repoCall2 := auth.On("ListAllSubjects", mock.Anything, tc.listAllSubjectsReq).Return(tc.listAllSubjectsResponse, tc.listAllSubjectsErr) + repoCall3 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) + repoCall4 := auth.On("ListPermissions", mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionErr) + page, err := svc.ListMembers(context.Background(), tc.token, tc.objectKind, tc.objectID, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveAll", context.Background(), tc.page) - assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) - } + repoCall.Unset() repoCall1.Unset() repoCall2.Unset() repoCall3.Unset() + repoCall4.Unset() } } func TestIssueToken(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) rClient := client rClient2 := client @@ -1354,40 +2121,54 @@ func TestIssueToken(t *testing.T) { rClient3.Credentials.Secret, _ = phasher.Hash("wrongsecret") cases := []struct { - desc string - client mgclients.Client - rClient mgclients.Client - err error + desc string + DomainID string + client mgclients.Client + retrieveByIdentityResponse mgclients.Client + issueResponse *magistrala.Token + retrieveByIdentityErr error + issueErr error + err error }{ { - desc: "issue token for an existing client", - client: client, - rClient: rClient, - err: nil, + desc: "issue token for an existing client", + client: client, + retrieveByIdentityResponse: rClient, + issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + err: nil, }, { - desc: "issue token for a non-existing client", - client: client, - rClient: mgclients.Client{}, - err: svcerr.ErrAuthentication, + desc: "issue token for a non-existing client", + client: client, + retrieveByIdentityResponse: mgclients.Client{}, + retrieveByIdentityErr: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { - desc: "issue token for a client with wrong secret", - client: rClient2, - rClient: rClient3, - err: errors.ErrAuthentication, + desc: "issue token for a client with wrong secret", + client: client, + retrieveByIdentityResponse: rClient3, + err: errors.ErrLogin, + }, + { + desc: "issue token with non-empty domain id", + DomainID: "domain", + client: client, + retrieveByIdentityResponse: rClient, + issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + err: nil, }, } for _, tc := range cases { - repoCall := auth.On("Issue", mock.Anything, mock.Anything).Return(&magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, tc.err) - repoCall1 := cRepo.On("RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity).Return(tc.rClient, tc.err) - token, err := svc.IssueToken(context.Background(), tc.client.Credentials.Identity, tc.client.Credentials.Secret, "") + repoCall := cRepo.On("RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) + repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + token, err := svc.IssueToken(context.Background(), tc.client.Credentials.Identity, tc.client.Credentials.Secret, tc.DomainID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if err == nil { assert.NotEmpty(t, token.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) assert.NotEmpty(t, token.GetRefreshToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetRefreshToken())) - ok := repoCall1.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity) + ok := repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity) assert.True(t, ok, fmt.Sprintf("RetrieveByIdentity was not called on %s", tc.desc)) } repoCall.Unset() @@ -1396,19 +2177,17 @@ func TestIssueToken(t *testing.T) { } func TestRefreshToken(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, _, auth, _ := newService(true) rClient := client rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) cases := []struct { - desc string - token string - client mgclients.Client - err error + desc string + token string + domainID string + client mgclients.Client + err error }{ { desc: "refresh token with refresh token for an existing client", @@ -1440,11 +2219,18 @@ func TestRefreshToken(t *testing.T) { client: client, err: svcerr.ErrAuthentication, }, + { + desc: "refresh token with non-empty domain id", + token: validToken, + domainID: validID, + client: client, + err: nil, + }, } for _, tc := range cases { repoCall := auth.On("Refresh", mock.Anything, mock.Anything).Return(&magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, tc.err) - token, err := svc.RefreshToken(context.Background(), tc.token, "") + token, err := svc.RefreshToken(context.Background(), tc.token, tc.domainID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if err == nil { assert.NotEmpty(t, token.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) @@ -1454,82 +2240,230 @@ func TestRefreshToken(t *testing.T) { } } +func TestGenerateResetToken(t *testing.T) { + svc, cRepo, auth, _ := newService(true) + + cases := []struct { + desc string + email string + host string + retrieveByIdentityResponse mgclients.Client + issueResponse *magistrala.Token + retrieveByIdentityErr error + issueErr error + err error + }{ + { + desc: "generate reset token for existing client", + email: "existingemail@example.com", + host: "examplehost", + retrieveByIdentityResponse: client, + issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + err: nil, + }, + { + desc: "generate reset token for client with non-existing client", + email: "example@example.com", + host: "examplehost", + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + Credentials: mgclients.Credentials{ + Identity: "", + }, + }, + retrieveByIdentityErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + { + desc: "generate reset token with failed to issue token", + email: "existingemail@example.com", + host: "examplehost", + retrieveByIdentityResponse: client, + issueResponse: &magistrala.Token{}, + issueErr: svcerr.ErrAuthorization, + err: users.ErrRecoveryToken, + }, + } + + for _, tc := range cases { + repoCall := cRepo.On("RetrieveByIdentity", context.Background(), tc.email).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) + repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + err := svc.GenerateResetToken(context.Background(), tc.email, tc.host) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), tc.email) + repoCall.Unset() + repoCall1.Unset() + } +} + func TestResetSecret(t *testing.T) { - cRepo := new(mocks.Repository) - auth := new(authmocks.Service) - e := mocks.NewEmailer() - svc := users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, true) + svc, cRepo, auth, _ := newService(true) + + client := mgclients.Client{ + ID: "clientID", + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + Secret: "Strongsecret", + }, + } cases := []struct { - desc string - resetToken string - secret string - client mgclients.Client - err error + desc string + token string + newSecret string + identifyResponse *magistrala.IdentityRes + retrieveByIDResponse mgclients.Client + updateSecretResponse mgclients.Client + identifyErr error + retrieveByIDErr error + updateSecretErr error + err error }{ { - desc: "reset secret with valid reset token for an existing client", - resetToken: validToken, - client: client, - secret: secret, - err: nil, + desc: "reset secret with successfully", + token: validToken, + newSecret: "newStrongSecret", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + updateSecretResponse: mgclients.Client{ + ID: "clientID", + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + Secret: "newStrongSecret", + }, + }, + err: nil, + }, + { + desc: "reset secret with invalid token", + token: inValidToken, + newSecret: "newStrongSecret", + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "reset secret with invalid ID", + token: validToken, + newSecret: "newStrongSecret", + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + retrieveByIDResponse: mgclients.Client{}, + retrieveByIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "reset secret with empty identity", + token: validToken, + newSecret: "newStrongSecret", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: mgclients.Client{ + ID: "clientID", + Credentials: mgclients.Credentials{ + Identity: "", + }, + }, + err: errors.ErrNotFound, }, { - desc: "reset secret with valid reset token for an non-existing client", - resetToken: validToken, - client: mgclients.Client{}, - secret: secret, - err: repoerr.ErrNotFound, + desc: "reset secret with invalid secret format", + token: validToken, + newSecret: "weak", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + err: users.ErrPasswordFormat, }, { - desc: "reset secret with wrong password format for an existing client", - resetToken: validToken, - client: client, - secret: weakSecret, - err: errors.ErrAuthentication, + desc: "reset secret with failed to update secret", + token: validToken, + newSecret: "newStrongSecret", + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + updateSecretResponse: mgclients.Client{}, + updateSecretErr: svcerr.ErrUpdateEntity, + err: svcerr.ErrAuthorization, }, { - desc: "reset secret with invalid reset token for an existing client", - resetToken: inValidToken, - client: client, - secret: secret, - err: svcerr.ErrAuthentication, + desc: "reset secret with a too long secret", + token: validToken, + newSecret: strings.Repeat("strongSecret", 10), + identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, + retrieveByIDResponse: client, + err: errHashPassword, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: client.ID}, nil) - if tc.resetToken == inValidToken { - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: inValidToken}).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) - } - repoCall1 := cRepo.On("RetrieveByID", context.Background(), client.ID).Return(tc.client, tc.err) - repoCall2 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.client, tc.err) - err := svc.ResetSecret(context.Background(), tc.resetToken, tc.secret) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) + repoCall2 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateSecretErr) + err := svc.ResetSecret(context.Background(), tc.token, tc.newSecret) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.client.ID) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall2.Parent.AssertCalled(t, "UpdateSecret", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("UpdateSecret was not called on %s", tc.desc)) - } + + repoCall2.Parent.AssertCalled(t, "UpdateSecret", context.Background(), mock.Anything) + repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), client.ID) + repoCall.Parent.AssertCalled(t, "Identify", mock.Anything, mock.Anything) repoCall.Unset() - repoCall1.Unset() repoCall2.Unset() + repoCall1.Unset() } } -func clientsToUUIDs(clients []mgclients.Client) []string { - ids := []string{} - for _, c := range clients { - ids = append(ids, c.ID) +func TestViewProfile(t *testing.T) { + svc, cRepo, auth, _ := newService(true) + + client := mgclients.Client{ + ID: "clientID", + Credentials: mgclients.Credentials{ + Identity: "existingIdentity", + Secret: "Strongsecret", + }, } - return ids -} + cases := []struct { + desc string + token string + client mgclients.Client + identifyResponse *magistrala.IdentityRes + retrieveByIDResponse mgclients.Client + identifyErr error + retrieveByIDErr error + err error + }{ + { + desc: "view profile successfully", + token: validToken, + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: validID}, + retrieveByIDResponse: client, + err: nil, + }, + { + desc: "view profile with invalid token", + token: inValidToken, + client: client, + identifyResponse: &magistrala.IdentityRes{}, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "view profile with invalid ID", + token: validToken, + client: client, + identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, + retrieveByIDResponse: mgclients.Client{}, + retrieveByIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, mock.Anything).Return(tc.identifyResponse, tc.identifyErr) + repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) + _, err := svc.ViewProfile(context.Background(), tc.token) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) -func prefixClientUUIDSWithDomain(clients []mgclients.Client) []string { - ids := []string{} - for _, c := range clients { - ids = append(ids, fmt.Sprintf("%s_%s", domainID, c.ID)) + repoCall.Parent.AssertCalled(t, "Identify", mock.Anything, mock.Anything) + repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), mock.Anything) + repoCall.Unset() + repoCall1.Unset() } - return ids } From 863131809899e6d717468c88e68e777a47a0228a Mon Sep 17 00:00:00 2001 From: charlie-jangala <129748315+charlie-jangala@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:34:54 +0000 Subject: [PATCH 20/71] NOISSUE - Limit Max Traces kept in-memory by Jaeger all-in-one (#259) Signed-off-by: Charlie Evans --- docker/.env | 1 + docker/docker-compose.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/docker/.env b/docker/.env index 81761fd3b9..8cc0e95df7 100644 --- a/docker/.env +++ b/docker/.env @@ -63,6 +63,7 @@ MG_JAEGER_FRONTEND=16686 MG_JAEGER_OLTP_HTTP=4318 MG_JAEGER_URL=http://jaeger:4318 MG_JAEGER_TRACE_RATIO=1.0 +MG_JAEGER_MEMORY_MAX_TRACES=5000 ## Call home MG_SEND_TELEMETRY=true diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f23eaaa89c..417c72b3ff 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -462,6 +462,7 @@ services: container_name: magistrala-jaeger environment: COLLECTOR_OTLP_ENABLED: ${MG_JAEGER_COLLECTOR_OTLP_ENABLED} + command: --memory.max-traces ${MG_JAEGER_MEMORY_MAX_TRACES} ports: - ${MG_JAEGER_FRONTEND}:${MG_JAEGER_FRONTEND} - ${MG_JAEGER_OLTP_HTTP}:${MG_JAEGER_OLTP_HTTP} From eba62a899853784f43104834d619e63819199931 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:10:20 +0300 Subject: [PATCH 21/71] NOISSUE - Bug Fix on Listing Things (#274) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- things/api/http/endpoints.go | 1 + 1 file changed, 1 insertion(+) diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index 6e90c0fad2..dd86e70f4e 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -109,6 +109,7 @@ func listClientsEndpoint(svc things.Service) endpoint.Endpoint { Permission: req.permission, Metadata: req.metadata, ListPerms: req.listPerms, + Role: mgclients.AllRole, // retrieve all things since things don't have roles } page, err := svc.ListClients(ctx, req.token, req.userID, pm) if err != nil { From e4bbdf6ea4e4397b5faafb1258cd407871a6c650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Fri, 19 Jan 2024 20:10:55 +0100 Subject: [PATCH 22/71] NOISSUE - Update VerneMQ build scripts (#2058) Signed-off-by: Dusan Borovcanin --- docker/vernemq/Dockerfile | 16 +- docker/vernemq/bin/vernemq.sh | 269 +++++++++++++++++++++++++++------- docker/vernemq/files/vm.args | 9 +- 3 files changed, 232 insertions(+), 62 deletions(-) mode change 100644 => 100755 docker/vernemq/bin/vernemq.sh diff --git a/docker/vernemq/Dockerfile b/docker/vernemq/Dockerfile index 99d871777c..76152b1f51 100644 --- a/docker/vernemq/Dockerfile +++ b/docker/vernemq/Dockerfile @@ -2,14 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 # Builder -FROM erlang:24.3.3.0-alpine AS builder +FROM erlang:25.3.2.8-alpine AS builder RUN apk add --update git build-base bsd-compat-headers openssl-dev snappy-dev curl \ - && git clone -b 1.12.5 https://github.com/vernemq/vernemq \ + && git clone -b 1.13.0 https://github.com/vernemq/vernemq \ && cd vernemq \ && make -j 16 rel # Executor -FROM alpine:3.13 +FROM alpine:3.19 COPY --from=builder /vernemq/_build/default/rel / @@ -23,7 +23,7 @@ RUN apk --no-cache --update --available upgrade && \ ENV DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR="app=vernemq" \ DOCKER_VERNEMQ_LOG__CONSOLE=console \ PATH="/vernemq/bin:$PATH" \ - VERNEMQ_VERSION="1.12.5" + VERNEMQ_VERSION="1.13.0" WORKDIR /vernemq @@ -41,16 +41,16 @@ RUN chown -R 10000:10000 /vernemq && \ # 8080 MQTT WebSockets # 44053 VerneMQ Message Distribution # 4369 EPMD - Erlang Port Mapper Daemon -# 8888 Prometheus Metrics +# 8888 Health, API, Prometheus Metrics # 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 Specific Distributed Erlang Port Range EXPOSE 1883 8883 8080 44053 4369 8888 \ - 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 + 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 VOLUME ["/vernemq/log", "/vernemq/data", "/vernemq/etc"] -HEALTHCHECK CMD curl -s -f http://localhost:8888/health || false +HEALTHCHECK CMD vernemq ping | grep -q pong USER vernemq -CMD ["start_vernemq"] +CMD ["start_vernemq"] \ No newline at end of file diff --git a/docker/vernemq/bin/vernemq.sh b/docker/vernemq/bin/vernemq.sh old mode 100644 new mode 100755 index c6a0f12d8f..4c990dafd1 --- a/docker/vernemq/bin/vernemq.sh +++ b/docker/vernemq/bin/vernemq.sh @@ -1,21 +1,66 @@ -#!/usr/bin/env bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 +#!/usr/bin/env sh NET_INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') NET_INTERFACE=${DOCKER_NET_INTERFACE:-${NET_INTERFACE}} IP_ADDRESS=$(ip -4 addr show ${NET_INTERFACE} | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sed -e "s/^[[:space:]]*//" | head -n 1) IP_ADDRESS=${DOCKER_IP_ADDRESS:-${IP_ADDRESS}} +VERNEMQ_ETC_DIR="/vernemq/etc" +VERNEMQ_VM_ARGS_FILE="${VERNEMQ_ETC_DIR}/vm.args" +VERNEMQ_CONF_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf" +VERNEMQ_CONF_LOCAL_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf.local" + +SECRETS_KUBERNETES_DIR="/var/run/secrets/kubernetes.io/serviceaccount" + +# Function to check istio readiness +istio_health() { + cmd=$(curl -s http://localhost:15021/healthz/ready > /dev/null) + status=$? + return $status +} + +# Ensure we have all files and needed directory write permissions +if [ ! -d ${VERNEMQ_ETC_DIR} ]; then + echo "Configuration directory at ${VERNEMQ_ETC_DIR} does not exist, exiting" >&2 + exit 1 +fi +if [ ! -f ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} does not exist, exiting" >&2 + echo "###" >&2 + exit 1 +fi +if [ ! -w ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "# whoami" + whoami + echo "# ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} exists, but there are no write permissions! Exiting." >&2 + echo "###" >&2 + exit 1 +fi +if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 + echo "### Exiting now." >&2 + echo "###" >&2 + exit 1 +fi + # Ensure the Erlang node name is set correctly if env | grep "DOCKER_VERNEMQ_NODENAME" -q; then - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${DOCKER_VERNEMQ_NODENAME}/" /vernemq/etc/vm.args + sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${DOCKER_VERNEMQ_NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} else if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then NODENAME=$(hostname -i) - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${NODENAME}/" /etc/vernemq/vm.args + sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} else - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${IP_ADDRESS}/" /vernemq/etc/vm.args + sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${IP_ADDRESS}/" ${VERNEMQ_VM_ARGS_FILE} fi fi @@ -38,63 +83,112 @@ if env | grep "DOCKER_VERNEMQ_DISCOVERY_NODE" -q; then discovery_node=$tmp fi - sed -i.bak -r "/-eval.+/d" /vernemq/etc/vm.args - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@$discovery_node')\"" >>/vernemq/etc/vm.args + sed -i.bak -r "/-eval.+/d" ${VERNEMQ_VM_ARGS_FILE} + echo "-eval \"vmq_server_cmd:node_join('VerneMQ@$discovery_node')\"" >> ${VERNEMQ_VM_ARGS_FILE} fi # If you encounter "SSL certification error (subject name does not match the host name)", you may try to set DOCKER_VERNEMQ_KUBERNETES_INSECURE to "1". insecure="" if env | grep "DOCKER_VERNEMQ_KUBERNETES_INSECURE" -q; then + echo "Using curl with \"--insecure\" argument to access kubernetes API without matching SSL certificate" insecure="--insecure" fi -if env | grep "DOCKER_VERNEMQ_DISCOVERY_KUBERNETES" -q; then - DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME=${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME:-cluster.local} +if env | grep "DOCKER_VERNEMQ_KUBERNETES_ISTIO_ENABLED" -q; then + istio_health + while [ $status != 0 ]; do + istio_health + sleep 1 + done + echo "Istio ready" +fi + +# Function to call a HTTP GET request on the given URL Path, using the hostname +# of the current k8s cluster name. Usage: "k8sCurlGet /my/path" +function k8sCurlGet () { + local urlPath=$1 + + local hostname="kubernetes.default.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" + local certsFile="${SECRETS_KUBERNETES_DIR}/ca.crt" + local token=$(cat ${SECRETS_KUBERNETES_DIR}/token) + local header="Authorization: Bearer ${token}" + local url="https://${hostname}/${urlPath}" + + curl -sS ${insecure} --cacert ${certsFile} -H "${header}" ${url} \ + || ( echo "### Error on accessing URL ${url}" ) +} + +DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME=${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME:-cluster.local} +if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then # Let's get the namespace if it isn't set - DOCKER_VERNEMQ_KUBERNETES_NAMESPACE=${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)} + DOCKER_VERNEMQ_KUBERNETES_NAMESPACE=${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE:-$(cat "${SECRETS_KUBERNETES_DIR}/namespace")} + + # Check the API access that will be needed in the TERM signal handler + podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) + statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') + statefulSetPath="apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" + statefulSetResponse=$(k8sCurlGet ${statefulSetPath} ) + isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') + if [[ ${isCodeForbidden} == "true" ]]; then + echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" + exit 1 + else + numReplicas=$(echo ${statefulSetResponse} | jq '.status.replicas') + echo "Permissions ok: Our pod $(hostname) belongs to StatefulSet ${statefulSetName} with ${numReplicas} replicas" + fi +fi + +# Set up kubernetes node discovery +start_join_cluster=0 +if env | grep "DOCKER_VERNEMQ_DISCOVERY_KUBERNETES" -q; then # Let's set our nodename correctly - VERNEMQ_KUBERNETES_SUBDOMAIN=${DOCKER_VERNEMQ_KUBERNETES_SUBDOMAIN:-$(curl -X GET $insecure --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.svc.$DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME/api/v1/namespaces/$DOCKER_VERNEMQ_KUBERNETES_NAMESPACE/pods?labelSelector=$DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" | jq '.items[0].spec.subdomain' | sed 's/"//g' | tr '\n' '\0')} - if [ $VERNEMQ_KUBERNETES_SUBDOMAIN == "null" ]; then + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#list-pod-v1-core + podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") + VERNEMQ_KUBERNETES_SUBDOMAIN=${DOCKER_VERNEMQ_KUBERNETES_SUBDOMAIN:-$(echo ${podList} | jq '.items[0].spec.subdomain' | tr '\n' '"' | sed 's/"//g')} + if [[ $VERNEMQ_KUBERNETES_SUBDOMAIN == "null" ]]; then VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} else VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} fi - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${VERNEMQ_KUBERNETES_HOSTNAME}/" /vernemq/etc/vm.args + sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${VERNEMQ_KUBERNETES_HOSTNAME}/" ${VERNEMQ_VM_ARGS_FILE} # Hack into K8S DNS resolution (temporarily) - kube_pod_names=$(curl -X GET $insecure --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.svc.$DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME/api/v1/namespaces/$DOCKER_VERNEMQ_KUBERNETES_NAMESPACE/pods?labelSelector=$DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ') + kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') + for kube_pod_name in $kube_pod_names; do - if [ $kube_pod_name == "null" ]; then + if [[ $kube_pod_name == "null" ]]; then echo "Kubernetes discovery selected, but no pods found. Maybe we're the first?" echo "Anyway, we won't attempt to join any cluster." break fi - if [ $kube_pod_name != $MY_POD_NAME ]; then - echo "Will join an existing Kubernetes cluster with discovery node at ${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}')\"" >>/vernemq/etc/vm.args + if [[ $kube_pod_name != $MY_POD_NAME ]]; then + discoveryHostname="${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" + start_join_cluster=1 + echo "Will join an existing Kubernetes cluster with discovery node at ${discoveryHostname}" + echo "-eval \"vmq_server_cmd:node_join('VerneMQ@${discoveryHostname}')\"" >> ${VERNEMQ_VM_ARGS_FILE} echo "Did I previously leave the cluster? If so, purging old state." - curl -fsSL http://${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}:8888/status.json >/dev/null 2>&1 || + curl -fsSL http://${discoveryHostname}:8888/status.json >/dev/null 2>&1 || (echo "Can't download status.json, better to exit now" && exit 1) - curl -fsSL http://${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}:8888/status.json | grep -q ${VERNEMQ_KUBERNETES_HOSTNAME} || + curl -fsSL http://${discoveryHostname}:8888/status.json | grep -q ${VERNEMQ_KUBERNETES_HOSTNAME} || (echo "Cluster doesn't know about me, this means I've left previously. Purging old state..." && rm -rf /vernemq/data/*) break fi done fi -if [ -f /vernemq/etc/vernemq.conf.local ]; then - cp /vernemq/etc/vernemq.conf.local /vernemq/etc/vernemq.conf - sed -i -r "s/###IPADDRESS###/${IP_ADDRESS}/" /vernemq/etc/vernemq.conf +if [ -f "${VERNEMQ_CONF_LOCAL_FILE}" ]; then + cp "${VERNEMQ_CONF_LOCAL_FILE}" ${VERNEMQ_CONF_FILE} + sed -i -r "s/###IPADDRESS###/${IP_ADDRESS}/" ${VERNEMQ_CONF_FILE} else - sed -i '/########## Start ##########/,/########## End ##########/d' /vernemq/etc/vernemq.conf + sed -i '/########## Start ##########/,/########## End ##########/d' ${VERNEMQ_CONF_FILE} - echo "########## Start ##########" >>/vernemq/etc/vernemq.conf + echo "########## Start ##########" >> ${VERNEMQ_CONF_FILE} - env | grep DOCKER_VERNEMQ | grep -v 'DISCOVERY_NODE\|KUBERNETES\|SWARM\|COMPOSE\|DOCKER_VERNEMQ_USER' | cut -c 16- | awk '{match($0,/^[A-Z0-9_]*/)}{print tolower(substr($0,RSTART,RLENGTH)) substr($0,RLENGTH+1)}' | sed 's/__/./g' >>/vernemq/etc/vernemq.conf + env | grep DOCKER_VERNEMQ | grep -v 'DISCOVERY_NODE\|KUBERNETES\|SWARM\|COMPOSE\|DOCKER_VERNEMQ_USER' | cut -c 16- | awk '{match($0,/^[A-Z0-9_]*/)}{print tolower(substr($0,RSTART,RLENGTH)) substr($0,RLENGTH+1)}' | sed 's/__/./g' >> ${VERNEMQ_CONF_FILE} users_are_set=$(env | grep DOCKER_VERNEMQ_USER) if [ ! -z "$users_are_set" ]; then - echo "vmq_passwd.password_file = /vernemq/etc/vmq.passwd" >>/vernemq/etc/vernemq.conf + echo "vmq_passwd.password_file = /vernemq/etc/vmq.passwd" >> ${VERNEMQ_CONF_FILE} touch /vernemq/etc/vmq.passwd fi @@ -108,34 +202,50 @@ EOF done if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MINIMUM" ]; then - echo "erlang.distribution.port_range.minimum = 9100" >>/vernemq/etc/vernemq.conf + echo "erlang.distribution.port_range.minimum = 9100" >> ${VERNEMQ_CONF_FILE} fi if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MAXIMUM" ]; then - echo "erlang.distribution.port_range.maximum = 9109" >>/vernemq/etc/vernemq.conf + echo "erlang.distribution.port_range.maximum = 9109" >> ${VERNEMQ_CONF_FILE} fi if [ -z "$DOCKER_VERNEMQ_LISTENER__TCP__DEFAULT" ]; then - echo "listener.tcp.default = ${IP_ADDRESS}:1883" >>/vernemq/etc/vernemq.conf + echo "listener.tcp.default = ${IP_ADDRESS}:1883" >> ${VERNEMQ_CONF_FILE} fi if [ -z "$DOCKER_VERNEMQ_LISTENER__WS__DEFAULT" ]; then - echo "listener.ws.default = ${IP_ADDRESS}:8080" >>/vernemq/etc/vernemq.conf + echo "listener.ws.default = ${IP_ADDRESS}:8080" >> ${VERNEMQ_CONF_FILE} fi if [ -z "$DOCKER_VERNEMQ_LISTENER__VMQ__CLUSTERING" ]; then - echo "listener.vmq.clustering = ${IP_ADDRESS}:44053" >>/vernemq/etc/vernemq.conf + echo "listener.vmq.clustering = ${IP_ADDRESS}:44053" >> ${VERNEMQ_CONF_FILE} fi if [ -z "$DOCKER_VERNEMQ_LISTENER__HTTP__METRICS" ]; then - echo "listener.http.metrics = ${IP_ADDRESS}:8888" >>/vernemq/etc/vernemq.conf + echo "listener.http.metrics = ${IP_ADDRESS}:8888" >> ${VERNEMQ_CONF_FILE} fi - echo "########## End ##########" >>/vernemq/etc/vernemq.conf + echo "########## End ##########" >> ${VERNEMQ_CONF_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_PORTS" ]; then + sed -i.bak -r "s/\+Q.+/\+Q ${DOCKER_VERNEMQ_ERLANG__MAX_PORTS}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT" ]; then + sed -i.bak -r "s/\+P.+/\+P ${DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES" ]; then + sed -i.bak -r "s/\+e.+/\+e ${DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE" ]; then + sed -i.bak -r "s/\+zdbbl.+/\+zdbbl ${DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE}/" ${VERNEMQ_VM_ARGS_FILE} fi # Check configuration file -/vernemq/bin/vernemq config generate 2>&1 >/dev/null | tee /tmp/config.out | grep error +/vernemq/bin/vernemq config generate 2>&1 > /dev/null | tee /tmp/config.out | grep error if [ $? -ne 1 ]; then echo "configuration error, exit" @@ -153,28 +263,77 @@ siguser1_handler() { # SIGTERM-handler sigterm_handler() { if [ $pid -ne 0 ]; then - # this will stop the VerneMQ process, but first drain the node from all existing client sessions (-k) - if [ -n "$VERNEMQ_KUBERNETES_HOSTNAME" ]; then - terminating_node_name=VerneMQ@$VERNEMQ_KUBERNETES_HOSTNAME - elif [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - terminating_node_name=VerneMQ@$(hostname -i) - else - terminating_node_name=VerneMQ@$IP_ADDRESS - fi - kube_pod_names=$(curl -X GET $insecure --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.svc.$DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME/api/v1/namespaces/$DOCKER_VERNEMQ_KUBERNETES_NAMESPACE/pods?labelSelector=$DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ') - if [ $kube_pod_names == $MY_POD_NAME ]; then - echo "I'm the only pod remaining, not performing leave and state purge." - /vernemq/bin/vmq-admin node stop >/dev/null + if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then + # this will stop the VerneMQ process, but first drain the node from all existing client sessions (-k) + if [ -n "$VERNEMQ_KUBERNETES_HOSTNAME" ]; then + terminating_node_name=VerneMQ@$VERNEMQ_KUBERNETES_HOSTNAME + else + terminating_node_name=VerneMQ@$IP_ADDRESS + fi + podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") + kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') + if [ "$kube_pod_names" = "$MY_POD_NAME" ]; then + echo "I'm the only pod remaining. Not performing leave and/or state purge." + /vernemq/bin/vmq-admin node stop >/dev/null + else + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-pod-v1-core + podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) + statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') + + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#-strong-read-operations-statefulset-v1-apps-strong- + statefulSetResponse=$(k8sCurlGet "apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" ) + + isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') + if [[ ${isCodeForbidden} == "true" ]]; then + echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" + fi + + reschedule=$(echo ${statefulSetResponse} | jq '.status.replicas == .status.readyReplicas') + scaled_down=$(echo ${statefulSetResponse} | jq '.status.currentReplicas == .status.updatedReplicas') + + if [[ $reschedule == "true" ]]; then + # Perhaps is an scale down? + if [[ $scaled_down == "true" ]]; then + echo "Seems that this is a scale down scenario. Leaving cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + else + echo "Reschedule is true. Not leaving the cluster." + /vernemq/bin/vmq-admin node stop >/dev/null + fi + else + echo "Reschedule is false. Leaving the cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + fi + fi else - /vernemq/bin/vmq-admin cluster leave node=$terminating_node_name -k && rm -rf /vernemq/data/* + if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then + terminating_node_name=VerneMQ@$(hostname -i) + # For Swarm we keep the old "cluster leave" approach for now + echo "Swarm node is leaving the cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + else + # In non-k8s mode: Stop the vernemq node gracefully /vernemq/bin/vmq-admin node stop >/dev/null + fi fi - sleep 5 kill -s TERM ${pid} - exit 0 + WAITFOR_PID=${pid} + pid=0 + wait ${WAITFOR_PID} fi + exit 143; # 128 + 15 -- SIGTERM } +if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 + echo "### Exiting now." >&2 + echo "###" >&2 + exit 1 +fi + # Setup OS signal handlers trap 'siguser1_handler' SIGUSR1 trap 'sigterm_handler' SIGTERM @@ -182,4 +341,12 @@ trap 'sigterm_handler' SIGTERM # Start VerneMQ /vernemq/bin/vernemq console -noshell -noinput $@ & pid=$! +if [ $start_join_cluster -eq 1 ]; then + mkdir -p /var/log/vernemq/log + join_cluster > /var/log/vernemq/log/join_cluster.log & +fi +if [ -n "$API_KEY" ]; then + sleep 10 && echo "Adding API_KEY..." && /vernemq/bin/vmq-admin api-key add key="${API_KEY:-DEFAULT}" + vmq-admin api-key show +fi wait $pid diff --git a/docker/vernemq/files/vm.args b/docker/vernemq/files/vm.args index 2b22bb9033..afb3c022bb 100644 --- a/docker/vernemq/files/vm.args +++ b/docker/vernemq/files/vm.args @@ -1,12 +1,15 @@ -+P 256000 --env ERL_MAX_ETS_TABLES 256000 ++P 512000 ++e 256000 -env ERL_CRASH_DUMP /erl_crash.dump -env ERL_FULLSWEEP_AFTER 0 --env ERL_MAX_PORTS 256000 ++Q 512000 +A 64 -setcookie vmq -name VerneMQ@127.0.0.1 +K true +W w ++sbwt none ++sbwtdcpu none ++sbwtdio none -smp enable +zdbbl 32768 From cd273309afde249dd1ff20e63e507b48c2276c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Tue, 23 Jan 2024 13:09:07 +0100 Subject: [PATCH 23/71] NOISSUE - Update dependencies (#2061) Signed-off-by: Dusan Borovcanin --- .github/workflows/check-license.yaml | 2 +- auth/postgres/setup_test.go | 20 +- bootstrap/events/producer/setup_test.go | 9 +- bootstrap/postgres/setup_test.go | 23 +- certs/postgres/setup_test.go | 23 +- consumers/notifiers/postgres/setup_test.go | 20 +- consumers/writers/cassandra/setup_test.go | 12 +- consumers/writers/influxdb/setup_test.go | 36 +- consumers/writers/mongodb/setup_test.go | 19 +- consumers/writers/postgres/setup_test.go | 20 +- consumers/writers/timescale/setup_test.go | 20 +- docker/addons/bootstrap/docker-compose.yml | 2 +- .../cassandra-writer/docker-compose.yml | 4 +- docker/addons/certs/docker-compose.yml | 2 +- .../addons/influxdb-writer/docker-compose.yml | 2 +- docker/addons/lora-adapter/docker-compose.yml | 2 +- .../addons/mongodb-writer/docker-compose.yml | 2 +- .../addons/opcua-adapter/docker-compose.yml | 2 +- .../addons/postgres-writer/docker-compose.yml | 2 +- docker/addons/prometheus/docker-compose.yml | 4 +- .../addons/smpp-notifier/docker-compose.yml | 2 +- .../addons/smtp-notifier/docker-compose.yml | 2 +- .../timescale-writer/docker-compose.yml | 2 +- docker/addons/twins/docker-compose.yml | 2 +- docker/addons/vault/docker-compose.yml | 2 +- docker/brokers/nats.yml | 2 +- docker/brokers/rabbitmq.yml | 2 +- docker/docker-compose.yml | 20 +- docker/es/docker-compose.yml | 2 +- go.mod | 149 ++-- go.sum | 707 +++--------------- internal/groups/postgres/setup_test.go | 2 +- invitations/postgres/setup_test.go | 2 +- pkg/clients/postgres/setup_test.go | 2 +- pkg/events/nats/setup_test.go | 2 +- pkg/events/rabbitmq/setup_test.go | 2 +- pkg/events/redis/setup_test.go | 3 +- pkg/messaging/mqtt/setup_test.go | 2 +- pkg/messaging/nats/setup_test.go | 2 +- pkg/messaging/rabbitmq/setup_test.go | 2 +- readers/api/transport.go | 1 - readers/cassandra/setup_test.go | 12 +- readers/influxdb/setup_test.go | 35 +- readers/mongodb/setup_test.go | 19 +- readers/postgres/setup_test.go | 21 +- readers/timescale/setup_test.go | 20 +- things/postgres/setup_test.go | 2 +- twins/mongodb/setup_test.go | 2 +- users/postgres/setup_test.go | 2 +- 49 files changed, 418 insertions(+), 833 deletions(-) diff --git a/.github/workflows/check-license.yaml b/.github/workflows/check-license.yaml index fea361d253..4423181ffe 100644 --- a/.github/workflows/check-license.yaml +++ b/.github/workflows/check-license.yaml @@ -20,7 +20,7 @@ jobs: - name: Check License Header run: | - CHECK=$(grep -rcL --exclude-dir={.git,build} \ + CHECK=$(grep -rcL --exclude-dir={.git,build,**vernemq**} \ --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ --exclude={CODEOWNERS,LICENSE,MAINTAINERS} \ --regexp "Copyright (c) Abstract Machines" .) diff --git a/auth/postgres/setup_test.go b/auth/postgres/setup_test.go index e12f8df7e2..24d08acc6b 100644 --- a/auth/postgres/setup_test.go +++ b/auth/postgres/setup_test.go @@ -18,6 +18,7 @@ import ( "github.com/absmach/magistrala/internal/postgres" "github.com/jmoiron/sqlx" dockertest "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" "go.opentelemetry.io/otel" ) @@ -33,12 +34,19 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/bootstrap/events/producer/setup_test.go b/bootstrap/events/producer/setup_test.go index 4efa450057..d19f5511a7 100644 --- a/bootstrap/events/producer/setup_test.go +++ b/bootstrap/events/producer/setup_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -25,7 +26,13 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - container, err := pool.Run("redis", "7.2.0-alpine", nil) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "redis", + Tag: "7.2.4-alpine", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/bootstrap/postgres/setup_test.go b/bootstrap/postgres/setup_test.go index 6bc0c324d0..a501f45843 100644 --- a/bootstrap/postgres/setup_test.go +++ b/bootstrap/postgres/setup_test.go @@ -5,6 +5,7 @@ package postgres_test import ( "fmt" + "log" "os" "testing" @@ -13,6 +14,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -26,14 +28,21 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port := container.GetPort("5432/tcp") diff --git a/certs/postgres/setup_test.go b/certs/postgres/setup_test.go index 803411b5ad..000d7522c8 100644 --- a/certs/postgres/setup_test.go +++ b/certs/postgres/setup_test.go @@ -6,6 +6,7 @@ package postgres_test import ( "database/sql" "fmt" + "log" "os" "testing" @@ -14,6 +15,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -28,14 +30,21 @@ func TestMain(m *testing.M) { return } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port := container.GetPort("5432/tcp") diff --git a/consumers/notifiers/postgres/setup_test.go b/consumers/notifiers/postgres/setup_test.go index cb89d1ad4b..2851594d0f 100644 --- a/consumers/notifiers/postgres/setup_test.go +++ b/consumers/notifiers/postgres/setup_test.go @@ -17,6 +17,7 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -30,12 +31,19 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/consumers/writers/cassandra/setup_test.go b/consumers/writers/cassandra/setup_test.go index ad1577a94e..747bd6363d 100644 --- a/consumers/writers/cassandra/setup_test.go +++ b/consumers/writers/cassandra/setup_test.go @@ -5,6 +5,7 @@ package cassandra_test import ( "fmt" + "log" "os" "testing" @@ -12,6 +13,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/gocql/gocql" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var logger, _ = mglog.New(os.Stdout, "info") @@ -22,9 +24,15 @@ func TestMain(m *testing.M) { logger.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - container, err := pool.Run("cassandra", "3.11.10", []string{}) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "cassandra", + Tag: "3.11.16", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - logger.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port := container.GetPort("9042/tcp") diff --git a/consumers/writers/influxdb/setup_test.go b/consumers/writers/influxdb/setup_test.go index 4299b86fac..630a9c6b78 100644 --- a/consumers/writers/influxdb/setup_test.go +++ b/consumers/writers/influxdb/setup_test.go @@ -15,6 +15,7 @@ import ( influxdata "github.com/influxdata/influxdb-client-go/v2" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) const ( @@ -27,8 +28,8 @@ const ( dbFluxEnabled = "true" dbBindAddress = ":8088" port = "8086/tcp" - broker = "influxdb" - brokerVersion = "2.2-alpine" + db = "influxdb" + dbVersion = "2.7-alpine" poolMaxWait = 120 * time.Second ) @@ -40,21 +41,26 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - fmt.Sprintf("DOCKER_INFLUXDB_INIT_MODE=%s", dbInitMode), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_USERNAME=%s", dbAdmin), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_PASSWORD=%s", dbPass), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_ORG=%s", dbOrg), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_BUCKET=%s", dbBucket), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=%s", dbToken), - fmt.Sprintf("INFLUXDB_HTTP_FLUX_ENABLED=%s", dbFluxEnabled), - fmt.Sprintf("INFLUXDB_BIND_ADDRESS=%s", dbBindAddress), - } - container, err := pool.Run(broker, brokerVersion, cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: db, + Tag: dbVersion, + Env: []string{ + fmt.Sprintf("DOCKER_INFLUXDB_INIT_MODE=%s", dbInitMode), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_USERNAME=%s", dbAdmin), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_PASSWORD=%s", dbPass), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_ORG=%s", dbOrg), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_BUCKET=%s", dbBucket), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=%s", dbToken), + fmt.Sprintf("INFLUXDB_HTTP_FLUX_ENABLED=%s", dbFluxEnabled), + fmt.Sprintf("INFLUXDB_BIND_ADDRESS=%s", dbBindAddress), + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } - handleInterrupt(m, pool, container) address = fmt.Sprintf("%s:%s", "http://localhost", container.GetPort(port)) diff --git a/consumers/writers/mongodb/setup_test.go b/consumers/writers/mongodb/setup_test.go index 68c41f352a..2d1dfaeb0d 100644 --- a/consumers/writers/mongodb/setup_test.go +++ b/consumers/writers/mongodb/setup_test.go @@ -6,10 +6,12 @@ package mongodb_test import ( "context" "fmt" + "log" "os" "testing" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -20,13 +22,18 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - "MONGO_INITDB_DATABASE=test", - } - - container, err := pool.Run("mongo", "4.4.6", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "mongo", + Tag: "7.0.5", + Env: []string{ + "MONGO_INITDB_DATABASE=test", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port = container.GetPort("27017/tcp") diff --git a/consumers/writers/postgres/setup_test.go b/consumers/writers/postgres/setup_test.go index 2a62d1783d..c500127604 100644 --- a/consumers/writers/postgres/setup_test.go +++ b/consumers/writers/postgres/setup_test.go @@ -15,6 +15,7 @@ import ( pgclient "github.com/absmach/magistrala/internal/clients/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var db *sqlx.DB @@ -25,12 +26,19 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/consumers/writers/timescale/setup_test.go b/consumers/writers/timescale/setup_test.go index 13a21ce370..6a07cfb593 100644 --- a/consumers/writers/timescale/setup_test.go +++ b/consumers/writers/timescale/setup_test.go @@ -15,6 +15,7 @@ import ( pgclient "github.com/absmach/magistrala/internal/clients/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var db *sqlx.DB @@ -25,12 +26,19 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("timescale/timescaledb", "2.4.0-pg12", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "timescale/timescaledb", + Tag: "2.13.1-pg16", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/docker/addons/bootstrap/docker-compose.yml b/docker/addons/bootstrap/docker-compose.yml index abc513fd4a..b9acd52e68 100644 --- a/docker/addons/bootstrap/docker-compose.yml +++ b/docker/addons/bootstrap/docker-compose.yml @@ -17,7 +17,7 @@ volumes: services: bootstrap-db: - image: postgres:13.3-alpine + image: postgres:16.1-alpine container_name: magistrala-bootstrap-db restart: on-failure environment: diff --git a/docker/addons/cassandra-writer/docker-compose.yml b/docker/addons/cassandra-writer/docker-compose.yml index 00305b3085..835658bf06 100644 --- a/docker/addons/cassandra-writer/docker-compose.yml +++ b/docker/addons/cassandra-writer/docker-compose.yml @@ -16,7 +16,7 @@ volumes: services: cassandra: - image: cassandra:3.11.10 + image: cassandra:3.11.16 container_name: magistrala-cassandra restart: on-failure networks: @@ -27,7 +27,7 @@ services: - magistrala-cassandra-volume:/var/lib/cassandra cassandra-init-keyspace: - image: cassandra:3.11.10 + image: cassandra:3.11.16 depends_on: - cassandra restart: on-failure diff --git a/docker/addons/certs/docker-compose.yml b/docker/addons/certs/docker-compose.yml index b90e020c7a..f5b1591a8e 100644 --- a/docker/addons/certs/docker-compose.yml +++ b/docker/addons/certs/docker-compose.yml @@ -17,7 +17,7 @@ volumes: services: certs-db: - image: postgres:13.3-alpine + image: postgres:16.1-alpine container_name: magistrala-certs-db restart: on-failure environment: diff --git a/docker/addons/influxdb-writer/docker-compose.yml b/docker/addons/influxdb-writer/docker-compose.yml index fb3f130a50..abe20ba1bd 100644 --- a/docker/addons/influxdb-writer/docker-compose.yml +++ b/docker/addons/influxdb-writer/docker-compose.yml @@ -16,7 +16,7 @@ volumes: services: influxdb: - image: influxdb:2.5 + image: influxdb:2.7-alpine container_name: magistrala-influxdb restart: on-failure environment: diff --git a/docker/addons/lora-adapter/docker-compose.yml b/docker/addons/lora-adapter/docker-compose.yml index 158a213769..d77e6336c7 100644 --- a/docker/addons/lora-adapter/docker-compose.yml +++ b/docker/addons/lora-adapter/docker-compose.yml @@ -13,7 +13,7 @@ networks: services: lora-redis: - image: redis:7.2.0-alpine + image: redis:7.2.4-alpine container_name: magistrala-lora-redis restart: on-failure networks: diff --git a/docker/addons/mongodb-writer/docker-compose.yml b/docker/addons/mongodb-writer/docker-compose.yml index 93ca1009df..dc7c439d95 100644 --- a/docker/addons/mongodb-writer/docker-compose.yml +++ b/docker/addons/mongodb-writer/docker-compose.yml @@ -19,7 +19,7 @@ volumes: services: mongodb: - image: mongo:4.4.6 + image: mongo:7.0.5 container_name: magistrala-mongodb restart: on-failure environment: diff --git a/docker/addons/opcua-adapter/docker-compose.yml b/docker/addons/opcua-adapter/docker-compose.yml index 42ac2e6eb8..a2088c8c25 100644 --- a/docker/addons/opcua-adapter/docker-compose.yml +++ b/docker/addons/opcua-adapter/docker-compose.yml @@ -17,7 +17,7 @@ volumes: services: opcua-redis: - image: redis:7.2.0-alpine + image: redis:7.2.4-alpine container_name: magistrala-opcua-redis restart: on-failure networks: diff --git a/docker/addons/postgres-writer/docker-compose.yml b/docker/addons/postgres-writer/docker-compose.yml index bebf5a744b..a2bf9feda7 100644 --- a/docker/addons/postgres-writer/docker-compose.yml +++ b/docker/addons/postgres-writer/docker-compose.yml @@ -18,7 +18,7 @@ volumes: services: postgres: - image: postgres:13.3-alpine + image: postgres:16.1-alpine container_name: magistrala-postgres restart: on-failure environment: diff --git a/docker/addons/prometheus/docker-compose.yml b/docker/addons/prometheus/docker-compose.yml index a99e419a53..1b3668ebca 100644 --- a/docker/addons/prometheus/docker-compose.yml +++ b/docker/addons/prometheus/docker-compose.yml @@ -17,7 +17,7 @@ volumes: services: promethues: - image: prom/prometheus:v2.42.0 + image: prom/prometheus:v2.49.1 container_name: magistrala-prometheus restart: on-failure ports: @@ -31,7 +31,7 @@ services: - magistrala-prometheus-volume:/prometheus grafana: - image: grafana/grafana:9.4.7 + image: grafana/grafana:10.2.3 container_name: magistrala-grafana depends_on: - promethues diff --git a/docker/addons/smpp-notifier/docker-compose.yml b/docker/addons/smpp-notifier/docker-compose.yml index 84d9997363..6e095f1b04 100644 --- a/docker/addons/smpp-notifier/docker-compose.yml +++ b/docker/addons/smpp-notifier/docker-compose.yml @@ -16,7 +16,7 @@ volumes: services: smpp-notifier-db: - image: postgres:10.2-alpine + image: postgres:16.1-alpine container_name: magistrala-smpp-notifier-db restart: on-failure environment: diff --git a/docker/addons/smtp-notifier/docker-compose.yml b/docker/addons/smtp-notifier/docker-compose.yml index 7ecbecdd3c..39d9bc15fd 100644 --- a/docker/addons/smtp-notifier/docker-compose.yml +++ b/docker/addons/smtp-notifier/docker-compose.yml @@ -16,7 +16,7 @@ volumes: services: smtp-notifier-db: - image: postgres:10.2-alpine + image: postgres:16.1-alpine container_name: magistrala-smtp-notifier-db restart: on-failure environment: diff --git a/docker/addons/timescale-writer/docker-compose.yml b/docker/addons/timescale-writer/docker-compose.yml index 2153c2cdfe..9b3c7a951b 100644 --- a/docker/addons/timescale-writer/docker-compose.yml +++ b/docker/addons/timescale-writer/docker-compose.yml @@ -18,7 +18,7 @@ volumes: services: timescale: - image: timescale/timescaledb:2.4.0-pg12 + image: timescale/timescaledb:2.13.1-pg16 container_name: magistrala-timescale restart: on-failure environment: diff --git a/docker/addons/twins/docker-compose.yml b/docker/addons/twins/docker-compose.yml index bb7e407925..814dc5f4c9 100644 --- a/docker/addons/twins/docker-compose.yml +++ b/docker/addons/twins/docker-compose.yml @@ -17,7 +17,7 @@ volumes: services: twins-redis: - image: redis:7.2.0-alpine + image: redis:7.2.4-alpine container_name: magistrala-twins-redis restart: on-failure networks: diff --git a/docker/addons/vault/docker-compose.yml b/docker/addons/vault/docker-compose.yml index cdc321c27b..0158a0dce5 100644 --- a/docker/addons/vault/docker-compose.yml +++ b/docker/addons/vault/docker-compose.yml @@ -18,7 +18,7 @@ volumes: services: vault: - image: vault:1.12.2 + image: hashicorp/vault:1.15.4 container_name: magistrala-vault ports: - ${MG_VAULT_PORT}:8200 diff --git a/docker/brokers/nats.yml b/docker/brokers/nats.yml index 6a33b2cb60..264a5f3d2a 100644 --- a/docker/brokers/nats.yml +++ b/docker/brokers/nats.yml @@ -3,7 +3,7 @@ services: broker: - image: nats:2.9.21-alpine + image: nats:2.10.9-alpine command: "--config=/etc/nats/nats.conf" volumes: - ./../nats/:/etc/nats diff --git a/docker/brokers/rabbitmq.yml b/docker/brokers/rabbitmq.yml index 13fe1b87c3..109f7df186 100644 --- a/docker/brokers/rabbitmq.yml +++ b/docker/brokers/rabbitmq.yml @@ -3,7 +3,7 @@ services: broker: - image: rabbitmq:3.9.20-management-alpine + image: rabbitmq:3.12.12-management-alpine environment: RABBITMQ_ERLANG_COOKIE: ${MG_RABBITMQ_COOKIE} RABBITMQ_DEFAULT_USER: ${MG_RABBITMQ_USER} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 417c72b3ff..82c9b8dcf0 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -27,7 +27,7 @@ include: services: spicedb: - image: "authzed/spicedb" + image: "authzed/spicedb:v1.29.0" container_name: magistrala-spicedb command: "serve" restart: "always" @@ -45,7 +45,7 @@ services: - spicedb-migrate spicedb-migrate: - image: "authzed/spicedb" + image: "authzed/spicedb:v1.29.0" container_name: magistrala-spicedb-migrate command: "migrate head" restart: "on-failure" @@ -58,7 +58,7 @@ services: - spicedb-db spicedb-db: - image: "postgres:15.3-alpine" + image: "postgres:16.1-alpine" container_name: magistrala-spicedb-db networks: - magistrala-base-net @@ -72,7 +72,7 @@ services: - magistrala-spicedb-db-volume:/var/lib/postgresql/data auth-db: - image: postgres:13.3-alpine + image: postgres:16.1-alpine container_name: magistrala-auth-db restart: on-failure ports: @@ -161,7 +161,7 @@ services: create_host_path: true invitations-db: - image: postgres:15.3-alpine + image: postgres:16.1-alpine container_name: magistrala-invitations-db restart: on-failure command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" @@ -232,7 +232,7 @@ services: create_host_path: true nginx: - image: nginx:1.23.3-alpine + image: nginx:1.25.0-alpine container_name: magistrala-nginx restart: on-failure volumes: @@ -261,7 +261,7 @@ services: - ws-adapter things-db: - image: postgres:13.3-alpine + image: postgres:16.1-alpine container_name: magistrala-things-db restart: on-failure command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" @@ -278,7 +278,7 @@ services: - magistrala-things-db-volume:/var/lib/postgresql/data things-redis: - image: redis:6.2.2-alpine + image: redis:7.2.4-alpine container_name: magistrala-things-redis restart: on-failure networks: @@ -373,7 +373,7 @@ services: create_host_path: true users-db: - image: postgres:15.1-alpine + image: postgres:16.1-alpine container_name: magistrala-users-db restart: on-failure command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" @@ -458,7 +458,7 @@ services: create_host_path: true jaeger: - image: jaegertracing/all-in-one:1.38.0 + image: jaegertracing/all-in-one:1.53.0 container_name: magistrala-jaeger environment: COLLECTOR_OTLP_ENABLED: ${MG_JAEGER_COLLECTOR_OTLP_ENABLED} diff --git a/docker/es/docker-compose.yml b/docker/es/docker-compose.yml index 897b0493a5..719b2c06a3 100644 --- a/docker/es/docker-compose.yml +++ b/docker/es/docker-compose.yml @@ -6,7 +6,7 @@ volumes: services: es-redis: - image: redis:7.2.0-alpine + image: redis:7.2.4-alpine container_name: magistrala-es-redis restart: on-failure networks: diff --git a/go.mod b/go.mod index a789885131..9ec172a1bd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/absmach/magistrala go 1.21 require ( - github.com/0x6flab/namegenerator v1.1.0 + github.com/0x6flab/namegenerator v1.2.0 github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd github.com/absmach/mproxy v0.4.2 github.com/absmach/senml v1.0.5 @@ -13,9 +13,9 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/docker/docker v24.0.7+incompatible github.com/eclipse/paho.mqtt.golang v1.4.3 - github.com/fatih/color v1.15.0 + github.com/fatih/color v1.16.0 github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba - github.com/go-chi/chi/v5 v5.0.10 + github.com/go-chi/chi/v5 v5.0.11 github.com/go-kit/kit v0.13.0 github.com/go-redis/redis/v8 v8.11.5 github.com/gocql/gocql v1.6.0 @@ -25,131 +25,98 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/hashicorp/vault/api v1.10.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f - github.com/influxdata/influxdb-client-go/v2 v2.12.3 + github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/ivanpirog/coloredcobra v1.0.1 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa - github.com/jackc/pgtype v1.14.0 - github.com/jackc/pgx/v5 v5.4.3 + github.com/jackc/pgtype v1.14.1 + github.com/jackc/pgx/v5 v5.5.2 github.com/jmoiron/sqlx v1.3.5 - github.com/lestrrat-go/jwx/v2 v2.0.16 + github.com/lestrrat-go/jwx/v2 v2.0.19 github.com/mitchellh/mapstructure v1.5.0 - github.com/nats-io/nats.go v1.31.0 + github.com/nats-io/nats.go v1.32.0 github.com/oklog/ulid/v2 v2.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/pelletier/go-toml v1.9.5 github.com/plgd-dev/go-coap/v2 v2.6.0 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.18.0 github.com/rabbitmq/amqp091-go v1.9.0 - github.com/rubenv/sql-migrate v1.5.2 - github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.17.0 + github.com/rubenv/sql-migrate v1.6.1 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 - go.mongodb.org/mongo-driver v1.12.1 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 - go.opentelemetry.io/otel v1.20.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 - go.opentelemetry.io/otel/sdk v1.20.0 - go.opentelemetry.io/otel/trace v1.20.0 + go.mongodb.org/mongo-driver v1.13.1 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 + go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 + go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/trace v1.22.0 golang.org/x/crypto v0.18.0 golang.org/x/net v0.20.0 golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac + google.golang.org/grpc v1.60.1 + google.golang.org/protobuf v1.32.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect - github.com/CloudyKit/jet/v6 v6.2.0 // indirect - github.com/Joker/jade v1.1.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bitly/go-hostpool v0.1.0 // indirect - github.com/bytedance/sonic v1.10.2 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/deepmap/oapi-codegen v1.16.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/cli v24.0.7+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/golib/memfile v1.0.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/fatih/structs v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.5.0 // indirect - github.com/gorilla/css v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.5 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect - github.com/iris-contrib/schema v0.0.6 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/jzelinskie/stringz v0.0.2 // indirect - github.com/kataras/blocks v0.0.8 // indirect - github.com/kataras/golog v0.1.9 // indirect - github.com/kataras/iris/v12 v12.2.7 // indirect - github.com/kataras/pio v0.0.12 // indirect - github.com/kataras/sitemap v0.0.6 // indirect - github.com/kataras/tunnel v0.0.4 // indirect - github.com/klauspost/compress v1.17.2 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/labstack/echo/v4 v4.11.2 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jzelinskie/stringz v0.0.3 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect @@ -157,53 +124,39 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailgun/raymond/v2 v2.0.48 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/term v0.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect - github.com/nats-io/nkeys v0.4.6 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.10 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pion/dtls/v2 v2.2.9 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/schollz/closestmatch v2.1.0+incompatible // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.10.0 // indirect - github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tdewolff/minify/v2 v2.20.5 // indirect - github.com/tdewolff/parse/v2 v2.7.3 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect @@ -212,21 +165,19 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yosssi/ace v0.0.5 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - go.opentelemetry.io/otel/metric v1.20.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.5.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 743a11ac51..b044da2ddb 100644 --- a/go.sum +++ b/go.sum @@ -1,70 +1,21 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.2 h1:nWEMDhgbBkBJjfpVySqU4jgWdc22PLR0o4vEexZHers= -cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/0x6flab/namegenerator v1.1.0 h1:JSdBX3Y8S8O3QUUzKsS8p3zmNhx26GF2pD+9KgrUtYk= -github.com/0x6flab/namegenerator v1.1.0/go.mod h1:h28wadnJ13Q7PxpUzAHckVj9ToyAEdx5T186Zj1kp+k= +github.com/0x6flab/namegenerator v1.2.0 h1:RuHRO7NDGQpZ9ajRFP5ALcl66cyi0hqjs1fXDV+3pZE= +github.com/0x6flab/namegenerator v1.2.0/go.mod h1:h28wadnJ13Q7PxpUzAHckVj9ToyAEdx5T186Zj1kp+k= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= -github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= -github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= -github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd h1:w6/9rG7Ov0aFz6BuArGW2HjWCHAlB1FyZI278rigR/c= @@ -73,18 +24,12 @@ github.com/absmach/mproxy v0.4.2 h1:u0ORPxSrUknqbVrC+E1MdsCv/7Q5eWNG7clIwOMV5hk= github.com/absmach/mproxy v0.4.2/go.mod h1:TeXhbHdjihXLVoohSzxvIEFzWu16WDOa91LNduks/N8= github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/authzed/authzed-go v0.10.1 h1:0aX2Ox9PPPknID92kLs/FnmhCmfl6Ni16v3ZTLsds5M= github.com/authzed/authzed-go v0.10.1/go.mod h1:ZsaFPCiMjwT0jLW0gCyYzh3elHqhKDDGGRySyykXwqc= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 h1:bQeIwWWRI9bl93poTqpix4sYHi+gnXUPK7N6bMtXzBE= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -94,10 +39,6 @@ github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENU github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -110,19 +51,8 @@ github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= @@ -132,7 +62,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -140,11 +70,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+UPAUXI= -github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= @@ -165,42 +92,28 @@ github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba h1:vBqABUa2HUSc6tj22Tw+ZMVGHuBzKtljM38kbRanmrM= github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba/go.mod h1:VfKFK7fGeCP81xEhbrOqUEh45n73Yy6jaPWwTVbxprI= -github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= -github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= +github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= @@ -216,20 +129,12 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ocf/go-coap/v2 v2.0.4-0.20200728125043-f38b86f047a7/go.mod h1:X9wVKcaOSx7wBxKcvrWgMQq1R2DNeA7NBLW2osIb8TM= github.com/go-ocf/kit v0.0.0-20200728130040-4aebdb6982bc/go.mod h1:TIsoMT/iB7t9P6ahkcOnsmvS83SIJsv9qXRfz/yLf6M= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -237,14 +142,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= @@ -258,32 +155,16 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -291,61 +172,32 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= -github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gopcua/opcua v0.1.6 h1:B9SVRKQGzcWcwP2QPYN93Uku32+3wL+v5cgzBxE6V5I= github.com/gopcua/opcua v0.1.6/go.mod h1:INwnDoRxmNWAt7+tzqxuGqQkSF2c1C69VAL0c2q6AcY= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -358,41 +210,31 @@ github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+ github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= -github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0IpXeMSkY/uJa/O/vC4= -github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= +github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= +github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= -github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= -github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= -github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4= github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= @@ -428,50 +270,32 @@ github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.1 h1:LyDar7M2K0tShCWqzJ/ctzF1QC3Wzc9c8a6cHE0PFdc= +github.com/jackc/pgtype v1.14.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c h1:Dznn52SgVIVst9UyOT9brctYUgxs+CvVfPaC3jKrA50= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA= +github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/jzelinskie/stringz v0.0.2 h1:OSjMEYvz8tjhovgZ/6cGcPID736ubeukr35mu6RYAmg= -github.com/jzelinskie/stringz v0.0.2/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= -github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= -github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk= -github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY= -github.com/kataras/iris/v12 v12.2.7 h1:C9KWZmZT5pB5f2ot1XYWDBdi5XeTz0CGweHRXCDARZg= -github.com/kataras/iris/v12 v12.2.7/go.mod h1:mD76k/tIBFy8pHTFIgUPrVrkI4lTKvFbIcfbStJSBnA= -github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w= -github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY= -github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= -github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= -github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= -github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= +github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -479,15 +303,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -497,12 +316,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE= -github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -513,9 +326,8 @@ github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1m github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.0.2/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= -github.com/lestrrat-go/jwx/v2 v2.0.16 h1:TuH3dBkYTy2giQg/9D8f20znS3JtMRuQJ372boS3lWk= -github.com/lestrrat-go/jwx/v2 v2.0.16/go.mod h1:jBHyESp4e7QxfERM0UKkQ80/94paqNIEcdEfiUYz5zE= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/jwx/v2 v2.0.19 h1:ekv1qEZE6BVct89QA+pRF6+4pCpfVrOnEJnTnT4RXoY= +github.com/lestrrat-go/jwx/v2 v2.0.19/go.mod h1:l3im3coce1lL2cDeAjqmaR+Awx+X8Ih+2k8BuHNJ4CU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= @@ -527,20 +339,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= -github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -551,38 +352,28 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= -github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= -github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= -github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= -github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nats.go v1.32.0 h1:Bx9BZS+aXYlxW08k8Gd3yR2s73pV5XSoAQUyp1Kwvp0= +github.com/nats-io/nats.go v1.32.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -605,12 +396,12 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pion/dtls/v2 v2.0.1-0.20200503085337-8e86b3a7d585/go.mod h1:/GahSOC8ZY/+17zkaGJIG4OUkSGAcZu/N/g3roBOCkM= github.com/pion/dtls/v2 v2.0.10-0.20210502094952-3dc563b9aede/go.mod h1:86wv5dgx2J/z871nUR+5fTTY9tISLUlo+C5Gm86r1Hs= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/dtls/v2 v2.2.9 h1:K+D/aVf9/REahQvqk6G5JavdrD8W1PWDKC11UlwN7ts= +github.com/pion/dtls/v2 v2.2.9/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= @@ -623,7 +414,6 @@ github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/plgd-dev/go-coap/v2 v2.0.4-0.20200819112225-8eb712b901bc/go.mod h1:+tCi9Q78H/orWRtpVWyBgrr4vKFo2zYtbbxUllerBp4= github.com/plgd-dev/go-coap/v2 v2.4.1-0.20210517130748-95c37ac8e1fa/go.mod h1:rA7fc7ar+B/qa+Q0hRqv7yj/EMtIlmo1l7vkQGSrHPU= github.com/plgd-dev/go-coap/v2 v2.6.0 h1:T8tefZK4jag1ssr6gAGU+922QhVcrjk207aPhdg7i3o= @@ -636,13 +426,13 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= @@ -653,47 +443,39 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= -github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos= +github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= -github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= -github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= -github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= -github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -710,37 +492,18 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= -github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw= -github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc5U= -github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= -github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew= -github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -758,50 +521,33 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= -github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= -go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= -go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= -go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 h1:CsBiKCiQPdSjS+MlRiqeTI9JDDpSuk0Hb6QTRfwer8k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0/go.mod h1:CMJYNAfooOwSZSAmAeMUV1M+TXld3BiK++z9fqIm2xk= -go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= -go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= -go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= -go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= -go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= -go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -824,9 +570,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= -golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -844,90 +587,43 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210502030024-e5908800b52b/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -937,27 +633,15 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= @@ -966,64 +650,34 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1033,12 +687,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1048,79 +697,34 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1129,113 +733,41 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= -google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= -google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 h1:HJMDndgxest5n2y77fnErkM62iUsptE/H8p0dC2Huo4= -google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1261,16 +793,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= -moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/groups/postgres/setup_test.go b/internal/groups/postgres/setup_test.go index ca0422767e..aba582754d 100644 --- a/internal/groups/postgres/setup_test.go +++ b/internal/groups/postgres/setup_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "15.1-alpine", + Tag: "16.1-alpine", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", diff --git a/invitations/postgres/setup_test.go b/invitations/postgres/setup_test.go index 86dbd44860..4f3233674f 100644 --- a/invitations/postgres/setup_test.go +++ b/invitations/postgres/setup_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "15.1-alpine", + Tag: "16.1-alpine", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", diff --git a/pkg/clients/postgres/setup_test.go b/pkg/clients/postgres/setup_test.go index d8c10e97fa..984659aca2 100644 --- a/pkg/clients/postgres/setup_test.go +++ b/pkg/clients/postgres/setup_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "15.1-alpine", + Tag: "16.1-alpine", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", diff --git a/pkg/events/nats/setup_test.go b/pkg/events/nats/setup_test.go index 2d66251019..d7965b336d 100644 --- a/pkg/events/nats/setup_test.go +++ b/pkg/events/nats/setup_test.go @@ -35,7 +35,7 @@ func TestMain(m *testing.M) { container, err = pool.RunWithOptions(&dockertest.RunOptions{ Name: "test-nats-events", Repository: "nats", - Tag: "2.9.21-alpine", + Tag: "2.10.9-alpine", Cmd: []string{"-DVV", "-js"}, }) if err != nil { diff --git a/pkg/events/rabbitmq/setup_test.go b/pkg/events/rabbitmq/setup_test.go index 6814035a8e..a36a2063b6 100644 --- a/pkg/events/rabbitmq/setup_test.go +++ b/pkg/events/rabbitmq/setup_test.go @@ -35,7 +35,7 @@ func TestMain(m *testing.M) { opts := dockertest.RunOptions{ Name: "test-rabbitmq-events", Repository: "rabbitmq", - Tag: "3.9.20", + Tag: "3.12.12", } container, err = pool.RunWithOptions(&opts) if err != nil { diff --git a/pkg/events/redis/setup_test.go b/pkg/events/redis/setup_test.go index e47f153412..851c3f1206 100644 --- a/pkg/events/redis/setup_test.go +++ b/pkg/events/redis/setup_test.go @@ -35,7 +35,7 @@ func TestMain(m *testing.M) { opts := dockertest.RunOptions{ Name: "tests-redis-events", Repository: "redis", - Tag: "7.2.0-alpine", + Tag: "7.2.4-alpine", } container, err = pool.RunWithOptions(&opts) if err != nil { @@ -48,7 +48,6 @@ func TestMain(m *testing.M) { ropts, err := redis.ParseURL(redisURL) if err != nil { log.Fatalf("Could not parse redis URL: %s", err) - } if err := pool.Retry(func() error { diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index 869211b885..b44e849416 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -31,7 +31,7 @@ const ( qos = 2 port = "1883/tcp" broker = "eclipse-mosquitto" - brokerVersion = "1.6.13" + brokerVersion = "2.0.18" brokerTimeout = 30 * time.Second poolMaxWait = 120 * time.Second ) diff --git a/pkg/messaging/nats/setup_test.go b/pkg/messaging/nats/setup_test.go index 9e131a1880..3608fa5827 100644 --- a/pkg/messaging/nats/setup_test.go +++ b/pkg/messaging/nats/setup_test.go @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "nats", - Tag: "2.9.21-alpine", + Tag: "2.10.9-alpine", Cmd: []string{"-DVV", "-js"}, }) if err != nil { diff --git a/pkg/messaging/rabbitmq/setup_test.go b/pkg/messaging/rabbitmq/setup_test.go index 2deeaf4dfb..f26e09bdd9 100644 --- a/pkg/messaging/rabbitmq/setup_test.go +++ b/pkg/messaging/rabbitmq/setup_test.go @@ -24,7 +24,7 @@ import ( const ( port = "5672/tcp" brokerName = "rabbitmq" - brokerVersion = "3.12.10-alpine" + brokerVersion = "3.12.12-alpine" ) var ( diff --git a/readers/api/transport.go b/readers/api/transport.go index fcdc1ab59d..5cab942a80 100644 --- a/readers/api/transport.go +++ b/readers/api/transport.go @@ -162,7 +162,6 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { To: to, }, } - return req, nil } diff --git a/readers/cassandra/setup_test.go b/readers/cassandra/setup_test.go index 7bfd6414dd..f77d85c60a 100644 --- a/readers/cassandra/setup_test.go +++ b/readers/cassandra/setup_test.go @@ -5,6 +5,7 @@ package cassandra_test import ( "fmt" + "log" "os" "testing" @@ -12,6 +13,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/gocql/gocql" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var logger, _ = mglog.New(os.Stdout, "info") @@ -22,9 +24,15 @@ func TestMain(m *testing.M) { logger.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - container, err := pool.Run("cassandra", "3.11.10", []string{}) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "cassandra", + Tag: "3.11.16", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - logger.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port := container.GetPort("9042/tcp") diff --git a/readers/influxdb/setup_test.go b/readers/influxdb/setup_test.go index 73f84dbe02..c8b1c98269 100644 --- a/readers/influxdb/setup_test.go +++ b/readers/influxdb/setup_test.go @@ -16,6 +16,7 @@ import ( mglog "github.com/absmach/magistrala/logger" influxdata "github.com/influxdata/influxdb-client-go/v2" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -33,8 +34,8 @@ const ( dbFluxEnabled = "true" dbBindAddress = ":8088" port = "8086/tcp" - broker = "influxdb" - brokerVersion = "2.2-alpine" + db = "influxdb" + dbVersion = "2.7-alpine" poolMaxWait = 120 * time.Second ) @@ -44,19 +45,25 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - fmt.Sprintf("DOCKER_INFLUXDB_INIT_MODE=%s", dbInitMode), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_USERNAME=%s", dbAdmin), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_PASSWORD=%s", dbPass), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_ORG=%s", dbOrg), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_BUCKET=%s", dbBucket), - fmt.Sprintf("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=%s", dbToken), - fmt.Sprintf("INFLUXDB_HTTP_FLUX_ENABLED=%s", dbFluxEnabled), - fmt.Sprintf("INFLUXDB_BIND_ADDRESS=%s", dbBindAddress), - } - container, err := pool.Run(broker, brokerVersion, cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: db, + Tag: dbVersion, + Env: []string{ + fmt.Sprintf("DOCKER_INFLUXDB_INIT_MODE=%s", dbInitMode), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_USERNAME=%s", dbAdmin), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_PASSWORD=%s", dbPass), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_ORG=%s", dbOrg), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_BUCKET=%s", dbBucket), + fmt.Sprintf("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=%s", dbToken), + fmt.Sprintf("INFLUXDB_HTTP_FLUX_ENABLED=%s", dbFluxEnabled), + fmt.Sprintf("INFLUXDB_BIND_ADDRESS=%s", dbBindAddress), + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } handleInterrupt(m, pool, container) diff --git a/readers/mongodb/setup_test.go b/readers/mongodb/setup_test.go index 770897e0d2..9ef60aebb3 100644 --- a/readers/mongodb/setup_test.go +++ b/readers/mongodb/setup_test.go @@ -6,11 +6,13 @@ package mongodb_test import ( "context" "fmt" + "log" "os" "testing" mglog "github.com/absmach/magistrala/logger" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -23,13 +25,18 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - "MONGO_INITDB_DATABASE=test", - } - - container, err := pool.Run("mongo", "4.4.3-bionic", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "mongo", + Tag: "6.0.13", + Env: []string{ + "MONGO_INITDB_DATABASE=test", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port = container.GetPort("27017/tcp") diff --git a/readers/postgres/setup_test.go b/readers/postgres/setup_test.go index 102ebd5113..fdf71fbb52 100644 --- a/readers/postgres/setup_test.go +++ b/readers/postgres/setup_test.go @@ -15,6 +15,7 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var db *sqlx.DB @@ -24,13 +25,19 @@ func TestMain(m *testing.M) { if err != nil { log.Fatalf("Could not connect to docker: %s", err) } - - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("postgres", "13.3-alpine", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.1-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/readers/timescale/setup_test.go b/readers/timescale/setup_test.go index 6a6899d308..b4d14da500 100644 --- a/readers/timescale/setup_test.go +++ b/readers/timescale/setup_test.go @@ -15,6 +15,7 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var db *sqlx.DB @@ -25,12 +26,19 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - cfg := []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - } - container, err := pool.Run("timescale/timescaledb", "2.4.0-pg12", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "timescale/timescaledb", + Tag: "2.13.1-pg16", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/things/postgres/setup_test.go b/things/postgres/setup_test.go index 08f102f0db..ce4358cf17 100644 --- a/things/postgres/setup_test.go +++ b/things/postgres/setup_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "15.1-alpine", + Tag: "16.1-alpine", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", diff --git a/twins/mongodb/setup_test.go b/twins/mongodb/setup_test.go index 28d5247c18..9eaeab9e49 100644 --- a/twins/mongodb/setup_test.go +++ b/twins/mongodb/setup_test.go @@ -26,7 +26,7 @@ func TestMain(m *testing.M) { "MONGO_INITDB_DATABASE=test", } - container, err := pool.Run("mongo", "4.4.3-bionic", cfg) + container, err := pool.Run("mongo", "7.0.5", cfg) if err != nil { testLog.Error(fmt.Sprintf("Could not start container: %s", err)) } diff --git a/users/postgres/setup_test.go b/users/postgres/setup_test.go index 0e11acba69..a6d84c89c0 100644 --- a/users/postgres/setup_test.go +++ b/users/postgres/setup_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "15.1-alpine", + Tag: "16.1-alpine", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", From 5eb959224d6154e2c18874478dc76074ed5bda65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Wed, 24 Jan 2024 13:43:56 +0100 Subject: [PATCH 24/71] NOISSUE - Revert Mosquitto broker version in MQTT tests (#2063) Signed-off-by: Dusan Borovcanin --- pkg/messaging/mqtt/setup_test.go | 11 ++++++++--- readers/mongodb/setup_test.go | 2 +- things/cache/setup_test.go | 9 ++++++++- twins/events/setup_test.go | 9 ++++++++- twins/mongodb/setup_test.go | 17 +++++++++++------ 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index b44e849416..a5d3a5eaf4 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -18,6 +18,7 @@ import ( mqttpubsub "github.com/absmach/magistrala/pkg/messaging/mqtt" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -30,8 +31,6 @@ const ( username = "magistrala-mqtt" qos = 2 port = "1883/tcp" - broker = "eclipse-mosquitto" - brokerVersion = "2.0.18" brokerTimeout = 30 * time.Second poolMaxWait = 120 * time.Second ) @@ -42,7 +41,13 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - container, err := pool.Run(broker, brokerVersion, nil) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "eclipse-mosquitto", + Tag: "1.6.15", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/readers/mongodb/setup_test.go b/readers/mongodb/setup_test.go index 9ef60aebb3..21ca88c921 100644 --- a/readers/mongodb/setup_test.go +++ b/readers/mongodb/setup_test.go @@ -27,7 +27,7 @@ func TestMain(m *testing.M) { container, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "mongo", - Tag: "6.0.13", + Tag: "7.0.5", Env: []string{ "MONGO_INITDB_DATABASE=test", }, diff --git a/things/cache/setup_test.go b/things/cache/setup_test.go index 435f2cdf36..90b26fc430 100644 --- a/things/cache/setup_test.go +++ b/things/cache/setup_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -25,7 +26,13 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - container, err := pool.Run("redis", "7.2.0-alpine", nil) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "redis", + Tag: "7.2.4-alpine", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/twins/events/setup_test.go b/twins/events/setup_test.go index ca5aa2ccd5..617ac3dbd1 100644 --- a/twins/events/setup_test.go +++ b/twins/events/setup_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" ) var ( @@ -25,7 +26,13 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - container, err := pool.Run("redis", "7.2.0-alpine", nil) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "redis", + Tag: "7.2.4-alpine", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { log.Fatalf("Could not start container: %s", err) } diff --git a/twins/mongodb/setup_test.go b/twins/mongodb/setup_test.go index 9eaeab9e49..0998be4c57 100644 --- a/twins/mongodb/setup_test.go +++ b/twins/mongodb/setup_test.go @@ -6,10 +6,12 @@ package mongodb_test import ( "context" "fmt" + "log" "os" "testing" "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -22,13 +24,16 @@ func TestMain(m *testing.M) { testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) } - cfg := []string{ - "MONGO_INITDB_DATABASE=test", - } - - container, err := pool.Run("mongo", "7.0.5", cfg) + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "mongo", + Tag: "7.0.5", + Env: []string{"MONGO_INITDB_DATABASE=test"}, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) if err != nil { - testLog.Error(fmt.Sprintf("Could not start container: %s", err)) + log.Fatalf("Could not start container: %s", err) } port = container.GetPort("27017/tcp") From e69b69f92a392742d621bedf7e3d2c939db5c430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Wed, 24 Jan 2024 17:09:12 +0100 Subject: [PATCH 25/71] NOISSUE - Use VerneMQ as the dafault MQTT broker (#2064) Signed-off-by: Dusan Borovcanin --- docker/.env | 10 +++++----- docker/docker-compose.yml | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docker/.env b/docker/.env index 8cc0e95df7..a24687f15d 100644 --- a/docker/.env +++ b/docker/.env @@ -12,7 +12,7 @@ MG_NGINX_MQTTS_PORT=8883 MG_NATS_PORT=4222 MG_NATS_HTTP_PORT=8222 MG_NATS_JETSTREAM_KEY=u7wFoAPgXpDueXOFldBnXDh4xjnSOyEJ2Cb8Z5SZvGLzIZ3U4exWhhoIBZHzuNvh -MG_NATS_URL=nats://nats:${MG_NATS_PORT} +MG_NATS_URL=nats://broker:${MG_NATS_PORT} # Configs for nats as MQTT broker MG_NATS_HEALTH_CHECK=http://nats:${MG_NATS_HTTP_PORT}/healthz MG_NATS_WS_TARGET_PATH= @@ -39,15 +39,15 @@ MG_VERNEMQ_WS_TARGET_PATH=/mqtt MG_VERNEMQ_MQTT_QOS=2 ## MQTT Broker -MG_MQTT_BROKER_TYPE=nats -MG_MQTT_BROKER_HEALTH_CHECK=${MG_NATS_HEALTH_CHECK} -MG_MQTT_ADAPTER_MQTT_QOS=${MG_NATS_MQTT_QOS} +MG_MQTT_BROKER_TYPE=vernemq +MG_MQTT_BROKER_HEALTH_CHECK=${MG_VERNEMQ_HEALTH_CHECK} +MG_MQTT_ADAPTER_MQTT_QOS=${MG_VERNEMQ_MQTT_QOS} MG_MQTT_ADAPTER_MQTT_TARGET_HOST=${MG_MQTT_BROKER_TYPE} MG_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${MG_MQTT_BROKER_HEALTH_CHECK} MG_MQTT_ADAPTER_WS_TARGET_HOST=${MG_MQTT_BROKER_TYPE} MG_MQTT_ADAPTER_WS_TARGET_PORT=8080 -MG_MQTT_ADAPTER_WS_TARGET_PATH=${MG_NATS_WS_TARGET_PATH} +MG_MQTT_ADAPTER_WS_TARGET_PATH=${MG_VERNEMQ_WS_TARGET_PATH} ## Redis MG_REDIS_TCP_PORT=6379 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 82c9b8dcf0..c64b3204b7 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -474,7 +474,7 @@ services: container_name: magistrala-mqtt depends_on: - things - - nats + - broker restart: on-failure environment: MG_MQTT_ADAPTER_LOG_LEVEL: ${MG_MQTT_ADAPTER_LOG_LEVEL} @@ -525,7 +525,7 @@ services: container_name: magistrala-http depends_on: - things - - nats + - broker restart: on-failure environment: MG_HTTP_ADAPTER_LOG_LEVEL: ${MG_HTTP_ADAPTER_LOG_LEVEL} @@ -570,7 +570,7 @@ services: container_name: magistrala-coap depends_on: - things - - nats + - broker restart: on-failure environment: MG_COAP_ADAPTER_LOG_LEVEL: ${MG_COAP_ADAPTER_LOG_LEVEL} @@ -620,7 +620,7 @@ services: container_name: magistrala-ws depends_on: - things - - nats + - broker restart: on-failure environment: MG_WS_ADAPTER_LOG_LEVEL: ${MG_WS_ADAPTER_LOG_LEVEL} From 0e5ba6fe3c3be057c4c7c22315db9e37ac63ea0c Mon Sep 17 00:00:00 2001 From: Nataly Musilah <115026536+Musilah@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:01:35 +0300 Subject: [PATCH 26/71] MG-234 - Improve Logging Middleware (#272) Signed-off-by: Musilah --- auth/api/logging.go | 308 +++++++++++++++++++++-------- bootstrap/api/logging.go | 156 ++++++++++----- certs/api/logging.go | 72 +++++-- coap/api/logging.go | 40 ++-- consumers/notifiers/api/logging.go | 71 +++++-- consumers/writers/api/logging.go | 10 +- internal/groups/api/logging.go | 157 +++++++++++---- invitations/middleware/logging.go | 58 ++++-- lora/api/logging.go | 98 ++++++--- opcua/api/logging.go | 99 +++++++--- pkg/messaging/handler/logging.go | 106 +++------- provision/api/logging.go | 32 ++- readers/api/logging.go | 22 ++- things/api/logging.go | 186 ++++++++++++----- twins/api/logging.go | 93 ++++++--- users/api/logging.go | 269 +++++++++++++++++-------- ws/api/logging.go | 14 +- 17 files changed, 1248 insertions(+), 543 deletions(-) diff --git a/auth/api/logging.go b/auth/api/logging.go index f73b9b05fe..a99fd2a92d 100644 --- a/auth/api/logging.go +++ b/auth/api/logging.go @@ -28,12 +28,16 @@ func LoggingMiddleware(svc auth.Service, logger *slog.Logger) auth.Service { func (lm *loggingMiddleware) ListObjects(ctx context.Context, pr auth.PolicyReq, nextPageToken string, limit int32) (p auth.PolicyPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_objects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Int64("limit", int64(limit)), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List objects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List objects completed successfully", args...) }(time.Now()) return lm.svc.ListObjects(ctx, pr, nextPageToken, limit) @@ -41,12 +45,21 @@ func (lm *loggingMiddleware) ListObjects(ctx context.Context, pr auth.PolicyReq, func (lm *loggingMiddleware) ListAllObjects(ctx context.Context, pr auth.PolicyReq) (p auth.PolicyPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_all_objects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("policy_request", + slog.String("object_type", pr.ObjectType), + slog.String("subject_id", pr.Subject), + slog.String("subject_type", pr.SubjectType), + slog.String("permission", pr.Permission), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List all objects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List all objects completed successfully", args...) }(time.Now()) return lm.svc.ListAllObjects(ctx, pr) @@ -54,24 +67,30 @@ func (lm *loggingMiddleware) ListAllObjects(ctx context.Context, pr auth.PolicyR func (lm *loggingMiddleware) CountObjects(ctx context.Context, pr auth.PolicyReq) (count int, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method count_objects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Count objects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Count objects completed successfully", args...) }(time.Now()) return lm.svc.CountObjects(ctx, pr) } func (lm *loggingMiddleware) ListSubjects(ctx context.Context, pr auth.PolicyReq, nextPageToken string, limit int32) (p auth.PolicyPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_subjects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List subjects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List subjects completed successfully", args...) }(time.Now()) return lm.svc.ListSubjects(ctx, pr, nextPageToken, limit) @@ -79,12 +98,21 @@ func (lm *loggingMiddleware) ListSubjects(ctx context.Context, pr auth.PolicyReq func (lm *loggingMiddleware) ListAllSubjects(ctx context.Context, pr auth.PolicyReq) (p auth.PolicyPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_all_subjects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("policy_request", + slog.String("sybject_type", pr.SubjectType), + slog.String("object_id", pr.Object), + slog.String("object_type", pr.ObjectType), + slog.String("permission", pr.Permission), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List all subjects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List all subjects completed successfully", args...) }(time.Now()) return lm.svc.ListAllSubjects(ctx, pr) @@ -92,24 +120,37 @@ func (lm *loggingMiddleware) ListAllSubjects(ctx context.Context, pr auth.Policy func (lm *loggingMiddleware) CountSubjects(ctx context.Context, pr auth.PolicyReq) (count int, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_subjects took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Count subjects failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Count subjects completed successfully", args...) }(time.Now()) return lm.svc.CountSubjects(ctx, pr) } func (lm *loggingMiddleware) ListPermissions(ctx context.Context, pr auth.PolicyReq, filterPermissions []string) (p auth.Permissions, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_permissions took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Any("filter_permissions", filterPermissions), + slog.Group("policy_request", + slog.String("object_id", pr.Object), + slog.String("object_type", pr.ObjectType), + slog.String("subject_id", pr.Subject), + slog.String("subject_type", pr.SubjectType), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List permissions failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List permissions completed successfully", args...) }(time.Now()) return lm.svc.ListPermissions(ctx, pr, filterPermissions) @@ -117,16 +158,19 @@ func (lm *loggingMiddleware) ListPermissions(ctx context.Context, pr auth.Policy func (lm *loggingMiddleware) Issue(ctx context.Context, token string, key auth.Key) (tkn auth.Token, err error) { defer func(begin time.Time) { - d := "" - if key.Type != auth.AccessKey && !key.ExpiresAt.IsZero() { - d = fmt.Sprintf("with expiration date %v", key.ExpiresAt) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("key", + slog.String("subject", key.Subject), + slog.Any("type", key.Type), + ), } - message := fmt.Sprintf("Method issue for %d key %s took %s to complete", key.Type, d, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Issue key failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Issue key completed successfully", args...) }(time.Now()) return lm.svc.Issue(ctx, token, key) @@ -134,12 +178,16 @@ func (lm *loggingMiddleware) Issue(ctx context.Context, token string, key auth.K func (lm *loggingMiddleware) Revoke(ctx context.Context, token, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method revoke for key %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("key_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Revoke key failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Revoke key completed successfully", args...) }(time.Now()) return lm.svc.Revoke(ctx, token, id) @@ -147,12 +195,16 @@ func (lm *loggingMiddleware) Revoke(ctx context.Context, token, id string) (err func (lm *loggingMiddleware) RetrieveKey(ctx context.Context, token, id string) (key auth.Key, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method retrieve for key %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("key_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Retrieve key failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Retrieve key completed successfully", args...) }(time.Now()) return lm.svc.RetrieveKey(ctx, token, id) @@ -160,12 +212,19 @@ func (lm *loggingMiddleware) RetrieveKey(ctx context.Context, token, id string) func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id auth.Key, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method identify took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("key", + slog.String("subject", id.Subject), + slog.Any("type", id.Type), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Identify key failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Identify key completed successfully", args...) }(time.Now()) return lm.svc.Identify(ctx, token) @@ -173,36 +232,62 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id aut func (lm *loggingMiddleware) Authorize(ctx context.Context, pr auth.PolicyReq) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method authorize took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("object", + slog.String("id", pr.Object), + slog.String("type", pr.ObjectType), + ), + slog.Group("subject", + slog.String("id", pr.Subject), + slog.String("kind", pr.SubjectKind), + slog.String("type", pr.SubjectType), + ), + slog.String("permission", pr.Permission), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Authorize failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Authorize completed successfully", args...) }(time.Now()) return lm.svc.Authorize(ctx, pr) } func (lm *loggingMiddleware) AddPolicy(ctx context.Context, pr auth.PolicyReq) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method add_policy took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("policy_request", + slog.String("object_id", pr.Object), + slog.String("object_type", pr.ObjectType), + slog.String("subject_id", pr.Subject), + slog.String("subject_type", pr.SubjectType), + slog.String("relation", pr.Relation), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Add policy failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Add policy completed successfully", args...) }(time.Now()) return lm.svc.AddPolicy(ctx, pr) } func (lm *loggingMiddleware) AddPolicies(ctx context.Context, prs []auth.PolicyReq) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method create_policy_bulk took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn(fmt.Sprintf("Add %d policies failed to complete successfully", len(prs)), args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info(fmt.Sprintf("Add %d policies completed successfully", len(prs)), args...) }(time.Now()) return lm.svc.AddPolicies(ctx, prs) @@ -210,132 +295,199 @@ func (lm *loggingMiddleware) AddPolicies(ctx context.Context, prs []auth.PolicyR func (lm *loggingMiddleware) DeletePolicy(ctx context.Context, pr auth.PolicyReq) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_policy took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("policy_request", + slog.String("object_id", pr.Object), + slog.String("object_type", pr.ObjectType), + slog.String("subject_id", pr.Subject), + slog.String("subject_type", pr.SubjectType), + slog.String("relation", pr.Relation), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Delete policy failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Delete policy completed successfully", args...) }(time.Now()) return lm.svc.DeletePolicy(ctx, pr) } func (lm *loggingMiddleware) DeletePolicies(ctx context.Context, prs []auth.PolicyReq) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_policies took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn(fmt.Sprintf("Delete %d policies failed to complete successfully", len(prs)), args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info(fmt.Sprintf("Delete %d policies completed successfully", len(prs)), args...) }(time.Now()) return lm.svc.DeletePolicies(ctx, prs) } func (lm *loggingMiddleware) CreateDomain(ctx context.Context, token string, d auth.Domain) (do auth.Domain, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method create_domain took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", d.ID), + slog.String("name", d.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Create domain failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create domain completed successfully", args...) }(time.Now()) return lm.svc.CreateDomain(ctx, token, d) } func (lm *loggingMiddleware) RetrieveDomain(ctx context.Context, token, id string) (do auth.Domain, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method retrieve_domain for domain id %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Retrieve domain failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Retrieve domain completed successfully", args...) }(time.Now()) return lm.svc.RetrieveDomain(ctx, token, id) } func (lm *loggingMiddleware) RetrieveDomainPermissions(ctx context.Context, token, id string) (permissions auth.Permissions, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method retrieve_domain_permissions for domain id %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Retrieve domain permissions failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Retrieve domain permissions completed successfully", args...) }(time.Now()) return lm.svc.RetrieveDomainPermissions(ctx, token, id) } func (lm *loggingMiddleware) UpdateDomain(ctx context.Context, token, id string, d auth.DomainReq) (do auth.Domain, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_domain for domain id %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.Any("name", d.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update domain failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update domain completed successfully", args...) }(time.Now()) return lm.svc.UpdateDomain(ctx, token, id, d) } func (lm *loggingMiddleware) ChangeDomainStatus(ctx context.Context, token, id string, d auth.DomainReq) (do auth.Domain, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method change_domain_status for domain id %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.String("name", do.Name), + slog.Any("status", d.Status), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Change domain status failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Change domain status completed successfully", args...) }(time.Now()) return lm.svc.ChangeDomainStatus(ctx, token, id, d) } func (lm *loggingMiddleware) ListDomains(ctx context.Context, token string, page auth.Page) (do auth.DomainsPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_domains took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Uint64("limit", page.Limit), + slog.Uint64("offset", page.Offset), + slog.Uint64("total", page.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List domains failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List domains completed successfully", args...) }(time.Now()) return lm.svc.ListDomains(ctx, token, page) } func (lm *loggingMiddleware) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method assign_users took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + slog.String("relation", relation), + slog.Any("user_ids", userIds), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Assign users to domain failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Assign users to domain completed successfully", args...) }(time.Now()) return lm.svc.AssignUsers(ctx, token, id, userIds, relation) } func (lm *loggingMiddleware) UnassignUsers(ctx context.Context, token, id string, userIds []string, relation string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method unassign_users took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + slog.String("relation", relation), + slog.Any("user_ids", userIds), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Unassign users to domain failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Unassign users to domain completed successfully", args...) }(time.Now()) return lm.svc.UnassignUsers(ctx, token, id, userIds, relation) } func (lm *loggingMiddleware) ListUserDomains(ctx context.Context, token, userID string, page auth.Page) (do auth.DomainsPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_user_domains took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", userID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List user domains failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List user domains completed successfully", args...) }(time.Now()) return lm.svc.ListUserDomains(ctx, token, userID, page) } diff --git a/bootstrap/api/logging.go b/bootstrap/api/logging.go index 43658a58e8..53fb12c4d9 100644 --- a/bootstrap/api/logging.go +++ b/bootstrap/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -30,12 +29,16 @@ func LoggingMiddleware(svc bootstrap.Service, logger *slog.Logger) bootstrap.Ser // If the request fails, it logs the error. func (lm *loggingMiddleware) Add(ctx context.Context, token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method add using token %s with thing %s took %s to complete", token, saved.ThingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", saved.ThingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Add new bootstrap failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Add new bootstrap completed successfully", args...) }(time.Now()) return lm.svc.Add(ctx, token, cfg) @@ -45,87 +48,120 @@ func (lm *loggingMiddleware) Add(ctx context.Context, token string, cfg bootstra // If the request fails, it logs the error. func (lm *loggingMiddleware) View(ctx context.Context, token, id string) (saved bootstrap.Config, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view using token %s with thing %s took %s to complete", token, saved.ThingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View thing config failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View thing config completed successfully", args...) }(time.Now()) return lm.svc.View(ctx, token, id) } -// Update logs the update request. It logs token, bootstrap thing ID and the time it took to complete the request. +// Update logs the update request. It logs bootstrap thing ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Update(ctx context.Context, token string, cfg bootstrap.Config) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update using token %s with thing %s took %s to complete", token, cfg.ThingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("config", + slog.String("thing_id", cfg.ThingID), + slog.String("name", cfg.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update boostrap config failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update boostrap config completed successfully", args...) }(time.Now()) return lm.svc.Update(ctx, token, cfg) } -// UpdateCert logs the update_cert request. It logs token, thing ID and the time it took to complete the request. +// UpdateCert logs the update_cert request. It logs thing ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateCert(ctx context.Context, token, thingID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_cert using token %s with thing id %s took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", cfg.ThingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update bootstrap config certificate failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update bootstrap config certificate completed successfully", args...) }(time.Now()) return lm.svc.UpdateCert(ctx, token, thingID, clientCert, clientKey, caCert) } -// UpdateConnections logs the update_connections request. It logs token, bootstrap ID and the time it took to complete the request. +// UpdateConnections logs the update_connections request. It logs bootstrap ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, token, id string, connections []string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_connections using token %s with thing %s took %s to complete", token, id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + slog.Any("connections", connections), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update config connections failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update config connections completed successfully", args...) }(time.Now()) return lm.svc.UpdateConnections(ctx, token, id, connections) } -// List logs the list request. It logs token, offset, limit and the time it took to complete the request. +// List logs the list request. It logs offset, limit and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) List(ctx context.Context, token string, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list using token %s with offset %d and limit %d took %s to complete", token, offset, limit, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Any("filter", filter), + slog.Uint64("offset", offset), + slog.Uint64("limit", limit), + slog.Uint64("total", res.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List configs failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List configs completed successfully", args...) }(time.Now()) return lm.svc.List(ctx, token, filter, offset, limit) } -// Remove logs the remove request. It logs token, bootstrap ID and the time it took to complete the request. +// Remove logs the remove request. It logs bootstrap ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Remove(ctx context.Context, token, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method remove using token %s with thing %s took %s to complete", token, id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove bootstrap config failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove bootstrap config completed successfully", args...) }(time.Now()) return lm.svc.Remove(ctx, token, id) @@ -133,12 +169,16 @@ func (lm *loggingMiddleware) Remove(ctx context.Context, token, id string) (err func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method bootstrap for thing with external id %s took %s to complete", externalID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("external_id", externalID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View bootstrap config failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View bootstrap completed successfully", args...) }(time.Now()) return lm.svc.Bootstrap(ctx, externalKey, externalID, secure) @@ -146,12 +186,17 @@ func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externa func (lm *loggingMiddleware) ChangeState(ctx context.Context, token, id string, state bootstrap.State) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method change_state for token %s and thing %s took %s to complete", token, id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("id", id), + slog.Any("state", state), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Change thing state failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Change thing state completed successfully", args...) }(time.Now()) return lm.svc.ChangeState(ctx, token, id, state) @@ -159,12 +204,20 @@ func (lm *loggingMiddleware) ChangeState(ctx context.Context, token, id string, func (lm *loggingMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_channel_handler for channel %s took %s to complete", channel.ID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", channel.ID), + slog.String("name", channel.Name), + slog.Any("metadata", channel.Metadata), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update channel handler failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update channel handler completed successfully", args...) }(time.Now()) return lm.svc.UpdateChannelHandler(ctx, channel) @@ -172,12 +225,16 @@ func (lm *loggingMiddleware) UpdateChannelHandler(ctx context.Context, channel b func (lm *loggingMiddleware) RemoveConfigHandler(ctx context.Context, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method remove_config_handler for config %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("config_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove config handler failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove config handler completed successfully", args...) }(time.Now()) return lm.svc.RemoveConfigHandler(ctx, id) @@ -185,12 +242,16 @@ func (lm *loggingMiddleware) RemoveConfigHandler(ctx context.Context, id string) func (lm *loggingMiddleware) RemoveChannelHandler(ctx context.Context, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method remove_channel_handler for channel %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove channel handler failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove channel handler completed successfully", args...) }(time.Now()) return lm.svc.RemoveChannelHandler(ctx, id) @@ -198,12 +259,17 @@ func (lm *loggingMiddleware) RemoveChannelHandler(ctx context.Context, id string func (lm *loggingMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method disconnect_thing_handler for channel %s and thing %s took %s to complete", channelID, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", channelID), + slog.String("thing_id", thingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Disconnect thing handler failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disconnect thing handler completed successfully", args...) }(time.Now()) return lm.svc.DisconnectThingHandler(ctx, channelID, thingID) diff --git a/certs/api/logging.go b/certs/api/logging.go index 63eba824c2..e83e0ac95c 100644 --- a/certs/api/logging.go +++ b/certs/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -26,75 +25,106 @@ func LoggingMiddleware(svc certs.Service, logger *slog.Logger) certs.Service { return &loggingMiddleware{logger, svc} } -// IssueCert logs the issue_cert request. It logs the token, thing ID and the time it took to complete the request. +// IssueCert logs the issue_cert request. It logs the ttl, thing ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, ttl string) (c certs.Cert, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method issue_cert using token %s and thing %s took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.String("ttl", ttl), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Issue certificate failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Issue certificate completed successfully", args...) }(time.Now()) return lm.svc.IssueCert(ctx, token, thingID, ttl) } -// ListCerts logs the list_certs request. It logs the token, thing ID and the time it took to complete the request. +// ListCerts logs the list_certs request. It logs the thing ID and the time it took to complete the request. func (lm *loggingMiddleware) ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (cp certs.Page, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_certs using token %s and thing id %s took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.Group("page", + slog.Uint64("offset", cp.Offset), + slog.Uint64("limit", cp.Limit), + slog.Uint64("total", cp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List certificates failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List certificates completed successfully", args...) }(time.Now()) return lm.svc.ListCerts(ctx, token, thingID, offset, limit) } -// ListSerials logs the list_serials request. It logs the token, thing ID and the time it took to complete the request. +// ListSerials logs the list_serials request. It logs the thing ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListSerials(ctx context.Context, token, thingID string, offset, limit uint64) (cp certs.Page, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_serials using token %s and thing id %s took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.Group("page", + slog.Uint64("offset", cp.Offset), + slog.Uint64("limit", cp.Limit), + slog.Uint64("total", cp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List certifcates serials failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List certificates serials completed successfully", args...) }(time.Now()) return lm.svc.ListSerials(ctx, token, thingID, offset, limit) } -// ViewCert logs the view_cert request. It logs the token, serial ID and the time it took to complete the request. +// ViewCert logs the view_cert request. It logs the serial ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewCert(ctx context.Context, token, serialID string) (c certs.Cert, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_cert using token %s and serial id %s took %s to complete", token, serialID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("serial_id", serialID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View certificate failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View certificate completed successfully", args...) }(time.Now()) return lm.svc.ViewCert(ctx, token, serialID) } -// RevokeCert logs the revoke_cert request. It logs the token, thing ID and the time it took to complete the request. +// RevokeCert logs the revoke_cert request. It logs the thing ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) RevokeCert(ctx context.Context, token, thingID string) (c certs.Revoke, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method revoke_cert using token %s and thing %s took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Revoke certificate failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Revoke certificate completed successfully", args...) }(time.Now()) return lm.svc.RevokeCert(ctx, token, thingID) diff --git a/coap/api/logging.go b/coap/api/logging.go index 6d0a612481..0818110802 100644 --- a/coap/api/logging.go +++ b/coap/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -31,16 +30,19 @@ func LoggingMiddleware(svc coap.Service, logger *slog.Logger) coap.Service { // If the request fails, it logs the error. func (lm *loggingMiddleware) Publish(ctx context.Context, key string, msg *messaging.Message) (err error) { defer func(begin time.Time) { - destChannel := msg.GetChannel() + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", msg.GetChannel()), + } if msg.GetSubtopic() != "" { - destChannel = fmt.Sprintf("%s.%s", destChannel, msg.GetSubtopic()) + args = append(args, slog.String("subtopic", msg.GetSubtopic())) } - message := fmt.Sprintf("Method publish to %s took %s to complete", destChannel, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Publish message failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Publish message completed successfully", args...) }(time.Now()) return lm.svc.Publish(ctx, key, msg) @@ -50,16 +52,19 @@ func (lm *loggingMiddleware) Publish(ctx context.Context, key string, msg *messa // If the request fails, it logs the error. func (lm *loggingMiddleware) Subscribe(ctx context.Context, key, chanID, subtopic string, c coap.Client) (err error) { defer func(begin time.Time) { - destChannel := chanID + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + } if subtopic != "" { - destChannel = fmt.Sprintf("%s.%s", destChannel, subtopic) + args = append(args, slog.String("subtopic", subtopic)) } - message := fmt.Sprintf("Method subscribe to %s for client %s took %s to complete", destChannel, c.Token(), time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Subscribe failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Subscribe completed successfully", args...) }(time.Now()) return lm.svc.Subscribe(ctx, key, chanID, subtopic, c) @@ -69,16 +74,19 @@ func (lm *loggingMiddleware) Subscribe(ctx context.Context, key, chanID, subtopi // If the request fails, it logs the error. func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, key, chanID, subtopic, token string) (err error) { defer func(begin time.Time) { - destChannel := chanID + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + } if subtopic != "" { - destChannel = fmt.Sprintf("%s.%s", destChannel, subtopic) + args = append(args, slog.String("subtopic", subtopic)) } - message := fmt.Sprintf("Method unsubscribe for the client %s from the channel %s took %s to complete", token, destChannel, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Unsubscribe failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Unsubscribe completed successfully", args...) }(time.Now()) return lm.svc.Unsubscribe(ctx, key, chanID, subtopic, token) diff --git a/consumers/notifiers/api/logging.go b/consumers/notifiers/api/logging.go index e8e0ebba2d..50b3bffa2d 100644 --- a/consumers/notifiers/api/logging.go +++ b/consumers/notifiers/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -26,76 +25,106 @@ func LoggingMiddleware(svc notifiers.Service, logger *slog.Logger) notifiers.Ser return &loggingMiddleware{logger, svc} } -// CreateSubscription logs the create_subscription request. It logs token and subscription ID and the time it took to complete the request. +// CreateSubscription logs the create_subscription request. It logs subscription ID and topic and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) CreateSubscription(ctx context.Context, token string, sub notifiers.Subscription) (id string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method create_subscription with the id %s for token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("subscription", + slog.String("topic", sub.Topic), + slog.String("id", id), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create subscription failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create subscription completed successfully", args...) }(time.Now()) return lm.svc.CreateSubscription(ctx, token, sub) } -// ViewSubscription logs the view_subscription request. It logs token and subscription topic and the time it took to complete the request. +// ViewSubscription logs the view_subscription request. It logs subscription topic and id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewSubscription(ctx context.Context, token, topic string) (sub notifiers.Subscription, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_subscription with the topic %s for token %s took %s to complete", topic, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("subscription", + slog.String("topic", topic), + slog.String("id", sub.ID), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View subscription failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View subscription completed successfully", args...) }(time.Now()) return lm.svc.ViewSubscription(ctx, token, topic) } -// ListSubscriptions logs the list_subscriptions request. It logs token and subscription topic and the time it took to complete the request. +// ListSubscriptions logs the list_subscriptions request. It logs page metadata and subscription topic and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListSubscriptions(ctx context.Context, token string, pm notifiers.PageMetadata) (res notifiers.Page, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_subscriptions for topic %s and token %s took %s to complete", pm.Topic, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.String("topic", pm.Topic), + slog.Int("limit", pm.Limit), + slog.Uint64("offset", uint64(pm.Offset)), + slog.Uint64("total", uint64(res.Total)), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List subscriptions failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List subscriptions completed successfully", args...) }(time.Now()) return lm.svc.ListSubscriptions(ctx, token, pm) } -// RemoveSubscription logs the remove_subscription request. It logs token and subscription ID and the time it took to complete the request. +// RemoveSubscription logs the remove_subscription request. It logs subscription ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) RemoveSubscription(ctx context.Context, token, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method remove_subscription for subscription %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("subscription_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove subscription failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove subscription completed successfully", args...) }(time.Now()) return lm.svc.RemoveSubscription(ctx, token, id) } -// ConsumeBlocking logs the consume_blocking request. It logs the message and the time it took to complete the request. +// ConsumeBlocking logs the consume_blocking request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msg interface{}) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method consume took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Blocking consumer failed to consume messages successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Blocking consumer consumed messages successfully", args...) }(time.Now()) return lm.svc.ConsumeBlocking(ctx, msg) diff --git a/consumers/writers/api/logging.go b/consumers/writers/api/logging.go index f5c3f41a31..77e5f91449 100644 --- a/consumers/writers/api/logging.go +++ b/consumers/writers/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -33,12 +32,15 @@ func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger) // If the request fails, it logs the error. func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method consume took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Blocking consumer failed to consume messages successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Blocking consumer consumed messages successfully", args...) }(time.Now()) return lm.consumer.ConsumeBlocking(ctx, msgs) diff --git a/internal/groups/api/logging.go b/internal/groups/api/logging.go index 69fd42a1f8..5f7befc15d 100644 --- a/internal/groups/api/logging.go +++ b/internal/groups/api/logging.go @@ -5,7 +5,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -28,122 +27,187 @@ func LoggingMiddleware(svc groups.Service, logger *slog.Logger) groups.Service { // If the request fails, it logs the error. func (lm *loggingMiddleware) CreateGroup(ctx context.Context, token, kind string, group groups.Group) (g groups.Group, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method create_group for %s %s with id %s using token %s took %s to complete", g.Name, kind, g.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("group", + slog.String("id", g.ID), + slog.String("name", g.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create group completed successfully", args...) }(time.Now()) return lm.svc.CreateGroup(ctx, token, kind, group) } -// UpdateGroup logs the update_group request. It logs the group name, id and token and the time it took to complete the request. +// UpdateGroup logs the update_group request. It logs the group name, id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateGroup(ctx context.Context, token string, group groups.Group) (g groups.Group, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_group for group %s with id %s using token %s took %s to complete", g.Name, g.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("group", + slog.String("id", group.ID), + slog.String("name", group.Name), + slog.Any("metadata", group.Metadata), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update group completed successfully", args...) }(time.Now()) return lm.svc.UpdateGroup(ctx, token, group) } -// ViewGroup logs the view_group request. It logs the group name, id and token and the time it took to complete the request. +// ViewGroup logs the view_group request. It logs the group name, id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewGroup(ctx context.Context, token, id string) (g groups.Group, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_group for group %s with id %s using token %s took %s to complete", g.Name, g.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("group", + slog.String("id", g.ID), + slog.String("name", g.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View group completed successfully", args...) }(time.Now()) return lm.svc.ViewGroup(ctx, token, id) } -// ViewGroupPerms logs the view_group request. It logs the group name, id and token and the time it took to complete the request. +// ViewGroupPerms logs the view_group request. It logs the group id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewGroupPerms(ctx context.Context, token, id string) (p []string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_group_perms for group with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View group permissions failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View group permissions completed successfully", args...) }(time.Now()) return lm.svc.ViewGroupPerms(ctx, token, id) } -// ListGroups logs the list_groups request. It logs the token and the time it took to complete the request. +// ListGroups logs the list_groups request. It logs the page metadata and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListGroups(ctx context.Context, token, memberKind, memberID string, gp groups.Page) (cg groups.Page, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_groups %d groups using token %s took %s to complete", cg.Total, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("member", + slog.String("id", memberID), + slog.String("kind", memberKind), + ), + slog.Group("page", + slog.Uint64("limit", gp.Limit), + slog.Uint64("offset", gp.Offset), + slog.Uint64("total", cg.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List groups failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List groups completed successfully", args...) }(time.Now()) return lm.svc.ListGroups(ctx, token, memberKind, memberID, gp) } -// EnableGroup logs the enable_group request. It logs the group name, id and token and the time it took to complete the request. +// EnableGroup logs the enable_group request. It logs the group name, id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) EnableGroup(ctx context.Context, token, id string) (g groups.Group, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method enable_group for group with id %s using token %s took %s to complete", g.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("group", + slog.String("id", id), + slog.String("name", g.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Enable group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Enable group completed successfully", args...) }(time.Now()) return lm.svc.EnableGroup(ctx, token, id) } -// DisableGroup logs the disable_group request. It logs the group name, id and token and the time it took to complete the request. +// DisableGroup logs the disable_group request. It logs the group id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) DisableGroup(ctx context.Context, token, id string) (g groups.Group, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method disable_group for group with id %s using token %s took %s to complete", g.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("group", + slog.String("id", id), + slog.String("name", g.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Disable group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disable group completed successfully", args...) }(time.Now()) return lm.svc.DisableGroup(ctx, token, id) } -// ListMembers logs the list_members request. It logs the groupID and token and the time it took to complete the request. +// ListMembers logs the list_members request. It logs the groupID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID, permission, memberKind string) (mp groups.MembersPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_memberships for group with id %s using token %s took %s to complete", groupID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", groupID), + slog.String("permission", permission), + slog.String("member_kind", memberKind), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List members failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List members completed successfully", args...) }(time.Now()) return lm.svc.ListMembers(ctx, token, groupID, permission, memberKind) } func (lm *loggingMiddleware) Assign(ctx context.Context, token, groupID, relation, memberKind string, memberIDs ...string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method assign for token %s and member %s group id %s took %s to complete", token, memberIDs, groupID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", groupID), + slog.String("relation", relation), + slog.String("member_kind", memberKind), + slog.Any("member_ids", memberIDs), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Assign member to group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Assign member to group completed successfully", args...) }(time.Now()) return lm.svc.Assign(ctx, token, groupID, relation, memberKind, memberIDs...) @@ -151,12 +215,19 @@ func (lm *loggingMiddleware) Assign(ctx context.Context, token, groupID, relatio func (lm *loggingMiddleware) Unassign(ctx context.Context, token, groupID, relation, memberKind string, memberIDs ...string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method unassign for token %s and member %s group id %s took %s to complete", token, memberIDs, groupID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", groupID), + slog.String("relation", relation), + slog.String("member_kind", memberKind), + slog.Any("member_ids", memberIDs), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Unassign member to group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Unassign member to group completed successfully", args...) }(time.Now()) return lm.svc.Unassign(ctx, token, groupID, relation, memberKind, memberIDs...) @@ -164,12 +235,16 @@ func (lm *loggingMiddleware) Unassign(ctx context.Context, token, groupID, relat func (lm *loggingMiddleware) DeleteGroup(ctx context.Context, token, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_group for group with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Delete group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Delete group completed successfully", args...) }(time.Now()) return lm.svc.DeleteGroup(ctx, token, id) } diff --git a/invitations/middleware/logging.go b/invitations/middleware/logging.go index 49524a9c9e..6acf135eed 100644 --- a/invitations/middleware/logging.go +++ b/invitations/middleware/logging.go @@ -5,7 +5,6 @@ package middleware import ( "context" - "fmt" "log/slog" "time" @@ -25,60 +24,87 @@ func Logging(logger *slog.Logger, svc invitations.Service) invitations.Service { func (lm *logging) SendInvitation(ctx context.Context, token string, invitation invitations.Invitation) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method send_invitation to user_id %s from domain_id %s took %s to complete", invitation.UserID, invitation.DomainID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", invitation.UserID), + slog.String("domain_id", invitation.DomainID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Send invitation failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Send invitation completed successfully", args...) }(time.Now()) return lm.svc.SendInvitation(ctx, token, invitation) } func (lm *logging) ViewInvitation(ctx context.Context, token, userID, domainID string) (invitation invitations.Invitation, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_invitation took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", userID), + slog.String("domain_id", domainID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View invitation failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View invitation completed successfully", args...) }(time.Now()) return lm.svc.ViewInvitation(ctx, token, userID, domainID) } func (lm *logging) ListInvitations(ctx context.Context, token string, page invitations.Page) (invs invitations.InvitationPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_invitations took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Uint64("offset", page.Offset), + slog.Uint64("limit", page.Limit), + slog.Uint64("total", invs.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List invitations failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List invitations completed successfully", args...) }(time.Now()) return lm.svc.ListInvitations(ctx, token, page) } func (lm *logging) AcceptInvitation(ctx context.Context, token, domainID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method accept_invitation took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", domainID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Accept invitation failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Accept invitation completed successfully", args...) }(time.Now()) return lm.svc.AcceptInvitation(ctx, token, domainID) } func (lm *logging) DeleteInvitation(ctx context.Context, token, userID, domainID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_invitation took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", userID), + slog.String("domain_id", domainID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Delete invitation failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Delete invitation completed successfully", args...) }(time.Now()) return lm.svc.DeleteInvitation(ctx, token, userID, domainID) } diff --git a/lora/api/logging.go b/lora/api/logging.go index fb9a37cfea..8190e700c8 100644 --- a/lora/api/logging.go +++ b/lora/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -31,12 +30,17 @@ func LoggingMiddleware(svc lora.Service, logger *slog.Logger) lora.Service { func (lm loggingMiddleware) CreateThing(ctx context.Context, thingID, loraDevEUI string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("create_thing for thing %s and lora-dev-eui %s took %s to complete", thingID, loraDevEUI, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.String("dev_eui", loraDevEUI), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create thing route-map completed successfully", args...) }(time.Now()) return lm.svc.CreateThing(ctx, thingID, loraDevEUI) @@ -44,12 +48,17 @@ func (lm loggingMiddleware) CreateThing(ctx context.Context, thingID, loraDevEUI func (lm loggingMiddleware) UpdateThing(ctx context.Context, thingID, loraDevEUI string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("update_thing for thing %s and lora-dev-eui %s took %s to complete", thingID, loraDevEUI, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.String("dev_eui", loraDevEUI), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update thing route-map completed successfully", args...) }(time.Now()) return lm.svc.UpdateThing(ctx, thingID, loraDevEUI) @@ -57,12 +66,16 @@ func (lm loggingMiddleware) UpdateThing(ctx context.Context, thingID, loraDevEUI func (lm loggingMiddleware) RemoveThing(ctx context.Context, thingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("remove_thing for thing %s took %s to complete", thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove thing route-map completed successfully", args...) }(time.Now()) return lm.svc.RemoveThing(ctx, thingID) @@ -70,12 +83,17 @@ func (lm loggingMiddleware) RemoveThing(ctx context.Context, thingID string) (er func (lm loggingMiddleware) CreateChannel(ctx context.Context, chanID, loraApp string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("create_channel for channel %s and lora-app %s took %s to complete", chanID, loraApp, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.String("lora_app", loraApp), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create channel route-map completed successfully", args...) }(time.Now()) return lm.svc.CreateChannel(ctx, chanID, loraApp) @@ -83,12 +101,16 @@ func (lm loggingMiddleware) CreateChannel(ctx context.Context, chanID, loraApp s func (lm loggingMiddleware) UpdateChannel(ctx context.Context, chanID, loraApp string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("update_channel for channel %s and lora-app %s took %s to complete", chanID, loraApp, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.String("lora_app", loraApp), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + lm.logger.Warn("Update channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update channel route-map completed successfully", args...) }(time.Now()) return lm.svc.UpdateChannel(ctx, chanID, loraApp) @@ -96,12 +118,15 @@ func (lm loggingMiddleware) UpdateChannel(ctx context.Context, chanID, loraApp s func (lm loggingMiddleware) RemoveChannel(ctx context.Context, chanID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("remove_channel for channel %s took %s to complete", chanID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + lm.logger.Warn("Remove channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove channel route-map completed successfully", args...) }(time.Now()) return lm.svc.RemoveChannel(ctx, chanID) @@ -109,12 +134,17 @@ func (lm loggingMiddleware) RemoveChannel(ctx context.Context, chanID string) (e func (lm loggingMiddleware) ConnectThing(ctx context.Context, chanID, thingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("connect_thing for channel %s and thing %s, took %s to complete", chanID, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.String("thing_id", thingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Connect thing to channel failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Connect thing to channel completed successfully", args...) }(time.Now()) return lm.svc.ConnectThing(ctx, chanID, thingID) @@ -122,12 +152,17 @@ func (lm loggingMiddleware) ConnectThing(ctx context.Context, chanID, thingID st func (lm loggingMiddleware) DisconnectThing(ctx context.Context, chanID, thingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("disconnect_thing mgx-%s : mgx-%s, took %s to complete", chanID, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.String("thing_id", thingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Disconnect thing from channel failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disconnect thing from channel completed successfully", args...) }(time.Now()) return lm.svc.DisconnectThing(ctx, chanID, thingID) @@ -135,12 +170,19 @@ func (lm loggingMiddleware) DisconnectThing(ctx context.Context, chanID, thingID func (lm loggingMiddleware) Publish(ctx context.Context, msg *lora.Message) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("publish application/%s/device/%s/rx took %s to complete", msg.ApplicationID, msg.DevEUI, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("message", + slog.String("application_id", msg.ApplicationID), + slog.String("device_eui", msg.DevEUI), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Publish failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Publish completed successfully", args...) }(time.Now()) return lm.svc.Publish(ctx, msg) diff --git a/opcua/api/logging.go b/opcua/api/logging.go index 27eeb6ce03..e58e97abba 100644 --- a/opcua/api/logging.go +++ b/opcua/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -31,12 +30,17 @@ func LoggingMiddleware(svc opcua.Service, logger *slog.Logger) opcua.Service { func (lm loggingMiddleware) CreateThing(ctx context.Context, mgxThing, opcuaNodeID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("create_thing %s with NodeID %s, took %s to complete", mgxThing, opcuaNodeID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", mgxThing), + slog.String("node_id", opcuaNodeID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create thing route-map completed successfully", args...) }(time.Now()) return lm.svc.CreateThing(ctx, mgxThing, opcuaNodeID) @@ -44,12 +48,17 @@ func (lm loggingMiddleware) CreateThing(ctx context.Context, mgxThing, opcuaNode func (lm loggingMiddleware) UpdateThing(ctx context.Context, mgxThing, opcuaNodeID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("update_thing %s with NodeID %s, took %s to complete", mgxThing, opcuaNodeID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", mgxThing), + slog.String("node_id", opcuaNodeID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update thing route-map completed successfully", args...) }(time.Now()) return lm.svc.UpdateThing(ctx, mgxThing, opcuaNodeID) @@ -57,12 +66,16 @@ func (lm loggingMiddleware) UpdateThing(ctx context.Context, mgxThing, opcuaNode func (lm loggingMiddleware) RemoveThing(ctx context.Context, mgxThing string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("remove_thing %s, took %s to complete", mgxThing, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", mgxThing), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove thing route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove thing route-map completed successfully", args...) }(time.Now()) return lm.svc.RemoveThing(ctx, mgxThing) @@ -70,12 +83,17 @@ func (lm loggingMiddleware) RemoveThing(ctx context.Context, mgxThing string) (e func (lm loggingMiddleware) CreateChannel(ctx context.Context, mgxChan, opcuaServerURI string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("create_channel %s with ServerURI %s, took %s to complete", mgxChan, opcuaServerURI, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", mgxChan), + slog.String("server_uri", opcuaServerURI), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Create channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Create channel route-map completed successfully", args...) }(time.Now()) return lm.svc.CreateChannel(ctx, mgxChan, opcuaServerURI) @@ -83,12 +101,17 @@ func (lm loggingMiddleware) CreateChannel(ctx context.Context, mgxChan, opcuaSer func (lm loggingMiddleware) UpdateChannel(ctx context.Context, mgxChanID, opcuaServerURI string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("update_channel %s with ServerURI %s, took %s to complete", mgxChanID, opcuaServerURI, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", mgxChanID), + slog.String("server_uri", opcuaServerURI), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update channel route-map completed successfully", args...) }(time.Now()) return lm.svc.UpdateChannel(ctx, mgxChanID, opcuaServerURI) @@ -96,12 +119,16 @@ func (lm loggingMiddleware) UpdateChannel(ctx context.Context, mgxChanID, opcuaS func (lm loggingMiddleware) RemoveChannel(ctx context.Context, mgxChanID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("remove_channel %s, took %s to complete", mgxChanID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", mgxChanID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove channel route-map failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove channel route-map completed successfully", args...) }(time.Now()) return lm.svc.RemoveChannel(ctx, mgxChanID) @@ -109,12 +136,17 @@ func (lm loggingMiddleware) RemoveChannel(ctx context.Context, mgxChanID string) func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID, mgxThingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("connect_thing for channel %s and thing %s, took %s to complete", mgxChanID, mgxThingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", mgxChanID), + slog.String("thing_id", mgxThingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Connect thing to channel failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Connect thing to channel completed successfully", args...) }(time.Now()) return lm.svc.ConnectThing(ctx, mgxChanID, mgxThingID) @@ -122,12 +154,17 @@ func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID, mgxThin func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID, mgxThingID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("disconnect_thing mgx-%s : mgx-%s, took %s to complete", mgxChanID, mgxThingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", mgxChanID), + slog.String("thing_id", mgxThingID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Disconnect thing from channel failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disconnect thing from channel completed successfully", args...) }(time.Now()) return lm.svc.DisconnectThing(ctx, mgxChanID, mgxThingID) @@ -135,12 +172,18 @@ func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID, mgxT func (lm loggingMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier string) (nodes []opcua.BrowsedNode, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("browse server URI %s and node %s;%s, took %s to complete", serverURI, namespace, identifier, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("server_uri", serverURI), + slog.String("namespace", namespace), + slog.String("identifier", identifier), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Browse available nodes failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Browse available nodes completed successfully", args...) }(time.Now()) return lm.svc.Browse(ctx, serverURI, namespace, identifier) diff --git a/pkg/messaging/handler/logging.go b/pkg/messaging/handler/logging.go index 5019c20b4b..6b92ca67b9 100644 --- a/pkg/messaging/handler/logging.go +++ b/pkg/messaging/handler/logging.go @@ -7,7 +7,6 @@ package handler import ( "context" - "fmt" "log/slog" "time" @@ -23,118 +22,65 @@ type loggingMiddleware struct { // AuthConnect implements session.Handler. func (lm *loggingMiddleware) AuthConnect(ctx context.Context) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method auth connect took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.AuthConnect(ctx) + return lm.logAction(ctx, "AuthConnect", nil) } // AuthPublish implements session.Handler. func (lm *loggingMiddleware) AuthPublish(ctx context.Context, topic *string, payload *[]byte) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method auth publish took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.AuthPublish(ctx, topic, payload) + return lm.logAction(ctx, "AuthPublish", &[]string{*topic}) } // AuthSubscribe implements session.Handler. func (lm *loggingMiddleware) AuthSubscribe(ctx context.Context, topics *[]string) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method auth subscribe took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.AuthSubscribe(ctx, topics) + return lm.logAction(ctx, "AuthSubscribe", topics) } // Connect implements session.Handler. func (lm *loggingMiddleware) Connect(ctx context.Context) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method connect took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.Connect(ctx) + return lm.logAction(ctx, "Connect", nil) } // Disconnect implements session.Handler. func (lm *loggingMiddleware) Disconnect(ctx context.Context) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method disconnect took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.Disconnect(ctx) + return lm.logAction(ctx, "Disconnect", nil) } // Publish logs the publish request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Publish(ctx context.Context, topic *string, payload *[]byte) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method publish to channel %s took %s to complete", *topic, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.Publish(ctx, topic, payload) + return lm.logAction(ctx, "Publish", &[]string{*topic}) } // Subscribe implements session.Handler. func (lm *loggingMiddleware) Subscribe(ctx context.Context, topics *[]string) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method subscribe took %s to complete", time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - - return lm.svc.Subscribe(ctx, topics) + return lm.logAction(ctx, "Subscribe", topics) } // Unsubscribe implements session.Handler. func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, topics *[]string) (err error) { + return lm.logAction(ctx, "Unsubscribe", topics) +} + +// LoggingMiddleware adds logging facilities to the adapter. +func LoggingMiddleware(svc session.Handler, logger *slog.Logger) session.Handler { + return &loggingMiddleware{logger, svc} +} + +func (lm *loggingMiddleware) logAction(ctx context.Context, action string, topics *[]string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method unsubscribe took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } + if topics != nil { + args = append(args, slog.Any("topics", *topics)) + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn(action+"() failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info(action+"() completed successfully", args...) }(time.Now()) - return lm.svc.Unsubscribe(ctx, topics) -} - -// LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(svc session.Handler, logger *slog.Logger) session.Handler { - return &loggingMiddleware{logger, svc} + return nil } diff --git a/provision/api/logging.go b/provision/api/logging.go index f1dfd948e0..c6aa58fbc0 100644 --- a/provision/api/logging.go +++ b/provision/api/logging.go @@ -6,7 +6,6 @@ package api import ( - "fmt" "log/slog" "time" @@ -27,12 +26,17 @@ func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision. func (lm *loggingMiddleware) Provision(token, name, externalID, externalKey string) (res provision.Result, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method provision for token: %s and things: %v took %s to complete", token, res.Things, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("name", name), + slog.String("external_id", externalID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Provision failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors", message)) + lm.logger.Info("Provision completed successfully", args...) }(time.Now()) return lm.svc.Provision(token, name, externalID, externalKey) @@ -40,12 +44,17 @@ func (lm *loggingMiddleware) Provision(token, name, externalID, externalKey stri func (lm *loggingMiddleware) Cert(token, thingID, duration string) (cert, key string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", thingID), + slog.String("ttl", duration), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Thing certificate failed to create successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors", message)) + lm.logger.Info("Thing certificate created successfully", args...) }(time.Now()) return lm.svc.Cert(token, thingID, duration) @@ -53,12 +62,15 @@ func (lm *loggingMiddleware) Cert(token, thingID, duration string) (cert, key st func (lm *loggingMiddleware) Mapping(token string) (res map[string]interface{}, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method mapping for token: %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Mapping failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors", message)) + lm.logger.Info("Mapping completed successfully", args...) }(time.Now()) return lm.svc.Mapping(token) diff --git a/readers/api/logging.go b/readers/api/logging.go index 73bb9dcffa..614337df95 100644 --- a/readers/api/logging.go +++ b/readers/api/logging.go @@ -6,7 +6,6 @@ package api import ( - "fmt" "log/slog" "time" @@ -30,12 +29,27 @@ func LoggingMiddleware(svc readers.MessageRepository, logger *slog.Logger) reade func (lm *loggingMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (page readers.MessagesPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method read_all for channel %s with query %v took %s to complete", chanID, rpm, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.Group("page", + slog.Uint64("offset", rpm.Offset), + slog.Uint64("limit", rpm.Limit), + slog.Uint64("total", page.Total), + ), + } + if rpm.Subtopic != "" { + args = append(args, slog.String("subtopic", rpm.Subtopic)) + } + if rpm.Publisher != "" { + args = append(args, slog.String("publisher", rpm.Publisher)) + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Read all failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Read all completed successfully", args...) }(time.Now()) return lm.svc.ReadAll(chanID, rpm) diff --git a/things/api/logging.go b/things/api/logging.go index 134c8eea82..86ae568038 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -27,180 +27,276 @@ func LoggingMiddleware(svc things.Service, logger *slog.Logger) things.Service { func (lm *loggingMiddleware) CreateThings(ctx context.Context, token string, clients ...mgclients.Client) (cs []mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method create_things %d things using token %s took %s to complete", len(cs), token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn(fmt.Sprintf("Create %d things failed to complete successfully", len(clients)), args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info(fmt.Sprintf("Create %d things completed successfully", len(clients)), args...) }(time.Now()) return lm.svc.CreateThings(ctx, token, clients...) } func (lm *loggingMiddleware) ViewClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_thing for thing with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View thing completed successfully", args...) }(time.Now()) return lm.svc.ViewClient(ctx, token, id) } func (lm *loggingMiddleware) ViewClientPerms(ctx context.Context, token, id string) (p []string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_thing_permissions for thing with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View thing permissions failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View thing permissions completed successfully", args...) }(time.Now()) return lm.svc.ViewClientPerms(ctx, token, id) } func (lm *loggingMiddleware) ListClients(ctx context.Context, token, reqUserID string, pm mgclients.Page) (cp mgclients.ClientsPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_things using token %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", reqUserID), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", cp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List things failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List things completed successfully", args...) }(time.Now()) return lm.svc.ListClients(ctx, token, reqUserID, pm) } func (lm *loggingMiddleware) UpdateClient(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_thing_name_and_metadata for thing with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", client.ID), + slog.String("name", client.Name), + slog.Any("metadata", client.Metadata), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update thing completed successfully", args...) }(time.Now()) return lm.svc.UpdateClient(ctx, token, client) } func (lm *loggingMiddleware) UpdateClientTags(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_thing_tags for thing with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.Any("tags", c.Tags), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update thing tags failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update thing tags completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientTags(ctx, token, client) } func (lm *loggingMiddleware) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_thing_secret for thing with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update thing secret failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update thing secret completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientSecret(ctx, token, oldSecret, newSecret) } func (lm *loggingMiddleware) EnableClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method enable_thing for thing with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", id), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Enable thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Enable thing completed successfully", args...) }(time.Now()) return lm.svc.EnableClient(ctx, token, id) } func (lm *loggingMiddleware) DisableClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method disable_thing for thing with id %s using token %s took %s to complete", id, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("thing", + slog.String("id", id), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Disable thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disable thing completed successfully", args...) }(time.Now()) return lm.svc.DisableClient(ctx, token, id) } func (lm *loggingMiddleware) ListClientsByGroup(ctx context.Context, token, channelID string, cp mgclients.Page) (mp mgclients.MembersPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_things_by_channel for channel with id %s using token %s took %s to complete", channelID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", channelID), + slog.Group("page", + slog.Uint64("offset", cp.Offset), + slog.Uint64("limit", cp.Limit), + slog.Uint64("total", mp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List things by group failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List things by group completed successfully", args...) }(time.Now()) return lm.svc.ListClientsByGroup(ctx, token, channelID, cp) } func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method identify for thing with id %s and key %s took %s to complete", id, key, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Identify thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Identify thing completed successfully", args...) }(time.Now()) return lm.svc.Identify(ctx, key) } func (lm *loggingMiddleware) Authorize(ctx context.Context, req *magistrala.AuthorizeReq) (id string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method authorize for thing key %s and channnel %s took %s to complete", req.Subject, req.Object, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("object", req.GetObject()), + slog.String("object_type", req.GetObjectType()), + slog.String("subject_type", req.GetSubjectType()), + slog.String("permission", req.GetPermission()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Authorize failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Authorize completed successfully", args...) }(time.Now()) return lm.svc.Authorize(ctx, req) } func (lm *loggingMiddleware) Share(ctx context.Context, token, id, relation string, userids ...string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method share for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + slog.Any("user_ids", userids), + slog.String("relation", relation), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Share thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Share thing completed successfully", args...) }(time.Now()) return lm.svc.Share(ctx, token, id, relation, userids...) } func (lm *loggingMiddleware) Unshare(ctx context.Context, token, id, relation string, userids ...string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method unshare for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + slog.Any("user_ids", userids), + slog.String("relation", relation), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Unshare thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Unshare thing completed successfully", args...) }(time.Now()) return lm.svc.Unshare(ctx, token, id, relation, userids...) } func (lm *loggingMiddleware) DeleteClient(ctx context.Context, token, id string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_client for thing id %s took %s to complete", id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("thing_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Delete thing failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Delete thing completed successfully", args...) }(time.Now()) return lm.svc.DeleteClient(ctx, token, id) } diff --git a/twins/api/logging.go b/twins/api/logging.go index c8b65305d1..eff62e13a6 100644 --- a/twins/api/logging.go +++ b/twins/api/logging.go @@ -7,7 +7,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -29,12 +28,20 @@ func LoggingMiddleware(svc twins.Service, logger *slog.Logger) twins.Service { func (lm *loggingMiddleware) AddTwin(ctx context.Context, token string, twin twins.Twin, def twins.Definition) (tw twins.Twin, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method add_twin for token %s and twin %s took %s to complete", token, twin.ID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("twin", + slog.String("id", tw.ID), + slog.String("name", tw.Name), + slog.Any("definitions", tw.Definitions), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Add twin failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Add twin completed successfully", args...) }(time.Now()) return lm.svc.AddTwin(ctx, token, twin, def) @@ -42,12 +49,20 @@ func (lm *loggingMiddleware) AddTwin(ctx context.Context, token string, twin twi func (lm *loggingMiddleware) UpdateTwin(ctx context.Context, token string, twin twins.Twin, def twins.Definition) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_twin for token %s and twin %s took %s to complete", token, twin.ID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("twin", + slog.String("id", twin.ID), + slog.String("name", twin.Name), + slog.Any("definitions", def), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update twin failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update twin completed successfully", args...) }(time.Now()) return lm.svc.UpdateTwin(ctx, token, twin, def) @@ -55,12 +70,16 @@ func (lm *loggingMiddleware) UpdateTwin(ctx context.Context, token string, twin func (lm *loggingMiddleware) ViewTwin(ctx context.Context, token, twinID string) (tw twins.Twin, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_twin for token %s and twin %s took %s to complete", token, twinID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("twin_id", twinID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View twin failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View twin completed successfully", args...) }(time.Now()) return lm.svc.ViewTwin(ctx, token, twinID) @@ -68,12 +87,21 @@ func (lm *loggingMiddleware) ViewTwin(ctx context.Context, token, twinID string) func (lm *loggingMiddleware) ListTwins(ctx context.Context, token string, offset, limit uint64, name string, metadata twins.Metadata) (page twins.Page, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_twins for token %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.String("name", name), + slog.Uint64("offset", offset), + slog.Uint64("limit", limit), + slog.Uint64("total", page.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List twins failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List twins completed successfully", args...) }(time.Now()) return lm.svc.ListTwins(ctx, token, offset, limit, name, metadata) @@ -81,12 +109,20 @@ func (lm *loggingMiddleware) ListTwins(ctx context.Context, token string, offset func (lm *loggingMiddleware) SaveStates(ctx context.Context, msg *messaging.Message) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method save_states took %s to complete", time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("message", + slog.String("channel", msg.GetChannel()), + slog.String("subtopic", msg.GetSubtopic()), + slog.String("publisher", msg.GetPublisher()), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Save states failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Save states completed successfully", args...) }(time.Now()) return lm.svc.SaveStates(ctx, msg) @@ -94,12 +130,21 @@ func (lm *loggingMiddleware) SaveStates(ctx context.Context, msg *messaging.Mess func (lm *loggingMiddleware) ListStates(ctx context.Context, token string, offset, limit uint64, twinID string) (page twins.StatesPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_states for token %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("twin_id", twinID), + slog.Group("page", + slog.Uint64("offset", offset), + slog.Uint64("limit", limit), + slog.Uint64("total", page.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List states failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List states completed successfully", args...) }(time.Now()) return lm.svc.ListStates(ctx, token, offset, limit, twinID) @@ -107,12 +152,16 @@ func (lm *loggingMiddleware) ListStates(ctx context.Context, token string, offse func (lm *loggingMiddleware) RemoveTwin(ctx context.Context, token, twinID string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method remove_twin for token %s and twin %s took %s to complete", token, twinID, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("twin_id", twinID), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Remove twin failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Remove twin completed successfully", args...) }(time.Now()) return lm.svc.RemoveTwin(ctx, token, twinID) diff --git a/users/api/logging.go b/users/api/logging.go index ac60fea545..70b693694f 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -5,7 +5,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -26,34 +25,44 @@ func LoggingMiddleware(svc users.Service, logger *slog.Logger) users.Service { return &loggingMiddleware{logger, svc} } -// RegisterClient logs the register_client request. It logs the client id and token and the time it took to complete the request. +// RegisterClient logs the register_client request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) RegisterClient(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method register_client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Register user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Register user completed successfully", args...) }(time.Now()) return lm.svc.RegisterClient(ctx, token, client) } -// IssueToken logs the issue_token request. It logs the client identity and token type and the time it took to complete the request. +// IssueToken logs the issue_token request. It logs the client identity type and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) IssueToken(ctx context.Context, identity, secret, domainID string) (t *magistrala.Token, err error) { defer func(begin time.Time) { - message := "Method issue_token" - if t != nil { - message = fmt.Sprintf("%s of type %s", message, t.AccessType) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", domainID), + } + if t.AccessType != "" { + args = append(args, slog.String("access_type", t.AccessType)) } - message = fmt.Sprintf("%s for client %s took %s to complete", message, identity, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Issue token failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Issue token completed successfully", args...) }(time.Now()) return lm.svc.IssueToken(ctx, identity, secret, domainID) } @@ -62,225 +71,329 @@ func (lm *loggingMiddleware) IssueToken(ctx context.Context, identity, secret, d // If the request fails, it logs the error. func (lm *loggingMiddleware) RefreshToken(ctx context.Context, refreshToken, domainID string) (t *magistrala.Token, err error) { defer func(begin time.Time) { - message := "Method refresh_token" - if t != nil { - message = fmt.Sprintf("%s of type %s", message, t.AccessType) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", domainID), + } + if t.AccessType != "" { + args = append(args, slog.String("access_type", t.AccessType)) } - message = fmt.Sprintf("%s for refresh token %s took %s to complete", message, refreshToken, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Refresh token failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Refresh token completed successfully", args...) }(time.Now()) return lm.svc.RefreshToken(ctx, refreshToken, domainID) } -// ViewClient logs the view_client request. It logs the client id and token and the time it took to complete the request. +// ViewClient logs the view_client request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", id), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View user completed successfully", args...) }(time.Now()) return lm.svc.ViewClient(ctx, token, id) } -// ViewProfile logs the view_profile request. It logs the client id and token and the time it took to complete the request. +// ViewProfile logs the view_profile request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ViewProfile(ctx context.Context, token string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method view_profile with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("View profile failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("View profile completed successfully", args...) }(time.Now()) return lm.svc.ViewProfile(ctx, token) } -// ListClients logs the list_clients request. It logs the token and page metadata and the time it took to complete the request. +// ListClients logs the list_clients request. It logs the page metadata and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm mgclients.Page) (cp mgclients.ClientsPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_clients %d clients using token %s took %s to complete", cp.Total, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", cp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List users failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List users completed successfully", args...) }(time.Now()) return lm.svc.ListClients(ctx, token, pm) } -// UpdateClient logs the update_client request. It logs the client id and token and the time it took to complete the request. +// UpdateClient logs the update_client request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateClient(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_name_and_metadata for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.Any("metadata", c.Metadata), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update user completed successfully", args...) }(time.Now()) return lm.svc.UpdateClient(ctx, token, client) } -// UpdateClientTags logs the update_client_tags request. It logs the client id and token and the time it took to complete the request. +// UpdateClientTags logs the update_client_tags request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateClientTags(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_tags for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.Any("tags", c.Tags), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update user tags failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update user tags completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientTags(ctx, token, client) } -// UpdateClientIdentity logs the update_client_identity request. It logs the client id and token and the time it took to complete the request. +// UpdateClientIdentity logs the update_identity request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateClientIdentity(ctx context.Context, token, id, identity string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_identity for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update client identity failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update client identity completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientIdentity(ctx, token, id, identity) } -// UpdateClientSecret logs the update_client_secret request. It logs the client id and token and the time it took to complete the request. +// UpdateClientSecret logs the update_client_secret request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_secret for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update user secret failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update user secret completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientSecret(ctx, token, oldSecret, newSecret) } -// GenerateResetToken logs the generate_reset_token request. It logs the email and host and the time it took to complete the request. +// GenerateResetToken logs the generate_reset_token request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) GenerateResetToken(ctx context.Context, email, host string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method generate_reset_token for email %s and host %s took %s to complete", email, host, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("host", host), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Generate reset token failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Generate reset token completed successfully", args...) }(time.Now()) return lm.svc.GenerateResetToken(ctx, email, host) } -// ResetSecret logs the reset_secret request. It logs the token and the time it took to complete the request. +// ResetSecret logs the reset_secret request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ResetSecret(ctx context.Context, token, secret string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method reset_secret using token %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Reset secret failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Reset secret completed successfully", args...) }(time.Now()) return lm.svc.ResetSecret(ctx, token, secret) } -// SendPasswordReset logs the send_password_reset request. It logs the token and the time it took to complete the request. +// SendPasswordReset logs the send_password_reset request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) (err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method send_password_reset using token %s took %s to complete", token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("host", host), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Send password reset failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Send password reset completed successfully", args...) }(time.Now()) return lm.svc.SendPasswordReset(ctx, host, email, user, token) } -// UpdateClientRole logs the update_client_role request. It logs the client id and token and the time it took to complete the request. +// UpdateClientRole logs the update_client_role request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) UpdateClientRole(ctx context.Context, token string, client mgclients.Client) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_role for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.String("role", client.Role.String()), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Update user role failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Update user role completed successfully", args...) }(time.Now()) return lm.svc.UpdateClientRole(ctx, token, client) } -// EnableClient logs the enable_client request. It logs the client id and token and the time it took to complete the request. +// EnableClient logs the enable_client request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) EnableClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method enable_client for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", id), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Enable user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Enable user completed successfully", args...) }(time.Now()) return lm.svc.EnableClient(ctx, token, id) } -// DisableClient logs the disable_client request. It logs the client id and token and the time it took to complete the request. +// DisableClient logs the disable_client request. It logs the client id and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) DisableClient(ctx context.Context, token, id string) (c mgclients.Client, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method disable_client for client with id %s using token %s took %s to complete", c.ID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("user", + slog.String("id", id), + slog.String("name", c.Name), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Disable user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Disable user completed successfully", args...) }(time.Now()) return lm.svc.DisableClient(ctx, token, id) } -// ListMembers logs the list_members request. It logs the group id, token and the time it took to complete the request. +// ListMembers logs the list_members request. It logs the group id, and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, cp mgclients.Page) (mp mgclients.MembersPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_members %d members for object kind %s and object id %s and token %s took %s to complete", mp.Total, objectKind, objectID, token, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("object", + slog.String("kind", objectKind), + slog.String("id", objectID), + ), + slog.Group("page", + slog.Uint64("limit", cp.Limit), + slog.Uint64("offset", cp.Offset), + slog.Uint64("total", mp.Total), + ), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("List members failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("List members completed successfully", args...) }(time.Now()) return lm.svc.ListMembers(ctx, token, objectKind, objectID, cp) } -// Identify logs the identify request. It logs the token and the time it took to complete the request. +// Identify logs the identify request. It logs the time it took to complete the request. func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method identify for token %s with id %s took %s to complete", token, id, time.Since(begin)) + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", id), + } if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Identify user failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Identify user completed successfully", args...) }(time.Now()) return lm.svc.Identify(ctx, token) } diff --git a/ws/api/logging.go b/ws/api/logging.go index c04e932501..7ecce696fa 100644 --- a/ws/api/logging.go +++ b/ws/api/logging.go @@ -5,7 +5,6 @@ package api import ( "context" - "fmt" "log/slog" "time" @@ -28,16 +27,19 @@ func LoggingMiddleware(svc ws.Service, logger *slog.Logger) ws.Service { // If the request fails, it logs the error. func (lm *loggingMiddleware) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *ws.Client) (err error) { defer func(begin time.Time) { - destChannel := chanID + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + } if subtopic != "" { - destChannel = fmt.Sprintf("%s.%s", destChannel, subtopic) + args = append(args, "subtopic", subtopic) } - message := fmt.Sprintf("Method subscribe to channel %s took %s to complete", destChannel, time.Since(begin)) if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err)) + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Subscibe failed to complete successfully", args...) return } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) + lm.logger.Info("Subscribe completed successfully", args...) }(time.Now()) return lm.svc.Subscribe(ctx, thingKey, chanID, subtopic, c) From 6f1fb126fab7b6ec192657347a0080f5da4e5404 Mon Sep 17 00:00:00 2001 From: Felix Gateru Date: Thu, 25 Jan 2024 16:03:20 +0300 Subject: [PATCH 27/71] NOISSUE - Improve tests in Auth service (#262) Signed-off-by: felix.gateru --- .github/workflows/check-generated-files.yml | 3 + auth.pb.go | 427 ++-- auth.proto | 4 +- auth/api/grpc/client.go | 21 +- auth/api/grpc/endpoint.go | 4 +- auth/api/grpc/endpoint_test.go | 1087 ++++++-- auth/api/grpc/requests.go | 5 - auth/api/grpc/responses.go | 4 +- auth/api/grpc/server.go | 8 +- auth/api/grpc/setup_test.go | 18 +- auth/api/http/domains/endpoint_test.go | 1331 ++++++++++ auth/api/http/keys/endpoint_test.go | 28 +- auth/api/http/keys/requests_test.go | 88 + auth/api/http/keys/transport.go | 57 +- auth/domains.go | 3 + auth/domains_test.go | 186 ++ auth/jwt/token_test.go | 59 +- auth/mocks/auth_client.go | 127 + auth/mocks/domains.go | 18 + auth/mocks/service.go | 659 ++++- auth/postgres/domains.go | 53 +- auth/postgres/domains_test.go | 1072 ++++++++ auth/service.go | 9 +- auth/service_test.go | 2498 ++++++++++++++++++- bootstrap/api/endpoint_test.go | 6 +- bootstrap/events/producer/streams_test.go | 4 +- bootstrap/service_test.go | 4 +- certs/service_test.go | 4 +- cmd/users/main.go | 2 +- consumers/notifiers/api/endpoint_test.go | 4 +- consumers/notifiers/service_test.go | 4 +- http/api/endpoint_test.go | 2 +- internal/groups/service_test.go | 46 +- invitations/service_test.go | 10 +- mqtt/handler_test.go | 4 +- pkg/sdk/go/certs_test.go | 2 +- pkg/sdk/go/channels_test.go | 6 +- pkg/sdk/go/consumers_test.go | 4 +- pkg/sdk/go/groups_test.go | 6 +- pkg/sdk/go/message_test.go | 4 +- pkg/sdk/go/things_test.go | 16 +- pkg/sdk/go/users_test.go | 8 +- readers/api/endpoint_test.go | 4 +- things/api/http/endpoints_test.go | 2 +- things/service.go | 2 +- things/service_test.go | 34 +- twins/mocks/service.go | 4 +- users/api/endpoint_test.go | 2 +- users/service.go | 4 +- users/service_test.go | 44 +- ws/adapter_test.go | 4 +- ws/api/endpoint_test.go | 2 +- 52 files changed, 7162 insertions(+), 845 deletions(-) create mode 100644 auth/api/http/domains/endpoint_test.go create mode 100644 auth/api/http/keys/requests_test.go create mode 100644 auth/domains_test.go create mode 100644 auth/mocks/auth_client.go diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index d55e2d3a6b..9773002293 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -49,6 +49,7 @@ jobs: - "auth/domains.go" - "auth/keys.go" - "auth/policies.go" + - "auth/service.go" - "pkg/events/events.go" - "provision/service.go" - "pkg/groups/groups.go" @@ -109,6 +110,7 @@ jobs: mv ./auth/mocks/authz.go ./auth/mocks/authz.go.tmp mv ./auth/mocks/domains.go ./auth/mocks/domains.go.tmp mv ./auth/mocks/keys.go ./auth/mocks/keys.go.tmp + mv ./auth/mocks/service.go ./auth/mocks/service.go.tmp mv ./pkg/events/mocks/publisher.go ./pkg/events/mocks/publisher.go.tmp mv ./pkg/events/mocks/subscriber.go ./pkg/events/mocks/subscriber.go.tmp mv ./provision/mocks/service.go ./provision/mocks/service.go.tmp @@ -140,6 +142,7 @@ jobs: check_mock_changes ./auth/mocks/authz.go "Auth Authz ./auth/mocks/authz.go" check_mock_changes ./auth/mocks/domains.go "Auth Domains ./auth/mocks/domains.go" check_mock_changes ./auth/mocks/keys.go "Auth Keys ./auth/mocks/keys.go" + check_mock_changes ./auth/mocks/service.go "Auth Service ./auth/mocks/service.go" check_mock_changes ./pkg/events/mocks/publisher.go "ES Publisher ./pkg/events/mocks/publisher.go" check_mock_changes ./pkg/events/mocks/subscriber.go "EE Subscriber ./pkg/events/mocks/subscriber.go" check_mock_changes ./provision/mocks/service.go "Provision Service ./provision/mocks/service.go" diff --git a/auth.pb.go b/auth.pb.go index 76e29a55ea..fc9b881932 100644 --- a/auth.pb.go +++ b/auth.pb.go @@ -654,7 +654,7 @@ type AddPolicyRes struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` + Added bool `protobuf:"varint,1,opt,name=added,proto3" json:"added,omitempty"` } func (x *AddPolicyRes) Reset() { @@ -689,9 +689,9 @@ func (*AddPolicyRes) Descriptor() ([]byte, []int) { return file_auth_proto_rawDescGZIP(), []int{9} } -func (x *AddPolicyRes) GetAuthorized() bool { +func (x *AddPolicyRes) GetAdded() bool { if x != nil { - return x.Authorized + return x.Added } return false } @@ -701,7 +701,7 @@ type AddPoliciesRes struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` + Added bool `protobuf:"varint,1,opt,name=added,proto3" json:"added,omitempty"` } func (x *AddPoliciesRes) Reset() { @@ -736,9 +736,9 @@ func (*AddPoliciesRes) Descriptor() ([]byte, []int) { return file_auth_proto_rawDescGZIP(), []int{10} } -func (x *AddPoliciesRes) GetAuthorized() bool { +func (x *AddPoliciesRes) GetAdded() bool { if x != nil { - return x.Authorized + return x.Added } return false } @@ -1938,119 +1938,45 @@ var file_auth_proto_rawDesc = []byte{ 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2e, 0x0a, 0x0c, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x22, 0x30, 0x0a, 0x0e, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, - 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x22, 0xca, 0x02, - 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, - 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, - 0x49, 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, - 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, 0x4c, 0x69, - 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, - 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xac, - 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, - 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, - 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x27, 0x0a, - 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xad, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, + 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, + 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, @@ -2069,10 +1995,61 @@ var file_auth_proto_rawDesc = []byte{ 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0x28, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, + 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, @@ -2081,103 +2058,125 @@ var file_auth_proto_rawDesc = []byte{ 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, 0x0c, 0x41, - 0x75, 0x74, 0x68, 0x7a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, - 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0xc6, - 0x08, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, - 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, - 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0x00, 0x12, 0x36, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, - 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, - 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, - 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, - 0x09, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, - 0x12, 0x47, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, - 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, + 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, + 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, + 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, + 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, + 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, + 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, + 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, - 0x12, 0x4a, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, + 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, + 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, + 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/auth.proto b/auth.proto index 80732096e5..3b370b995b 100644 --- a/auth.proto +++ b/auth.proto @@ -98,9 +98,9 @@ message AddPoliciesReq{ repeated AddPolicyReq addPoliciesReq= 1; } -message AddPolicyRes { bool authorized = 1; } +message AddPolicyRes { bool added = 1; } -message AddPoliciesRes { bool authorized = 1; } +message AddPoliciesRes { bool added = 1; } message DeletePolicyReq { string domain = 1; diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index bdae9eea99..ff0b3418c6 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -292,12 +292,12 @@ func (client grpcClient) AddPolicy(ctx context.Context, in *magistrala.AddPolicy } apr := res.(addPolicyRes) - return &magistrala.AddPolicyRes{Authorized: apr.authorized}, nil + return &magistrala.AddPolicyRes{Added: apr.added}, nil } func decodeAddPolicyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(*magistrala.AddPolicyRes) - return addPolicyRes{authorized: res.Authorized}, nil + return addPolicyRes{added: res.Added}, nil } func encodeAddPolicyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { @@ -341,12 +341,12 @@ func (client grpcClient) AddPolicies(ctx context.Context, in *magistrala.AddPoli } apr := res.(addPoliciesRes) - return &magistrala.AddPoliciesRes{Authorized: apr.authorized}, nil + return &magistrala.AddPoliciesRes{Added: apr.added}, nil } func decodeAddPoliciesResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(*magistrala.AddPoliciesRes) - return addPoliciesRes{authorized: res.Authorized}, nil + return addPoliciesRes{added: res.Added}, nil } func encodeAddPoliciesRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { @@ -532,7 +532,7 @@ func (client grpcClient) CountObjects(ctx context.Context, in *magistrala.CountO ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() - res, err := client.countObjects(ctx, listObjectsReq{ + res, err := client.countObjects(ctx, countObjectsReq{ Domain: in.GetDomain(), SubjectType: in.GetSubjectType(), Subject: in.GetSubject(), @@ -712,11 +712,12 @@ func decodeListPermissionsResponse(_ context.Context, grpcRes interface{}) (inte func encodeListPermissionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(listPermissionsReq) return &magistrala.ListPermissionsReq{ - Domain: req.Domain, - SubjectType: req.SubjectType, - Subject: req.Subject, - ObjectType: req.ObjectType, - Object: req.Object, + Domain: req.Domain, + SubjectType: req.SubjectType, + Subject: req.Subject, + ObjectType: req.ObjectType, + Object: req.Object, + FilterPermissions: req.FilterPermissions, }, nil } diff --git a/auth/api/grpc/endpoint.go b/auth/api/grpc/endpoint.go index e15f7fdfb1..c691ea732e 100644 --- a/auth/api/grpc/endpoint.go +++ b/auth/api/grpc/endpoint.go @@ -117,7 +117,7 @@ func addPolicyEndpoint(svc auth.Service) endpoint.Endpoint { if err != nil { return addPolicyRes{}, err } - return addPolicyRes{authorized: true}, err + return addPolicyRes{added: true}, err } } @@ -147,7 +147,7 @@ func addPoliciesEndpoint(svc auth.Service) endpoint.Endpoint { if err := svc.AddPolicies(ctx, prs); err != nil { return addPoliciesRes{}, err } - return addPoliciesRes{authorized: true}, nil + return addPoliciesRes{added: true}, nil } } diff --git a/auth/api/grpc/endpoint_test.go b/auth/api/grpc/endpoint_test.go index 318161e465..699777a817 100644 --- a/auth/api/grpc/endpoint_test.go +++ b/auth/api/grpc/endpoint_test.go @@ -13,7 +13,10 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "google.golang.org/grpc" @@ -38,6 +41,15 @@ const ( loginDuration = 30 * time.Minute refreshDuration = 24 * time.Hour invalidDuration = 7 * 24 * time.Hour + validToken = "valid" + inValidToken = "invalid" + validPolicy = "valid" +) + +var ( + validID = testsutil.GenerateUUID(&testing.T{}) + domainID = testsutil.GenerateUUID(&testing.T{}) + authAddr = fmt.Sprintf("localhost:%d", port) ) func startGRPCServer(svc auth.Service, port int) { @@ -45,333 +57,956 @@ func startGRPCServer(svc auth.Service, port int) { server := grpc.NewServer() magistrala.RegisterAuthServiceServer(server, grpcapi.NewServer(svc)) go func() { - if err := server.Serve(listener); err != nil { - panic(fmt.Sprintf("failed to serve: %s", err)) - } + err := server.Serve(listener) + assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating server %s"`, err)) }() } func TestIssue(t *testing.T) { - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) client := grpcapi.NewClient(conn, time.Second) cases := []struct { - desc string - id string - email string - kind auth.KeyType - err error - code codes.Code + desc string + userId string + domainID string + kind auth.KeyType + issueResponse auth.Token + err error }{ { - desc: "issue for user with valid token", - id: id, - email: email, - kind: auth.AccessKey, - err: nil, - code: codes.OK, + desc: "issue for user with valid token", + userId: validID, + domainID: domainID, + kind: auth.AccessKey, + issueResponse: auth.Token{ + AccessToken: validToken, + RefreshToken: validToken, + }, + err: nil, }, { - desc: "issue recovery key", - id: id, - email: email, - kind: auth.RecoveryKey, - err: nil, - code: codes.OK, + desc: "issue recovery key", + userId: validID, + domainID: domainID, + kind: auth.RecoveryKey, + issueResponse: auth.Token{ + AccessToken: validToken, + RefreshToken: validToken, + }, + err: nil, }, { - desc: "issue API key unauthenticated", - id: id, - email: email, - kind: auth.APIKey, - err: errors.ErrAuthentication, - code: codes.Unauthenticated, + desc: "issue API key unauthenticated", + userId: validID, + domainID: domainID, + kind: auth.APIKey, + issueResponse: auth.Token{}, + err: errors.ErrAuthentication, }, { - desc: "issue for invalid key type", - id: id, - email: email, - kind: 32, - err: errors.ErrMalformedEntity, - code: codes.InvalidArgument, + desc: "issue for invalid key type", + userId: validID, + domainID: domainID, + kind: 32, + issueResponse: auth.Token{}, + err: errors.ErrMalformedEntity, }, { - desc: "issue for user that exist", - id: "", - email: "", - kind: auth.APIKey, - err: errors.ErrAuthentication, - code: codes.Unauthenticated, + desc: "issue for user that does notexist", + userId: "", + domainID: "", + kind: auth.APIKey, + issueResponse: auth.Token{}, + err: errors.ErrAuthentication, }, } for _, tc := range cases { - _, err := client.Issue(context.Background(), &magistrala.IssueReq{UserId: tc.id, Type: uint32(tc.kind)}) + repoCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) + _, err := client.Issue(context.Background(), &magistrala.IssueReq{UserId: tc.userId, DomainId: &tc.domainID, Type: uint32(tc.kind)}) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } -func TestIdentify(t *testing.T) { - repocall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - loginToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: id, IssuedAt: time.Now(), Domain: groupName}) - assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err)) - repocall.Unset() - - recoveryToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing recovery key expected to succeed: %s", err)) - - repocall1 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - apiToken, err := svc.Issue(context.Background(), loginToken.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing API key expected to succeed: %s", err)) - repocall1.Unset() - - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) +func TestRefresh(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) client := grpcapi.NewClient(conn, time.Second) cases := []struct { - desc string - token string - idt *magistrala.IdentityRes - err error - code codes.Code + desc string + token string + domainID string + issueResponse auth.Token + err error }{ { - desc: "identify user with user token", - token: loginToken.AccessToken, - idt: &magistrala.IdentityRes{Id: id, UserId: id, DomainId: groupName}, - err: nil, - code: codes.OK, + desc: "refresh token with valid token", + token: validToken, + domainID: domainID, + issueResponse: auth.Token{ + AccessToken: validToken, + RefreshToken: validToken, + }, + err: nil, }, { - desc: "identify user with recovery token", - token: recoveryToken.AccessToken, - idt: &magistrala.IdentityRes{Id: id}, - err: nil, - code: codes.OK, + desc: "refresh token with invalid token", + token: inValidToken, + domainID: domainID, + issueResponse: auth.Token{}, + err: errors.ErrAuthentication, + }, + { + desc: "refresh token with empty token", + token: "", + domainID: domainID, + issueResponse: auth.Token{}, + err: apiutil.ErrMissingSecret, }, + } + + for _, tc := range cases { + repoCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) + _, err := client.Refresh(context.Background(), &magistrala.RefreshReq{DomainId: &tc.domainID, RefreshToken: tc.token}) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestIdentify(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + idt *magistrala.IdentityRes + svcErr error + err error + }{ { - desc: "identify user with API token", - token: apiToken.AccessToken, - idt: &magistrala.IdentityRes{Id: id}, + desc: "identify user with valid user token", + token: validToken, + idt: &magistrala.IdentityRes{Id: id, UserId: email, DomainId: domainID}, err: nil, - code: codes.OK, }, { - desc: "identify user with invalid user token", - token: "invalid", - idt: &magistrala.IdentityRes{}, - err: status.Error(codes.Unauthenticated, "unauthenticated access"), - code: codes.Unauthenticated, + desc: "identify user with invalid user token", + token: "invalid", + idt: &magistrala.IdentityRes{}, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "identify user with empty token", token: "", idt: &magistrala.IdentityRes{}, - err: status.Error(codes.InvalidArgument, "received invalid token request"), - code: codes.Unauthenticated, + err: apiutil.ErrBearerToken, }, } for _, tc := range cases { - repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{Subject: id}, nil) - idt, _ := client.Identify(context.Background(), &magistrala.IdentityReq{Token: tc.token}) + repoCall := svc.On("Identify", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{Subject: id, User: email, Domain: domainID}, tc.svcErr) + idt, err := client.Identify(context.Background(), &magistrala.IdentityReq{Token: tc.token}) if idt != nil { assert.Equal(t, tc.idt, idt, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.idt, idt)) } - repocall.Unset() + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } func TestAuthorize(t *testing.T) { - repocall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id, User: id, Domain: groupName}) - assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err)) - repocall.Unset() - - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) client := grpcapi.NewClient(conn, time.Second) cases := []struct { - desc string - token string - subject string - subjecttype string - object string - objecttype string - relation string - permission string - ar *magistrala.AuthorizeRes - err error - code codes.Code + desc string + token string + authRequest *magistrala.AuthorizeReq + authResponse *magistrala.AuthorizeRes + err error }{ { - desc: "authorize user with authorized token", - token: token.AccessToken, - subject: id, - subjecttype: usersType, - object: authoritiesObj, - objecttype: usersType, - relation: memberRelation, - permission: adminpermission, - ar: &magistrala.AuthorizeRes{Authorized: true}, - err: nil, - code: codes.OK, - }, - { - desc: "authorize user with unauthorized relation", - token: token.AccessToken, - subject: id, - object: authoritiesObj, - relation: "unauthorizedRelation", - ar: &magistrala.AuthorizeRes{Authorized: false}, - err: nil, - code: codes.PermissionDenied, - }, - { - desc: "authorize user with unauthorized object", - token: token.AccessToken, - subject: id, - object: "unauthorizedobject", - relation: memberRelation, - ar: &magistrala.AuthorizeRes{Authorized: false}, - err: nil, - code: codes.PermissionDenied, - }, - { - desc: "authorize user with unauthorized subject", - token: token.AccessToken, - subject: "unauthorizedSubject", - object: authoritiesObj, - relation: memberRelation, - ar: &magistrala.AuthorizeRes{Authorized: false}, - err: nil, - code: codes.PermissionDenied, - }, - { - desc: "authorize user with invalid ACL", - token: token.AccessToken, - subject: "", - object: "", - relation: "", - ar: &magistrala.AuthorizeRes{Authorized: false}, - err: nil, - code: codes.InvalidArgument, + desc: "authorize user with authorized token", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: usersType, + Object: authoritiesObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + err: nil, + }, + { + desc: "authorize user with unauthorized token", + token: inValidToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: usersType, + Object: authoritiesObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: svcerr.ErrAuthorization, + }, + { + desc: "authorize user with empty subject", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: "", + SubjectType: usersType, + Object: authoritiesObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: apiutil.ErrMissingPolicySub, + }, + { + desc: "authorize user with empty subject type", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: "", + Object: authoritiesObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: apiutil.ErrMissingPolicySub, + }, + { + desc: "authorize user with empty object", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: usersType, + Object: "", + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: apiutil.ErrMissingPolicyObj, + }, + { + desc: "authorize user with empty object type", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: usersType, + Object: authoritiesObj, + ObjectType: "", + Relation: memberRelation, + Permission: adminpermission, + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: apiutil.ErrMissingPolicyObj, + }, + { + desc: "authorize user with empty permission", + token: validToken, + authRequest: &magistrala.AuthorizeReq{ + Subject: id, + SubjectType: usersType, + Object: authoritiesObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: "", + }, + authResponse: &magistrala.AuthorizeRes{Authorized: false}, + err: apiutil.ErrMalformedPolicyPer, }, } for _, tc := range cases { - repocall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - ar, _ := client.Authorize(context.Background(), &magistrala.AuthorizeReq{Subject: tc.subject, SubjectType: tc.subjecttype, Object: tc.object, ObjectType: tc.objecttype, Relation: tc.relation, Permission: tc.permission}) + repocall := svc.On("Authorize", mock.Anything, mock.Anything).Return(tc.err) + ar, err := client.Authorize(context.Background(), tc.authRequest) if ar != nil { - assert.Equal(t, tc.ar, ar, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.ar, ar)) + assert.Equal(t, tc.authResponse, ar, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.authResponse, ar)) } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repocall.Unset() } } func TestAddPolicy(t *testing.T) { - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err)) + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + groupAdminObj := "groupadmin" + + cases := []struct { + desc string + token string + addPolicyReq *magistrala.AddPolicyReq + addPolicyRes *magistrala.AddPolicyRes + err error + }{ + { + desc: "add groupadmin policy to user", + token: validToken, + addPolicyReq: &magistrala.AddPolicyReq{ + Subject: id, + SubjectType: usersType, + Object: groupAdminObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + addPolicyRes: &magistrala.AddPolicyRes{Added: true}, + err: nil, + }, + { + desc: "add groupadmin policy to user with invalid token", + token: inValidToken, + addPolicyReq: &magistrala.AddPolicyReq{ + Subject: id, + SubjectType: usersType, + Object: groupAdminObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + addPolicyRes: &magistrala.AddPolicyRes{Added: false}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("AddPolicy", mock.Anything, mock.Anything).Return(tc.err) + apr, err := client.AddPolicy(context.Background(), tc.addPolicyReq) + if apr != nil { + assert.Equal(t, tc.addPolicyRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.addPolicyRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) +func TestAddPolicies(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) client := grpcapi.NewClient(conn, time.Second) groupAdminObj := "groupadmin" cases := []struct { - desc string - token string - subject string - subjecttype string - object string - objecttype string - relation string - permission string - ar *magistrala.AddPolicyRes - err error - code codes.Code + desc string + token string + pr *magistrala.AddPoliciesReq + ar *magistrala.AddPoliciesRes + err error }{ { - desc: "add groupadmin policy to user", - token: token.AccessToken, - subject: id, - subjecttype: usersType, - object: groupAdminObj, - objecttype: usersType, - relation: memberRelation, - permission: adminpermission, - ar: &magistrala.AddPolicyRes{Authorized: true}, - err: nil, - code: codes.OK, + desc: "add groupadmin policy to user", + token: validToken, + pr: &magistrala.AddPoliciesReq{ + AddPoliciesReq: []*magistrala.AddPolicyReq{ + { + Subject: id, + SubjectType: usersType, + Object: groupAdminObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + }, + }, + ar: &magistrala.AddPoliciesRes{Added: true}, + err: nil, + }, + { + desc: "add groupadmin policy to user with invalid token", + token: inValidToken, + pr: &magistrala.AddPoliciesReq{ + AddPoliciesReq: []*magistrala.AddPolicyReq{ + { + Subject: id, + SubjectType: usersType, + Object: groupAdminObj, + ObjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + }, + }, + ar: &magistrala.AddPoliciesRes{Added: false}, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repocall := prepo.On("AddPolicy", mock.Anything, mock.Anything).Return(nil) - apr, err := client.AddPolicy(context.Background(), &magistrala.AddPolicyReq{Subject: tc.subject, SubjectType: tc.subjecttype, Object: tc.object, ObjectType: tc.objecttype, Relation: tc.relation, Permission: tc.permission}) + repoCall := svc.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.err) + apr, err := client.AddPolicies(context.Background(), tc.pr) if apr != nil { assert.Equal(t, tc.ar, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.ar, apr)) } - repocall.Unset() - - e, ok := status.FromError(err) - assert.True(t, ok, "gRPC status can't be extracted from the error") - assert.Equal(t, tc.code, e.Code(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.code, e.Code())) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } func TestDeletePolicy(t *testing.T) { - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err)) + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + readRelation := "read" + thingID := "thing" + + cases := []struct { + desc string + token string + deletePolicyReq *magistrala.DeletePolicyReq + deletePolicyRes *magistrala.DeletePolicyRes + err error + }{ + { + desc: "delete valid policy", + token: validToken, + deletePolicyReq: &magistrala.DeletePolicyReq{ + Subject: id, + SubjectType: usersType, + Object: thingID, + ObjectType: thingsType, + Relation: readRelation, + Permission: readRelation, + }, + deletePolicyRes: &magistrala.DeletePolicyRes{Deleted: true}, + err: nil, + }, + { + desc: "delete invalid policy with invalid token", + token: inValidToken, + deletePolicyReq: &magistrala.DeletePolicyReq{ + Subject: id, + SubjectType: usersType, + Object: thingID, + ObjectType: thingsType, + Relation: readRelation, + Permission: readRelation, + }, + deletePolicyRes: &magistrala.DeletePolicyRes{Deleted: false}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("DeletePolicy", mock.Anything, mock.Anything).Return(tc.err) + dpr, err := client.DeletePolicy(context.Background(), tc.deletePolicyReq) + assert.Equal(t, tc.deletePolicyRes.GetDeleted(), dpr.GetDeleted(), fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.deletePolicyRes.GetDeleted(), dpr.GetDeleted())) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) +func TestDeletePolicies(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) client := grpcapi.NewClient(conn, time.Second) readRelation := "read" thingID := "thing" - repocall := prepo.On("AddPolicy", mock.Anything, mock.Anything).Return(nil) - apr, err := client.AddPolicy(context.Background(), &magistrala.AddPolicyReq{Domain: groupName, Subject: id, SubjectType: usersType, Object: thingID, ObjectType: thingsType, Permission: readRelation}) - assert.Nil(t, err, fmt.Sprintf("Adding read policy to user expected to succeed: %s", err)) - assert.True(t, apr.GetAuthorized(), fmt.Sprintf("Adding read policy expected to make user authorized, expected %v got %v", true, apr.GetAuthorized())) - repocall.Unset() + cases := []struct { + desc string + token string + deletePoliciesReq *magistrala.DeletePoliciesReq + deletePoliciesRes *magistrala.DeletePoliciesRes + err error + }{ + { + desc: "delete policies with valid token", + token: validToken, + deletePoliciesReq: &magistrala.DeletePoliciesReq{ + DeletePoliciesReq: []*magistrala.DeletePolicyReq{ + { + Subject: id, + SubjectType: usersType, + Object: thingID, + ObjectType: thingsType, + Relation: readRelation, + Permission: readRelation, + }, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{Deleted: true}, + err: nil, + }, + { + desc: "delete policies with invalid token", + token: inValidToken, + deletePoliciesReq: &magistrala.DeletePoliciesReq{ + DeletePoliciesReq: []*magistrala.DeletePolicyReq{ + { + Subject: id, + SubjectType: usersType, + Object: thingID, + ObjectType: thingsType, + Relation: readRelation, + Permission: readRelation, + }, + }, + }, + deletePoliciesRes: &magistrala.DeletePoliciesRes{Deleted: false}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.err) + apr, err := client.DeletePolicies(context.Background(), tc.deletePoliciesReq) + if apr != nil { + assert.Equal(t, tc.deletePoliciesRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.deletePoliciesRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestListObjects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + listObjectsReq *magistrala.ListObjectsReq + listObjectsRes *magistrala.ListObjectsRes + err error + }{ + { + desc: "list objects with valid token", + token: validToken, + listObjectsReq: &magistrala.ListObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + listObjectsRes: &magistrala.ListObjectsRes{ + Policies: []string{validPolicy}, + }, + err: nil, + }, + { + desc: "list objects with invalid token", + token: inValidToken, + listObjectsReq: &magistrala.ListObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + listObjectsRes: &magistrala.ListObjectsRes{}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("ListObjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.PolicyPage{Policies: tc.listObjectsRes.Policies}, tc.err) + apr, err := client.ListObjects(context.Background(), tc.listObjectsReq) + if apr != nil { + assert.Equal(t, tc.listObjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.listObjectsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestListAllObjects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) cases := []struct { - desc string - token string - subject string - subjecttype string - object string - objecttype string - relation string - permission string - dpr *magistrala.DeletePolicyRes - code codes.Code + desc string + token string + listAllObjectsReq *magistrala.ListObjectsReq + listAllObjectsRes *magistrala.ListObjectsRes + err error }{ { - desc: "delete valid policy", - token: token.AccessToken, - subject: id, - subjecttype: usersType, - object: thingID, - objecttype: thingsType, - relation: readRelation, - permission: readRelation, - dpr: &magistrala.DeletePolicyRes{Deleted: true}, - code: codes.OK, + desc: "list all objects with valid token", + token: validToken, + listAllObjectsReq: &magistrala.ListObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + listAllObjectsRes: &magistrala.ListObjectsRes{ + Policies: []string{validPolicy}, + }, + err: nil, + }, + { + desc: "list all objects with invalid token", + token: inValidToken, + listAllObjectsReq: &magistrala.ListObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + listAllObjectsRes: &magistrala.ListObjectsRes{}, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repocall := prepo.On("DeletePolicy", mock.Anything, mock.Anything).Return(nil) - dpr, err := client.DeletePolicy(context.Background(), &magistrala.DeletePolicyReq{Subject: tc.subject, SubjectType: tc.subjecttype, Object: tc.object, ObjectType: tc.objecttype, Relation: tc.relation}) - e, ok := status.FromError(err) - repocall.Unset() + repoCall := svc.On("ListAllObjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.PolicyPage{Policies: tc.listAllObjectsRes.Policies}, tc.err) + apr, err := client.ListAllObjects(context.Background(), tc.listAllObjectsReq) + if apr != nil { + assert.Equal(t, tc.listAllObjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.listAllObjectsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestCountObects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + countObjectsReq *magistrala.CountObjectsReq + countObjectsRes *magistrala.CountObjectsRes + err error + }{ + { + desc: "count objects with valid token", + token: validToken, + countObjectsReq: &magistrala.CountObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + countObjectsRes: &magistrala.CountObjectsRes{ + Count: 1, + }, + err: nil, + }, + { + desc: "count objects with invalid token", + token: inValidToken, + countObjectsReq: &magistrala.CountObjectsReq{ + Domain: domainID, + ObjectType: thingsType, + Relation: memberRelation, + Permission: adminpermission, + }, + countObjectsRes: &magistrala.CountObjectsRes{}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("CountObjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int(tc.countObjectsRes.Count), tc.err) + apr, err := client.CountObjects(context.Background(), tc.countObjectsReq) + if apr != nil { + assert.Equal(t, tc.countObjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.countObjectsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestListSubjects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + listSubjectsReq *magistrala.ListSubjectsReq + listSubjectsRes *magistrala.ListSubjectsRes + err error + }{ + { + desc: "list subjects with valid token", + token: validToken, + listSubjectsReq: &magistrala.ListSubjectsReq{ + Domain: domainID, + SubjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + listSubjectsRes: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + err: nil, + }, + { + desc: "list subjects with invalid token", + token: inValidToken, + listSubjectsReq: &magistrala.ListSubjectsReq{ + Domain: domainID, + SubjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + listSubjectsRes: &magistrala.ListSubjectsRes{}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("ListSubjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.PolicyPage{Policies: tc.listSubjectsRes.Policies}, tc.err) + apr, err := client.ListSubjects(context.Background(), tc.listSubjectsReq) + if apr != nil { + assert.Equal(t, tc.listSubjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.listSubjectsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestListAllSubjects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf(`"Unexpected error creating client connection %s"`, err)) + client := grpcapi.NewClient(conn, time.Second) + cases := []struct { + desc string + token string + listSubjectsReq *magistrala.ListSubjectsReq + listSubjectsRes *magistrala.ListSubjectsRes + err error + }{ + { + desc: "list all subjects with valid token", + token: validToken, + listSubjectsReq: &magistrala.ListSubjectsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Relation: memberRelation, + Permission: adminpermission, + }, + listSubjectsRes: &magistrala.ListSubjectsRes{ + Policies: []string{validPolicy}, + }, + err: nil, + }, + { + desc: "list all subjects with invalid token", + token: inValidToken, + listSubjectsReq: &magistrala.ListSubjectsReq{ + Domain: domainID, + SubjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + listSubjectsRes: &magistrala.ListSubjectsRes{}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases { + repoCall := svc.On("ListAllSubjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.PolicyPage{Policies: tc.listSubjectsRes.Policies}, tc.err) + apr, err := client.ListAllSubjects(context.Background(), tc.listSubjectsReq) + if apr != nil { + assert.Equal(t, tc.listSubjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.listSubjectsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestCountSubjects(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + countSubjectsReq *magistrala.CountSubjectsReq + countSubjectsRes *magistrala.CountSubjectsRes + err error + code codes.Code + }{ + { + desc: "count subjects with valid token", + token: validToken, + countSubjectsReq: &magistrala.CountSubjectsReq{ + Domain: domainID, + SubjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + countSubjectsRes: &magistrala.CountSubjectsRes{ + Count: 1, + }, + code: codes.OK, + err: nil, + }, + { + desc: "count subjects with invalid token", + token: inValidToken, + countSubjectsReq: &magistrala.CountSubjectsReq{ + Domain: domainID, + SubjectType: usersType, + Relation: memberRelation, + Permission: adminpermission, + }, + countSubjectsRes: &magistrala.CountSubjectsRes{}, + err: svcerr.ErrAuthentication, + code: codes.Unauthenticated, + }, + } + for _, tc := range cases { + repoCall := svc.On("CountSubjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int(tc.countSubjectsRes.Count), tc.err) + apr, err := client.CountSubjects(context.Background(), tc.countSubjectsReq) + if apr != nil { + assert.Equal(t, tc.countSubjectsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.countSubjectsRes, apr)) + } + e, ok := status.FromError(err) assert.True(t, ok, "gRPC status can't be extracted from the error") assert.Equal(t, tc.code, e.Code(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.code, e.Code())) - assert.Equal(t, tc.dpr.GetDeleted(), dpr.GetDeleted(), fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.dpr.GetDeleted(), dpr.GetDeleted())) + repoCall.Unset() + } +} + +func TestListPermissions(t *testing.T) { + conn, err := grpc.Dial(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) + client := grpcapi.NewClient(conn, time.Second) + + cases := []struct { + desc string + token string + listPermissionsReq *magistrala.ListPermissionsReq + listPermissionsRes *magistrala.ListPermissionsRes + err error + }{ + { + desc: "list permissions of thing type with valid token", + token: validToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.ThingType, + Object: validID, + FilterPermissions: []string{"view"}, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{ + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.ThingType, + Object: validID, + Permissions: []string{"view"}, + }, + err: nil, + }, + { + desc: "list permissions of group type with valid token", + token: validToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.GroupType, + Object: validID, + FilterPermissions: []string{"view"}, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{ + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.GroupType, + Object: validID, + Permissions: []string{"view"}, + }, + err: nil, + }, + { + desc: "list permissions of platform type with valid token", + token: validToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.PlatformType, + Object: validID, + FilterPermissions: []string{"view"}, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{ + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.PlatformType, + Object: validID, + Permissions: []string{"view"}, + }, + err: nil, + }, + { + desc: "list permissions of domain type with valid token", + token: validToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.DomainType, + Object: validID, + FilterPermissions: []string{"view"}, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{ + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.DomainType, + Object: validID, + Permissions: []string{"view"}, + }, + err: nil, + }, + { + desc: "list permissions of thing type with invalid token", + token: inValidToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: auth.ThingType, + Object: validID, + FilterPermissions: []string{"view"}, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{}, + err: svcerr.ErrAuthentication, + }, + { + desc: "list permissions with invalid object type", + token: validToken, + listPermissionsReq: &magistrala.ListPermissionsReq{ + Domain: domainID, + SubjectType: auth.UserType, + Subject: id, + ObjectType: "invalid", + Object: validID, + }, + listPermissionsRes: &magistrala.ListPermissionsRes{}, + err: apiutil.ErrMalformedPolicy, + }, + } + for _, tc := range cases { + repoCall := svc.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Permissions{"view"}, tc.err) + apr, err := client.ListPermissions(context.Background(), tc.listPermissionsReq) + if apr != nil { + assert.Equal(t, tc.listPermissionsRes, apr, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.listPermissionsRes, apr)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } diff --git a/auth/api/grpc/requests.go b/auth/api/grpc/requests.go index 2bb2a89f60..7df318cee2 100644 --- a/auth/api/grpc/requests.go +++ b/auth/api/grpc/requests.go @@ -100,11 +100,6 @@ func (req policyReq) validate() error { type policiesReq []policyReq func (prs policiesReq) validate() error { - for _, pr := range prs { - if err := pr.validate(); err != nil { - return nil - } - } return nil } diff --git a/auth/api/grpc/responses.go b/auth/api/grpc/responses.go index ded31a7aac..9d6c0785ce 100644 --- a/auth/api/grpc/responses.go +++ b/auth/api/grpc/responses.go @@ -21,10 +21,10 @@ type authorizeRes struct { } type addPolicyRes struct { - authorized bool + added bool } type addPoliciesRes struct { - authorized bool + added bool } type deletePolicyRes struct { diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index e50c032996..89c2a2add9 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -337,7 +337,7 @@ func decodeAddPolicyRequest(_ context.Context, grpcReq interface{}) (interface{} func encodeAddPolicyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(addPolicyRes) - return &magistrala.AddPolicyRes{Authorized: res.authorized}, nil + return &magistrala.AddPolicyRes{Added: res.added}, nil } func decodeAddPoliciesRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { @@ -361,7 +361,7 @@ func decodeAddPoliciesRequest(_ context.Context, grpcReq interface{}) (interface func encodeAddPoliciesResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(addPoliciesRes) - return &magistrala.AddPoliciesRes{Authorized: res.authorized}, nil + return &magistrala.AddPoliciesRes{Added: res.added}, nil } func decodeDeletePolicyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { @@ -478,8 +478,8 @@ func decodeCountSubjectsRequest(_ context.Context, grpcReq interface{}) (interfa } func encodeCountSubjectsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(countObjectsRes) - return &magistrala.CountObjectsRes{Count: int64(res.count)}, nil + res := grpcRes.(countSubjectsRes) + return &magistrala.CountSubjectsRes{Count: int64(res.count)}, nil } func decodeListPermissionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { diff --git a/auth/api/grpc/setup_test.go b/auth/api/grpc/setup_test.go index 0793eb8074..df3e2f2543 100644 --- a/auth/api/grpc/setup_test.go +++ b/auth/api/grpc/setup_test.go @@ -7,27 +7,13 @@ import ( "os" "testing" - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/jwt" "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/pkg/uuid" ) -var ( - svc auth.Service - krepo *mocks.KeyRepository - prepo *mocks.PolicyAgent -) +var svc *mocks.Service func TestMain(m *testing.M) { - krepo = new(mocks.KeyRepository) - prepo = new(mocks.PolicyAgent) - drepo := new(mocks.DomainsRepository) - idProvider := uuid.NewMock() - - t := jwt.New([]byte(secret)) - - svc = auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration) + svc = new(mocks.Service) startGRPCServer(svc, port) code := m.Run() diff --git a/auth/api/http/domains/endpoint_test.go b/auth/api/http/domains/endpoint_test.go new file mode 100644 index 0000000000..4a53418b47 --- /dev/null +++ b/auth/api/http/domains/endpoint_test.go @@ -0,0 +1,1331 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package domains_test + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/absmach/magistrala/auth" + httpapi "github.com/absmach/magistrala/auth/api/http/domains" + "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" + mglog "github.com/absmach/magistrala/logger" + mgclients "github.com/absmach/magistrala/pkg/clients" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + validCMetadata = mgclients.Metadata{"role": "client"} + ID = testsutil.GenerateUUID(&testing.T{}) + domain = auth.Domain{ + ID: ID, + Name: "domainname", + Tags: []string{"tag1", "tag2"}, + Metadata: validCMetadata, + Status: auth.EnabledStatus, + Alias: "mydomain", + } + validToken = "token" + inValidToken = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + + id = "testID" +) + +const ( + contentType = "application/json" + refreshDuration = 24 * time.Hour + invalidDuration = 7 * 24 * time.Hour +) + +type testRequest struct { + client *http.Client + method string + url string + contentType string + token string + body io.Reader +} + +func (tr testRequest) make() (*http.Response, error) { + req, err := http.NewRequest(tr.method, tr.url, tr.body) + if err != nil { + return nil, err + } + + if tr.token != "" { + req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) + } + + if tr.contentType != "" { + req.Header.Set("Content-Type", tr.contentType) + } + + req.Header.Set("Referer", "http://localhost") + + return tr.client.Do(req) +} + +func toJSON(data interface{}) string { + jsonData, err := json.Marshal(data) + if err != nil { + return "" + } + return string(jsonData) +} + +func newDomainsServer() (*httptest.Server, *mocks.Service) { + logger := mglog.NewMock() + mux := chi.NewRouter() + svc := new(mocks.Service) + httpapi.MakeHandler(svc, mux, logger) + return httptest.NewServer(mux), svc +} + +func TestCreateDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + domain auth.Domain + token string + contentType string + svcErr error + status int + err error + }{ + { + desc: "register a new domain successfully", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: validToken, + contentType: contentType, + status: http.StatusOK, + err: nil, + }, + { + desc: "register a new domain with empty token", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: "", + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "register a new domain with invalid token", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: inValidToken, + contentType: contentType, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "register a new domain with an empty name", + domain: auth.Domain{ + ID: ID, + Name: "", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: validToken, + contentType: contentType, + status: http.StatusInternalServerError, + err: apiutil.ErrMissingName, + }, + { + desc: "register a new domain with invalid content type", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: validToken, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "register a new domain that cant be marshalled", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + token: validToken, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + data := toJSON(tc.domain) + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains", ds.URL), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(data), + } + + svcCall := svc.On("CreateDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestListDomains(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + token string + query string + listDomainsRequest auth.DomainsPage + status int + svcErr error + err error + }{ + { + desc: "list domains with valid token", + token: validToken, + status: http.StatusOK, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + err: nil, + }, + { + desc: "list domains with empty token", + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list domains with invalid token", + token: inValidToken, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "list domains with offset", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "offset=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with limit", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "limit=1", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with invalid limit", + token: validToken, + query: "limit=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with name", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "name=domainname", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with empty name", + token: validToken, + query: "name= ", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate name", + token: validToken, + query: "name=1&name=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with status", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "status=enabled", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with invalid status", + token: validToken, + query: "status=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate status", + token: validToken, + query: "status=enabled&status=disabled", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with tags", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "tag=tag1,tag2", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with empty tags", + token: validToken, + query: "tag= ", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate tags", + token: validToken, + query: "tag=tag1&tag=tag2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with metadata", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with invalid metadata", + token: validToken, + query: "metadata=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate metadata", + token: validToken, + query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with permissions", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "permission=view", + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains with invalid permissions", + token: validToken, + query: "permission= ", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate permissions", + token: validToken, + query: "permission=view&permission=view", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with order", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "order=name", + status: http.StatusOK, + }, + { + desc: "list domains with invalid order", + token: validToken, + query: "order= ", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate order", + token: validToken, + query: "order=name&order=name", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + { + desc: "list domains with dir", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "dir=asc", + status: http.StatusOK, + }, + { + desc: "list domains with invalid dir", + token: validToken, + query: "dir= ", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains with duplicate dir", + token: validToken, + query: "dir=asc&dir=asc", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/domains?", ds.URL) + tc.query, + token: tc.token, + } + + svcCall := svc.On("ListDomains", mock.Anything, mock.Anything, mock.Anything).Return(tc.listDomainsRequest, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestViewDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + token string + domainID string + status int + svcErr error + err error + }{ + { + desc: "view domain successfully", + token: validToken, + domainID: id, + status: http.StatusOK, + err: nil, + }, + { + desc: "view domain with empty token", + token: "", + domainID: id, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "view domain with invalid token", + token: inValidToken, + domainID: id, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/domains/%s", ds.URL, tc.domainID), + token: tc.token, + } + + svcCall := svc.On("RetrieveDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestViewDomainPermissions(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + token string + domainID string + status int + svcErr error + err error + }{ + { + desc: "view domain permissions successfully", + token: validToken, + domainID: id, + status: http.StatusOK, + err: nil, + }, + { + desc: "view domain permissions with empty token", + token: "", + domainID: id, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "view domain permissions with invalid token", + token: inValidToken, + domainID: id, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "view domain permissions with empty domainID", + token: validToken, + domainID: "", + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/domains/%s/permissions", ds.URL, tc.domainID), + token: tc.token, + } + + svcCall := svc.On("RetrieveDomainPermissions", mock.Anything, mock.Anything, mock.Anything).Return(auth.Permissions{}, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUpdateDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + token string + domain auth.Domain + contentType string + status int + svcErr error + err error + }{ + { + desc: "update domain successfully", + token: validToken, + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + contentType: contentType, + status: http.StatusOK, + err: nil, + }, + { + desc: "update domain with empty token", + token: "", + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "update domain with invalid token", + token: inValidToken, + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + contentType: contentType, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "update domain with invalid content type", + token: validToken, + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: mgclients.Metadata{"role": "domain"}, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + contentType: "application/xml", + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "update domain with data that cant be marshalled", + token: validToken, + domain: auth.Domain{ + ID: ID, + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + }, + contentType: contentType, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + data := toJSON(tc.domain) + req := testRequest{ + client: ds.Client(), + method: http.MethodPatch, + url: fmt.Sprintf("%s/domains/%s", ds.URL, tc.domain.ID), + body: strings.NewReader(data), + contentType: tc.contentType, + token: tc.token, + } + + svcCall := svc.On("UpdateDomain", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var errRes respBody + err = json.NewDecoder(res.Body).Decode(&errRes) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if errRes.Err != "" || errRes.Message != "" { + err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) + } + + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestEnableDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + disabledDomain := domain + disabledDomain.Status = auth.DisabledStatus + + cases := []struct { + desc string + domain auth.Domain + response auth.Domain + token string + status int + svcErr error + err error + }{ + { + desc: "enable domain with valid token", + domain: disabledDomain, + response: auth.Domain{ + ID: domain.ID, + Status: auth.EnabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "enable domain with invalid token", + domain: disabledDomain, + token: inValidToken, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "enable domain with empty token", + domain: disabledDomain, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "enable domain with empty id", + domain: auth.Domain{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "enable domain with invalid id", + domain: auth.Domain{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + svcErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.domain) + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains/%s/enable", ds.URL, tc.domain.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestDisableDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + domain auth.Domain + response auth.Domain + token string + status int + svcErr error + err error + }{ + { + desc: "disable domain with valid token", + domain: domain, + response: auth.Domain{ + ID: domain.ID, + Status: auth.DisabledStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "disable domain with invalid token", + domain: domain, + token: inValidToken, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "disable domain with empty token", + domain: domain, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "disable domain with empty id", + domain: auth.Domain{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "disable domain with invalid id", + domain: auth.Domain{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + svcErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.domain) + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains/%s/disable", ds.URL, tc.domain.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestFreezeDomain(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + domain auth.Domain + response auth.Domain + token string + status int + svcErr error + err error + }{ + { + desc: "freeze domain with valid token", + domain: domain, + response: auth.Domain{ + ID: domain.ID, + Status: auth.FreezeStatus, + }, + token: validToken, + status: http.StatusOK, + err: nil, + }, + { + desc: "freeze domain with invalid token", + domain: domain, + token: inValidToken, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "freeze domain with empty token", + domain: domain, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "freeze domain with empty id", + domain: auth.Domain{ + ID: "", + }, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "freeze domain with invalid id", + domain: auth.Domain{ + ID: "invalid", + }, + token: validToken, + status: http.StatusForbidden, + svcErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + data := toJSON(tc.domain) + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains/%s/freeze", ds.URL, tc.domain.ID), + contentType: contentType, + token: tc.token, + body: strings.NewReader(data), + } + svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestAssignDomainUsers(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + data string + domainID string + contentType string + token string + status int + err error + }{ + { + desc: "assign domain users with valid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusCreated, + err: nil, + }, + { + desc: "assign domain users with invalid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "assign domain users with empty token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "assign domain users with empty id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: "", + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "assign domain users with invalid id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: "invalid", + contentType: contentType, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "assign domain users with malformed data", + data: fmt.Sprintf(`{"relation": "%s", user_ids : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "assign domain users with invalid content type", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "assign domain users with empty user ids", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : []}`, "editor"), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "assign domain users with empty relation", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains/%s/users/assign", ds.URL, tc.domainID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("AssignUsers", mock.Anything, tc.token, tc.domainID, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestUnassignDomainUsers(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + data string + domainID string + contentType string + token string + status int + err error + }{ + { + desc: "unassign domain users with valid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusNoContent, + err: nil, + }, + { + desc: "unassign domain users with invalid token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: inValidToken, + status: http.StatusUnauthorized, + err: svcerr.ErrAuthentication, + }, + { + desc: "unassign domain users with empty token", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: "", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "unassign domain users with empty domain id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: "", + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "unassign domain users with invalid id", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: "invalid", + contentType: contentType, + token: validToken, + status: http.StatusForbidden, + err: svcerr.ErrAuthorization, + }, + { + desc: "unassign domain users with malformed data", + data: fmt.Sprintf(`{"relation": "%s", user_ids : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "unassign domain users with invalid content type", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), + domainID: domain.ID, + contentType: "application/xml", + token: validToken, + status: http.StatusUnsupportedMediaType, + err: apiutil.ErrUnsupportedContentType, + }, + { + desc: "unassign domain users with empty user ids", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : []}`, "editor"), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + { + desc: "unassign domain users with empty relation", + data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "", validID, validID), + domainID: domain.ID, + contentType: contentType, + token: validToken, + status: http.StatusInternalServerError, + err: apiutil.ErrValidation, + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/domains/%s/users/unassign", ds.URL, tc.domainID), + contentType: tc.contentType, + token: tc.token, + body: strings.NewReader(tc.data), + } + + svcCall := svc.On("UnassignUsers", mock.Anything, tc.token, tc.domainID, mock.Anything, mock.Anything).Return(tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +func TestListDomainsByUserID(t *testing.T) { + ds, svc := newDomainsServer() + defer ds.Close() + + cases := []struct { + desc string + token string + query string + listDomainsRequest auth.DomainsPage + userID string + status int + svcErr error + err error + }{ + { + desc: "list domains by user id with valid token", + token: validToken, + status: http.StatusOK, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + userID: validID, + err: nil, + }, + { + desc: "list domains by user id with empty user id", + token: validToken, + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + }, + { + desc: "list domains by user id with empty token", + token: "", + userID: validID, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + }, + { + desc: "list domains by user id with invalid token", + token: inValidToken, + userID: validID, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "list domains by user id with offset", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "offset=1", + userID: validID, + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains by user id with invalid offset", + token: validToken, + query: "offset=invalid", + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + { + desc: "list domains by user id with limit", + token: validToken, + listDomainsRequest: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + }, + Domains: []auth.Domain{domain}, + }, + query: "limit=1", + userID: validID, + status: http.StatusOK, + err: nil, + }, + { + desc: "list domains by user id with invalid limit", + token: validToken, + query: "limit=invalid", + userID: validID, + status: http.StatusBadRequest, + err: apiutil.ErrValidation, + }, + } + for _, tc := range cases { + req := testRequest{ + client: ds.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/users/%s/domains?", ds.URL, tc.userID) + tc.query, + token: tc.token, + } + + svcCall := svc.On("ListUserDomains", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.listDomainsRequest, tc.svcErr) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + } +} + +type respBody struct { + Err string `json:"error"` + Message string `json:"message"` + Total int `json:"total"` + Permissions []string `json:"permissions"` + ID string `json:"id"` + Tags []string `json:"tags"` + Status mgclients.Status `json:"status"` +} diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 65ed1237be..ae46d02b2a 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -218,13 +218,20 @@ func TestRetrieve(t *testing.T) { desc string id string token string + key auth.Key status int err error }{ { - desc: "retrieve an existing key", - id: k.AccessToken, - token: token.AccessToken, + desc: "retrieve an existing key", + id: k.AccessToken, + token: token.AccessToken, + key: auth.Key{ + Subject: id, + Type: auth.AccessKey, + IssuedAt: time.Now(), + ExpiresAt: time.Now().Add(refreshDuration), + }, status: http.StatusOK, err: nil, }, @@ -242,6 +249,13 @@ func TestRetrieve(t *testing.T) { status: http.StatusUnauthorized, err: errors.ErrAuthentication, }, + { + desc: "retrieve a key with an empty token", + token: "", + id: k.AccessToken, + status: http.StatusUnauthorized, + err: errors.ErrAuthentication, + }, } for _, tc := range cases { @@ -251,7 +265,7 @@ func TestRetrieve(t *testing.T) { url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id), token: tc.token, } - repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) + repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(tc.key, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) @@ -298,6 +312,12 @@ func TestRevoke(t *testing.T) { token: "wrong", status: http.StatusUnauthorized, }, + { + desc: "revoke key with empty token", + id: k.AccessToken, + token: "", + status: http.StatusUnauthorized, + }, } for _, tc := range cases { diff --git a/auth/api/http/keys/requests_test.go b/auth/api/http/keys/requests_test.go new file mode 100644 index 0000000000..7ab8ae7041 --- /dev/null +++ b/auth/api/http/keys/requests_test.go @@ -0,0 +1,88 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package keys + +import ( + "testing" + + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/stretchr/testify/assert" +) + +var valid = "valid" + +func TestIssueKeyReqValidate(t *testing.T) { + cases := []struct { + desc string + req issueKeyReq + err error + }{ + { + desc: "valid request", + req: issueKeyReq{ + token: valid, + Type: auth.AccessKey, + }, + err: nil, + }, + { + desc: "empty token", + req: issueKeyReq{ + token: "", + Type: auth.AccessKey, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "invalid key type", + req: issueKeyReq{ + token: valid, + Type: auth.KeyType(100), + }, + err: apiutil.ErrInvalidAPIKey, + }, + } + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err) + } +} + +func TestKeyReqValidate(t *testing.T) { + cases := []struct { + desc string + req keyReq + err error + }{ + { + desc: "valid request", + req: keyReq{ + token: valid, + id: valid, + }, + err: nil, + }, + { + desc: "empty token", + req: keyReq{ + token: "", + id: valid, + }, + err: apiutil.ErrBearerToken, + }, + { + desc: "empty id", + req: keyReq{ + token: valid, + id: "", + }, + err: apiutil.ErrMissingID, + }, + } + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err) + } +} diff --git a/auth/api/http/keys/transport.go b/auth/api/http/keys/transport.go index cbd2afd317..c66c15c0e4 100644 --- a/auth/api/http/keys/transport.go +++ b/auth/api/http/keys/transport.go @@ -10,8 +10,8 @@ import ( "net/http" "strings" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" "github.com/go-chi/chi/v5" @@ -23,27 +23,27 @@ const contentType = "application/json" // MakeHandler returns a HTTP handler for API endpoints. func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } mux.Route("/keys", func(r chi.Router) { r.Post("/", kithttp.NewServer( issueEndpoint(svc), decodeIssue, - encodeResponse, + api.EncodeResponse, opts..., ).ServeHTTP) r.Get("/{id}", kithttp.NewServer( (retrieveEndpoint(svc)), decodeKeyReq, - encodeResponse, + api.EncodeResponse, opts..., ).ServeHTTP) r.Delete("/{id}", kithttp.NewServer( (revokeEndpoint(svc)), decodeKeyReq, - encodeResponse, + api.EncodeResponse, opts..., ).ServeHTTP) }) @@ -52,7 +52,7 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.ErrUnsupportedContentType + return nil, apiutil.ErrUnsupportedContentType } req := issueKeyReq{token: apiutil.ExtractBearerToken(r)} @@ -70,48 +70,3 @@ func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) { } return req, nil } - -func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", contentType) - - if ar, ok := response.(magistrala.Response); ok { - for k, v := range ar.Headers() { - w.Header().Set(k, v) - } - - w.WriteHeader(ar.Code()) - - if ar.Empty() { - return nil - } - } - - return json.NewEncoder(w).Encode(response) -} - -func encodeError(_ context.Context, err error, w http.ResponseWriter) { - switch { - case errors.Contains(err, errors.ErrMalformedEntity), - err == apiutil.ErrMissingID, - err == apiutil.ErrInvalidAPIKey: - w.WriteHeader(http.StatusBadRequest) - case errors.Contains(err, errors.ErrAuthentication), - err == apiutil.ErrBearerToken: - w.WriteHeader(http.StatusUnauthorized) - case errors.Contains(err, errors.ErrNotFound): - w.WriteHeader(http.StatusNotFound) - case errors.Contains(err, errors.ErrConflict): - w.WriteHeader(http.StatusConflict) - case errors.Contains(err, errors.ErrUnsupportedContentType): - w.WriteHeader(http.StatusUnsupportedMediaType) - default: - w.WriteHeader(http.StatusInternalServerError) - } - - if errorVal, ok := err.(errors.Error); ok { - w.Header().Set("Content-Type", contentType) - if err := json.NewEncoder(w).Encode(apiutil.ErrorRes{Err: errorVal.Msg()}); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - } -} diff --git a/auth/domains.go b/auth/domains.go index a7b9febaac..c4ac9a3329 100644 --- a/auth/domains.go +++ b/auth/domains.go @@ -183,4 +183,7 @@ type DomainsRepository interface { // ListDomains list all the domains ListDomains(ctx context.Context, pm Page) (DomainsPage, error) + + // CheckPolicy check policy in domains database. + CheckPolicy(ctx context.Context, pc Policy) error } diff --git a/auth/domains_test.go b/auth/domains_test.go new file mode 100644 index 0000000000..f24a18ff6e --- /dev/null +++ b/auth/domains_test.go @@ -0,0 +1,186 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package auth_test + +import ( + "testing" + + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/internal/apiutil" + "github.com/stretchr/testify/assert" +) + +func TestStatusString(t *testing.T) { + cases := []struct { + desc string + status auth.Status + expected string + }{ + { + desc: "Enabled", + status: auth.EnabledStatus, + expected: "enabled", + }, + { + desc: "Disabled", + status: auth.DisabledStatus, + expected: "disabled", + }, + { + desc: "Freezed", + status: auth.FreezeStatus, + expected: "freezed", + }, + { + desc: "All", + status: auth.AllStatus, + expected: "all", + }, + { + desc: "Unknown", + status: auth.Status(100), + expected: "unknown", + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got := tc.status.String() + assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected) + }) + } +} + +func TestToStatus(t *testing.T) { + cases := []struct { + desc string + status string + expetcted auth.Status + err error + }{ + { + desc: "Enabled", + status: "enabled", + expetcted: auth.EnabledStatus, + err: nil, + }, + { + desc: "Disabled", + status: "disabled", + expetcted: auth.DisabledStatus, + err: nil, + }, + { + desc: "Freezed", + status: "freezed", + expetcted: auth.FreezeStatus, + err: nil, + }, + { + desc: "All", + status: "all", + expetcted: auth.AllStatus, + err: nil, + }, + { + desc: "Unknown", + status: "unknown", + expetcted: auth.Status(0), + err: apiutil.ErrInvalidStatus, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := auth.ToStatus(tc.status) + assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expetcted, got, "ToStatus() = %v, expected %v", got, tc.expetcted) + }) + } +} + +func TestStatusMarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected []byte + status auth.Status + err error + }{ + { + desc: "Enabled", + expected: []byte(`"enabled"`), + status: auth.EnabledStatus, + err: nil, + }, + { + desc: "Disabled", + expected: []byte(`"disabled"`), + status: auth.DisabledStatus, + err: nil, + }, + { + desc: "All", + expected: []byte(`"all"`), + status: auth.AllStatus, + err: nil, + }, + { + desc: "Unknown", + expected: []byte(`"unknown"`), + status: auth.Status(100), + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.status.MarshalJSON() + assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) + }) + } +} + +func TestStatusUnmarshalJSON(t *testing.T) { + cases := []struct { + desc string + expected auth.Status + status []byte + err error + }{ + { + desc: "Enabled", + expected: auth.EnabledStatus, + status: []byte(`"enabled"`), + err: nil, + }, + { + desc: "Disabled", + expected: auth.DisabledStatus, + status: []byte(`"disabled"`), + err: nil, + }, + { + desc: "All", + expected: auth.AllStatus, + status: []byte(`"all"`), + err: nil, + }, + { + desc: "Unknown", + expected: auth.Status(0), + status: []byte(`"unknown"`), + err: apiutil.ErrInvalidStatus, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + var s auth.Status + err := s.UnmarshalJSON(tc.status) + assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) + assert.Equal(t, tc.expected, s, "UnmarshalJSON() = %v, expected %v", s, tc.expected) + }) + } +} diff --git a/auth/jwt/token_test.go b/auth/jwt/token_test.go index a0e324a390..d441727f69 100644 --- a/auth/jwt/token_test.go +++ b/auth/jwt/token_test.go @@ -9,13 +9,26 @@ import ( "time" "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/jwt" + authjwt "github.com/absmach/magistrala/auth/jwt" "github.com/absmach/magistrala/pkg/errors" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const secret = "test" +const ( + tokenType = "type" + userField = "user" + domainField = "domain" + issuerName = "magistrala.auth" + secret = "test" +) + +var ( + errInvalidIssuer = errors.New("invalid token issuer value") + reposecret = []byte("test") +) func key() auth.Key { exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) @@ -29,8 +42,26 @@ func key() auth.Key { } } +func newToken(issuerName string, key auth.Key) string { + builder := jwt.NewBuilder() + builder. + Issuer(issuerName). + IssuedAt(key.IssuedAt). + Subject(key.Subject). + Claim(tokenType, "r"). + Expiration(key.ExpiresAt) + builder.Claim(userField, key.User) + builder.Claim(domainField, key.Domain) + if key.ID != "" { + builder.JwtID(key.ID) + } + tkn, _ := builder.Build() + tokn, _ := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, reposecret)) + return string(tokn) +} + func TestIssue(t *testing.T) { - tokenizer := jwt.New([]byte(secret)) + tokenizer := authjwt.New([]byte(secret)) cases := []struct { desc string @@ -54,7 +85,7 @@ func TestIssue(t *testing.T) { } func TestParse(t *testing.T) { - tokenizer := jwt.New([]byte(secret)) + tokenizer := authjwt.New([]byte(secret)) token, err := tokenizer.Issue(key()) require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) @@ -70,6 +101,8 @@ func TestParse(t *testing.T) { expToken, err := tokenizer.Issue(expKey) require.Nil(t, err, fmt.Sprintf("issuing expired key expected to succeed: %s", err)) + inValidToken := newToken("invalid", key()) + cases := []struct { desc string key auth.Key @@ -83,7 +116,7 @@ func TestParse(t *testing.T) { err: nil, }, { - desc: "parse ivalid key", + desc: "parse invalid key", key: auth.Key{}, token: "invalid", err: errors.ErrAuthentication, @@ -92,13 +125,25 @@ func TestParse(t *testing.T) { desc: "parse expired key", key: auth.Key{}, token: expToken, - err: jwt.ErrExpiry, + err: authjwt.ErrExpiry, }, { desc: "parse expired API key", key: apiKey, token: apiToken, - err: jwt.ErrExpiry, + err: authjwt.ErrExpiry, + }, + { + desc: "parse token with invalid issuer", + key: auth.Key{}, + token: inValidToken, + err: errInvalidIssuer, + }, + { + desc: "parse token with invalid content", + key: auth.Key{}, + token: newToken(issuerName, key()), + err: authjwt.ErrJSONHandle, }, } diff --git a/auth/mocks/auth_client.go b/auth/mocks/auth_client.go new file mode 100644 index 0000000000..0d166cb3b5 --- /dev/null +++ b/auth/mocks/auth_client.go @@ -0,0 +1,127 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mocks + +import ( + context "context" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +const InvalidValue = "invalid" + +var _ magistrala.AuthServiceClient = (*AuthClient)(nil) + +type AuthClient struct { + mock.Mock +} + +func (m *AuthClient) Issue(ctx context.Context, in *magistrala.IssueReq, opts ...grpc.CallOption) (*magistrala.Token, error) { + ret := m.Called(ctx, in) + if in.GetUserId() == InvalidValue || in.GetUserId() == "" { + return &magistrala.Token{}, svcerr.ErrAuthentication + } + + return ret.Get(0).(*magistrala.Token), ret.Error(1) +} + +func (m *AuthClient) Refresh(ctx context.Context, in *magistrala.RefreshReq, opts ...grpc.CallOption) (*magistrala.Token, error) { + ret := m.Called(ctx, in) + if in.GetRefreshToken() == InvalidValue || in.GetRefreshToken() == "" { + return &magistrala.Token{}, svcerr.ErrAuthentication + } + + return ret.Get(0).(*magistrala.Token), ret.Error(1) +} + +func (m *AuthClient) Identify(ctx context.Context, in *magistrala.IdentityReq, opts ...grpc.CallOption) (*magistrala.IdentityRes, error) { + ret := m.Called(ctx, in) + if in.GetToken() == InvalidValue || in.GetToken() == "" { + return &magistrala.IdentityRes{}, svcerr.ErrAuthentication + } + + return ret.Get(0).(*magistrala.IdentityRes), ret.Error(1) +} + +func (m *AuthClient) Authorize(ctx context.Context, in *magistrala.AuthorizeReq, opts ...grpc.CallOption) (*magistrala.AuthorizeRes, error) { + ret := m.Called(ctx, in) + if in.GetSubject() == InvalidValue || in.GetSubject() == "" { + return &magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization + } + if in.GetObject() == InvalidValue || in.GetObject() == "" { + return &magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization + } + + return ret.Get(0).(*magistrala.AuthorizeRes), ret.Error(1) +} + +func (m *AuthClient) AddPolicy(ctx context.Context, in *magistrala.AddPolicyReq, opts ...grpc.CallOption) (*magistrala.AddPolicyRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.AddPolicyRes), ret.Error(1) +} + +func (m *AuthClient) AddPolicies(ctx context.Context, in *magistrala.AddPoliciesReq, opts ...grpc.CallOption) (*magistrala.AddPoliciesRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.AddPoliciesRes), ret.Error(1) +} + +func (m *AuthClient) DeletePolicy(ctx context.Context, in *magistrala.DeletePolicyReq, opts ...grpc.CallOption) (*magistrala.DeletePolicyRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.DeletePolicyRes), ret.Error(1) +} + +func (m *AuthClient) DeletePolicies(ctx context.Context, in *magistrala.DeletePoliciesReq, opts ...grpc.CallOption) (*magistrala.DeletePoliciesRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.DeletePoliciesRes), ret.Error(1) +} + +func (m *AuthClient) ListObjects(ctx context.Context, in *magistrala.ListObjectsReq, opts ...grpc.CallOption) (*magistrala.ListObjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.ListObjectsRes), ret.Error(1) +} + +func (m *AuthClient) ListAllObjects(ctx context.Context, in *magistrala.ListObjectsReq, opts ...grpc.CallOption) (*magistrala.ListObjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.ListObjectsRes), ret.Error(1) +} + +func (m *AuthClient) CountObjects(ctx context.Context, in *magistrala.CountObjectsReq, opts ...grpc.CallOption) (*magistrala.CountObjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.CountObjectsRes), ret.Error(1) +} + +func (m *AuthClient) ListSubjects(ctx context.Context, in *magistrala.ListSubjectsReq, opts ...grpc.CallOption) (*magistrala.ListSubjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.ListSubjectsRes), ret.Error(1) +} + +func (m *AuthClient) ListAllSubjects(ctx context.Context, in *magistrala.ListSubjectsReq, opts ...grpc.CallOption) (*magistrala.ListSubjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.ListSubjectsRes), ret.Error(1) +} + +func (m *AuthClient) CountSubjects(ctx context.Context, in *magistrala.CountSubjectsReq, opts ...grpc.CallOption) (*magistrala.CountSubjectsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.CountSubjectsRes), ret.Error(1) +} + +func (m *AuthClient) ListPermissions(ctx context.Context, in *magistrala.ListPermissionsReq, opts ...grpc.CallOption) (*magistrala.ListPermissionsRes, error) { + ret := m.Called(ctx, in) + + return ret.Get(0).(*magistrala.ListPermissionsRes), ret.Error(1) +} diff --git a/auth/mocks/domains.go b/auth/mocks/domains.go index 7973b91c58..42a8c73f6c 100644 --- a/auth/mocks/domains.go +++ b/auth/mocks/domains.go @@ -17,6 +17,24 @@ type DomainsRepository struct { mock.Mock } +// CheckPolicy provides a mock function with given fields: ctx, pc +func (_m *DomainsRepository) CheckPolicy(ctx context.Context, pc auth.Policy) error { + ret := _m.Called(ctx, pc) + + if len(ret) == 0 { + panic("no return value specified for CheckPolicy") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, auth.Policy) error); ok { + r0 = rf(ctx, pc) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Delete provides a mock function with given fields: ctx, id func (_m *DomainsRepository) Delete(ctx context.Context, id string) error { ret := _m.Called(ctx, id) diff --git a/auth/mocks/service.go b/auth/mocks/service.go index 3991463ded..daa26c9b28 100644 --- a/auth/mocks/service.go +++ b/auth/mocks/service.go @@ -1,127 +1,656 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + // Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 package mocks import ( context "context" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" -) - -const InvalidValue = "invalid" + auth "github.com/absmach/magistrala/auth" -var _ magistrala.AuthServiceClient = (*Service)(nil) + mock "github.com/stretchr/testify/mock" +) +// Service is an autogenerated mock type for the Service type type Service struct { mock.Mock } -func (m *Service) Issue(ctx context.Context, in *magistrala.IssueReq, opts ...grpc.CallOption) (*magistrala.Token, error) { - ret := m.Called(ctx, in) - if in.GetUserId() == InvalidValue || in.GetUserId() == "" { - return &magistrala.Token{}, svcerr.ErrAuthentication +// AddPolicies provides a mock function with given fields: ctx, prs +func (_m *Service) AddPolicies(ctx context.Context, prs []auth.PolicyReq) error { + ret := _m.Called(ctx, prs) + + if len(ret) == 0 { + panic("no return value specified for AddPolicies") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []auth.PolicyReq) error); ok { + r0 = rf(ctx, prs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddPolicy provides a mock function with given fields: ctx, pr +func (_m *Service) AddPolicy(ctx context.Context, pr auth.PolicyReq) error { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for AddPolicy") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) error); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AssignUsers provides a mock function with given fields: ctx, token, id, userIds, relation +func (_m *Service) AssignUsers(ctx context.Context, token string, id string, userIds []string, relation string) error { + ret := _m.Called(ctx, token, id, userIds, relation) + + if len(ret) == 0 { + panic("no return value specified for AssignUsers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, string) error); ok { + r0 = rf(ctx, token, id, userIds, relation) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Authorize provides a mock function with given fields: ctx, pr +func (_m *Service) Authorize(ctx context.Context, pr auth.PolicyReq) error { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for Authorize") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) error); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ChangeDomainStatus provides a mock function with given fields: ctx, token, id, d +func (_m *Service) ChangeDomainStatus(ctx context.Context, token string, id string, d auth.DomainReq) (auth.Domain, error) { + ret := _m.Called(ctx, token, id, d) + + if len(ret) == 0 { + panic("no return value specified for ChangeDomainStatus") + } + + var r0 auth.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) (auth.Domain, error)); ok { + return rf(ctx, token, id, d) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) auth.Domain); ok { + r0 = rf(ctx, token, id, d) + } else { + r0 = ret.Get(0).(auth.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.DomainReq) error); ok { + r1 = rf(ctx, token, id, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CountObjects provides a mock function with given fields: ctx, pr +func (_m *Service) CountObjects(ctx context.Context, pr auth.PolicyReq) (int, error) { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for CountObjects") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) (int, error)); ok { + return rf(ctx, pr) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) int); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq) error); ok { + r1 = rf(ctx, pr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CountSubjects provides a mock function with given fields: ctx, pr +func (_m *Service) CountSubjects(ctx context.Context, pr auth.PolicyReq) (int, error) { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for CountSubjects") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) (int, error)); ok { + return rf(ctx, pr) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) int); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq) error); ok { + r1 = rf(ctx, pr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateDomain provides a mock function with given fields: ctx, token, d +func (_m *Service) CreateDomain(ctx context.Context, token string, d auth.Domain) (auth.Domain, error) { + ret := _m.Called(ctx, token, d) + + if len(ret) == 0 { + panic("no return value specified for CreateDomain") + } + + var r0 auth.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Domain) (auth.Domain, error)); ok { + return rf(ctx, token, d) + } + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Domain) auth.Domain); ok { + r0 = rf(ctx, token, d) + } else { + r0 = ret.Get(0).(auth.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, auth.Domain) error); ok { + r1 = rf(ctx, token, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeletePolicies provides a mock function with given fields: ctx, prs +func (_m *Service) DeletePolicies(ctx context.Context, prs []auth.PolicyReq) error { + ret := _m.Called(ctx, prs) + + if len(ret) == 0 { + panic("no return value specified for DeletePolicies") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []auth.PolicyReq) error); ok { + r0 = rf(ctx, prs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeletePolicy provides a mock function with given fields: ctx, pr +func (_m *Service) DeletePolicy(ctx context.Context, pr auth.PolicyReq) error { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for DeletePolicy") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) error); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Identify provides a mock function with given fields: ctx, token +func (_m *Service) Identify(ctx context.Context, token string) (auth.Key, error) { + ret := _m.Called(ctx, token) + + if len(ret) == 0 { + panic("no return value specified for Identify") + } + + var r0 auth.Key + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (auth.Key, error)); ok { + return rf(ctx, token) + } + if rf, ok := ret.Get(0).(func(context.Context, string) auth.Key); ok { + r0 = rf(ctx, token) + } else { + r0 = ret.Get(0).(auth.Key) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, token) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Issue provides a mock function with given fields: ctx, token, key +func (_m *Service) Issue(ctx context.Context, token string, key auth.Key) (auth.Token, error) { + ret := _m.Called(ctx, token, key) + + if len(ret) == 0 { + panic("no return value specified for Issue") + } + + var r0 auth.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Key) (auth.Token, error)); ok { + return rf(ctx, token, key) + } + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Key) auth.Token); ok { + r0 = rf(ctx, token, key) + } else { + r0 = ret.Get(0).(auth.Token) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, auth.Key) error); ok { + r1 = rf(ctx, token, key) + } else { + r1 = ret.Error(1) } - return ret.Get(0).(*magistrala.Token), ret.Error(1) + return r0, r1 } -func (m *Service) Refresh(ctx context.Context, in *magistrala.RefreshReq, opts ...grpc.CallOption) (*magistrala.Token, error) { - ret := m.Called(ctx, in) - if in.GetRefreshToken() == InvalidValue || in.GetRefreshToken() == "" { - return &magistrala.Token{}, svcerr.ErrAuthentication +// ListAllObjects provides a mock function with given fields: ctx, pr +func (_m *Service) ListAllObjects(ctx context.Context, pr auth.PolicyReq) (auth.PolicyPage, error) { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for ListAllObjects") } - return ret.Get(0).(*magistrala.Token), ret.Error(1) + var r0 auth.PolicyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) (auth.PolicyPage, error)); ok { + return rf(ctx, pr) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) auth.PolicyPage); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Get(0).(auth.PolicyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq) error); ok { + r1 = rf(ctx, pr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) Identify(ctx context.Context, in *magistrala.IdentityReq, opts ...grpc.CallOption) (*magistrala.IdentityRes, error) { - ret := m.Called(ctx, in) - if in.GetToken() == InvalidValue || in.GetToken() == "" { - return &magistrala.IdentityRes{}, svcerr.ErrAuthentication +// ListAllSubjects provides a mock function with given fields: ctx, pr +func (_m *Service) ListAllSubjects(ctx context.Context, pr auth.PolicyReq) (auth.PolicyPage, error) { + ret := _m.Called(ctx, pr) + + if len(ret) == 0 { + panic("no return value specified for ListAllSubjects") } - return ret.Get(0).(*magistrala.IdentityRes), ret.Error(1) + var r0 auth.PolicyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) (auth.PolicyPage, error)); ok { + return rf(ctx, pr) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq) auth.PolicyPage); ok { + r0 = rf(ctx, pr) + } else { + r0 = ret.Get(0).(auth.PolicyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq) error); ok { + r1 = rf(ctx, pr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) Authorize(ctx context.Context, in *magistrala.AuthorizeReq, opts ...grpc.CallOption) (*magistrala.AuthorizeRes, error) { - ret := m.Called(ctx, in) - if in.GetSubject() == InvalidValue || in.GetSubject() == "" { - return &magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization +// ListDomains provides a mock function with given fields: ctx, token, page +func (_m *Service) ListDomains(ctx context.Context, token string, page auth.Page) (auth.DomainsPage, error) { + ret := _m.Called(ctx, token, page) + + if len(ret) == 0 { + panic("no return value specified for ListDomains") } - if in.GetObject() == InvalidValue || in.GetObject() == "" { - return &magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization + + var r0 auth.DomainsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Page) (auth.DomainsPage, error)); ok { + return rf(ctx, token, page) + } + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Page) auth.DomainsPage); ok { + r0 = rf(ctx, token, page) + } else { + r0 = ret.Get(0).(auth.DomainsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, auth.Page) error); ok { + r1 = rf(ctx, token, page) + } else { + r1 = ret.Error(1) } - return ret.Get(0).(*magistrala.AuthorizeRes), ret.Error(1) + return r0, r1 } -func (m *Service) AddPolicy(ctx context.Context, in *magistrala.AddPolicyReq, opts ...grpc.CallOption) (*magistrala.AddPolicyRes, error) { - ret := m.Called(ctx, in) +// ListObjects provides a mock function with given fields: ctx, pr, nextPageToken, limit +func (_m *Service) ListObjects(ctx context.Context, pr auth.PolicyReq, nextPageToken string, limit int32) (auth.PolicyPage, error) { + ret := _m.Called(ctx, pr, nextPageToken, limit) + + if len(ret) == 0 { + panic("no return value specified for ListObjects") + } + + var r0 auth.PolicyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, string, int32) (auth.PolicyPage, error)); ok { + return rf(ctx, pr, nextPageToken, limit) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, string, int32) auth.PolicyPage); ok { + r0 = rf(ctx, pr, nextPageToken, limit) + } else { + r0 = ret.Get(0).(auth.PolicyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq, string, int32) error); ok { + r1 = rf(ctx, pr, nextPageToken, limit) + } else { + r1 = ret.Error(1) + } - return ret.Get(0).(*magistrala.AddPolicyRes), ret.Error(1) + return r0, r1 } -func (m *Service) AddPolicies(ctx context.Context, in *magistrala.AddPoliciesReq, opts ...grpc.CallOption) (*magistrala.AddPoliciesRes, error) { - ret := m.Called(ctx, in) +// ListPermissions provides a mock function with given fields: ctx, pr, filterPermission +func (_m *Service) ListPermissions(ctx context.Context, pr auth.PolicyReq, filterPermission []string) (auth.Permissions, error) { + ret := _m.Called(ctx, pr, filterPermission) - return ret.Get(0).(*magistrala.AddPoliciesRes), ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for ListPermissions") + } + + var r0 auth.Permissions + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, []string) (auth.Permissions, error)); ok { + return rf(ctx, pr, filterPermission) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, []string) auth.Permissions); ok { + r0 = rf(ctx, pr, filterPermission) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(auth.Permissions) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq, []string) error); ok { + r1 = rf(ctx, pr, filterPermission) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) DeletePolicy(ctx context.Context, in *magistrala.DeletePolicyReq, opts ...grpc.CallOption) (*magistrala.DeletePolicyRes, error) { - ret := m.Called(ctx, in) +// ListSubjects provides a mock function with given fields: ctx, pr, nextPageToken, limit +func (_m *Service) ListSubjects(ctx context.Context, pr auth.PolicyReq, nextPageToken string, limit int32) (auth.PolicyPage, error) { + ret := _m.Called(ctx, pr, nextPageToken, limit) + + if len(ret) == 0 { + panic("no return value specified for ListSubjects") + } + + var r0 auth.PolicyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, string, int32) (auth.PolicyPage, error)); ok { + return rf(ctx, pr, nextPageToken, limit) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.PolicyReq, string, int32) auth.PolicyPage); ok { + r0 = rf(ctx, pr, nextPageToken, limit) + } else { + r0 = ret.Get(0).(auth.PolicyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.PolicyReq, string, int32) error); ok { + r1 = rf(ctx, pr, nextPageToken, limit) + } else { + r1 = ret.Error(1) + } - return ret.Get(0).(*magistrala.DeletePolicyRes), ret.Error(1) + return r0, r1 } -func (m *Service) DeletePolicies(ctx context.Context, in *magistrala.DeletePoliciesReq, opts ...grpc.CallOption) (*magistrala.DeletePoliciesRes, error) { - ret := m.Called(ctx, in) +// ListUserDomains provides a mock function with given fields: ctx, token, userID, page +func (_m *Service) ListUserDomains(ctx context.Context, token string, userID string, page auth.Page) (auth.DomainsPage, error) { + ret := _m.Called(ctx, token, userID, page) + + if len(ret) == 0 { + panic("no return value specified for ListUserDomains") + } + + var r0 auth.DomainsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.Page) (auth.DomainsPage, error)); ok { + return rf(ctx, token, userID, page) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.Page) auth.DomainsPage); ok { + r0 = rf(ctx, token, userID, page) + } else { + r0 = ret.Get(0).(auth.DomainsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.Page) error); ok { + r1 = rf(ctx, token, userID, page) + } else { + r1 = ret.Error(1) + } - return ret.Get(0).(*magistrala.DeletePoliciesRes), ret.Error(1) + return r0, r1 } -func (m *Service) ListObjects(ctx context.Context, in *magistrala.ListObjectsReq, opts ...grpc.CallOption) (*magistrala.ListObjectsRes, error) { - ret := m.Called(ctx, in) +// RetrieveDomain provides a mock function with given fields: ctx, token, id +func (_m *Service) RetrieveDomain(ctx context.Context, token string, id string) (auth.Domain, error) { + ret := _m.Called(ctx, token, id) - return ret.Get(0).(*magistrala.ListObjectsRes), ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for RetrieveDomain") + } + + var r0 auth.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Domain, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Domain); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(auth.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) ListAllObjects(ctx context.Context, in *magistrala.ListObjectsReq, opts ...grpc.CallOption) (*magistrala.ListObjectsRes, error) { - ret := m.Called(ctx, in) +// RetrieveDomainPermissions provides a mock function with given fields: ctx, token, id +func (_m *Service) RetrieveDomainPermissions(ctx context.Context, token string, id string) (auth.Permissions, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveDomainPermissions") + } + + var r0 auth.Permissions + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Permissions, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Permissions); ok { + r0 = rf(ctx, token, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(auth.Permissions) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } - return ret.Get(0).(*magistrala.ListObjectsRes), ret.Error(1) + return r0, r1 } -func (m *Service) CountObjects(ctx context.Context, in *magistrala.CountObjectsReq, opts ...grpc.CallOption) (*magistrala.CountObjectsRes, error) { - ret := m.Called(ctx, in) +// RetrieveKey provides a mock function with given fields: ctx, token, id +func (_m *Service) RetrieveKey(ctx context.Context, token string, id string) (auth.Key, error) { + ret := _m.Called(ctx, token, id) - return ret.Get(0).(*magistrala.CountObjectsRes), ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for RetrieveKey") + } + + var r0 auth.Key + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Key, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Key); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(auth.Key) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) ListSubjects(ctx context.Context, in *magistrala.ListSubjectsReq, opts ...grpc.CallOption) (*magistrala.ListSubjectsRes, error) { - ret := m.Called(ctx, in) +// Revoke provides a mock function with given fields: ctx, token, id +func (_m *Service) Revoke(ctx context.Context, token string, id string) error { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for Revoke") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Error(0) + } - return ret.Get(0).(*magistrala.ListSubjectsRes), ret.Error(1) + return r0 } -func (m *Service) ListAllSubjects(ctx context.Context, in *magistrala.ListSubjectsReq, opts ...grpc.CallOption) (*magistrala.ListSubjectsRes, error) { - ret := m.Called(ctx, in) +// UnassignUsers provides a mock function with given fields: ctx, token, id, userIds, relation +func (_m *Service) UnassignUsers(ctx context.Context, token string, id string, userIds []string, relation string) error { + ret := _m.Called(ctx, token, id, userIds, relation) - return ret.Get(0).(*magistrala.ListSubjectsRes), ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for UnassignUsers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, string) error); ok { + r0 = rf(ctx, token, id, userIds, relation) + } else { + r0 = ret.Error(0) + } + + return r0 } -func (m *Service) CountSubjects(ctx context.Context, in *magistrala.CountSubjectsReq, opts ...grpc.CallOption) (*magistrala.CountSubjectsRes, error) { - ret := m.Called(ctx, in) +// UpdateDomain provides a mock function with given fields: ctx, token, id, d +func (_m *Service) UpdateDomain(ctx context.Context, token string, id string, d auth.DomainReq) (auth.Domain, error) { + ret := _m.Called(ctx, token, id, d) - return ret.Get(0).(*magistrala.CountSubjectsRes), ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for UpdateDomain") + } + + var r0 auth.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) (auth.Domain, error)); ok { + return rf(ctx, token, id, d) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) auth.Domain); ok { + r0 = rf(ctx, token, id, d) + } else { + r0 = ret.Get(0).(auth.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.DomainReq) error); ok { + r1 = rf(ctx, token, id, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (m *Service) ListPermissions(ctx context.Context, in *magistrala.ListPermissionsReq, opts ...grpc.CallOption) (*magistrala.ListPermissionsRes, error) { - ret := m.Called(ctx, in) +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) - return ret.Get(0).(*magistrala.ListPermissionsRes), ret.Error(1) + return mock } diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index b0e5556525..b3fe875668 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -40,7 +40,7 @@ func (repo domainRepo) Save(ctx context.Context, d auth.Domain) (ad auth.Domain, VALUES (:id, :name, :tags, :alias, :metadata, :created_at, :updated_at, :updated_by, :created_by, :status) RETURNING id, name, tags, alias, metadata, created_at, updated_at, updated_by, created_by, status;` - dbd, err := toDBDomains(d) + dbd, err := toDBDomain(d) if err != nil { return auth.Domain{}, errors.Wrap(repoerr.ErrCreateEntity, repoerr.ErrRollbackTx) } @@ -74,22 +74,26 @@ func (repo domainRepo) RetrieveByID(ctx context.Context, id string) (auth.Domain ID: id, } - row, err := repo.db.NamedQueryContext(ctx, q, dbdp) + rows, err := repo.db.NamedQueryContext(ctx, q, dbdp) if err != nil { - if err == sql.ErrNoRows { - return auth.Domain{}, errors.Wrap(errors.ErrNotFound, err) - } - return auth.Domain{}, errors.Wrap(errors.ErrViewEntity, err) + return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) } + defer rows.Close() - defer row.Close() - row.Next() dbd := dbDomain{} - if err := row.StructScan(&dbd); err != nil { - return auth.Domain{}, errors.Wrap(errors.ErrNotFound, err) - } + if rows.Next() { + if err = rows.StructScan(&dbd); err != nil { + return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + domain, err := toDomain(dbd) + if err != nil { + return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } - return toDomain(dbd) + return domain, nil + } + return auth.Domain{}, repoerr.ErrNotFound } func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id string) ([]string, error) { @@ -131,9 +135,6 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth if err != nil { return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } - if query == "" { - return auth.DomainsPage{}, nil - } q = `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status FROM domains d` @@ -179,9 +180,6 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma if err != nil { return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } - if query == "" { - return auth.DomainsPage{}, nil - } q = `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status, pc.relation as relation FROM domains as d @@ -269,7 +267,7 @@ func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.Do RETURNING id, name, tags, alias, metadata, created_at, updated_at, updated_by, created_by, status;`, upq, ws) - dbd, err := toDBDomains(d) + dbd, err := toDBDomain(d) if err != nil { return auth.Domain{}, errors.Wrap(errors.ErrUpdateEntity, err) } @@ -295,18 +293,15 @@ func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.Do // Delete delete domain from database. func (repo domainRepo) Delete(ctx context.Context, id string) error { - q := fmt.Sprintf(` - DELETE FROM - domains - WHERE - id = '%s' - ;`, id) + q := "DELETE FROM domains WHERE id = $1;" - row, err := repo.db.NamedQueryContext(ctx, q, nil) + res, err := repo.db.ExecContext(ctx, q, id) if err != nil { return postgres.HandleError(repoerr.ErrRemoveEntity, err) } - defer row.Close() + if rows, _ := res.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } return nil } @@ -349,7 +344,7 @@ func (repo domainRepo) CheckPolicy(ctx context.Context, pc auth.Policy) error { defer row.Close() row.Next() if err := row.StructScan(&dbpc); err != nil { - return err + return errors.Wrap(repoerr.ErrNotFound, err) } return nil } @@ -421,7 +416,7 @@ type dbDomain struct { UpdatedAt sql.NullTime `db:"updated_at,omitempty"` } -func toDBDomains(d auth.Domain) (dbDomain, error) { +func toDBDomain(d auth.Domain) (dbDomain, error) { data := []byte("{}") if len(d.Metadata) > 0 { b, err := json.Marshal(d.Metadata) diff --git a/auth/postgres/domains_test.go b/auth/postgres/domains_test.go index 32c2a69b8f..4ecf721854 100644 --- a/auth/postgres/domains_test.go +++ b/auth/postgres/domains_test.go @@ -7,11 +7,25 @@ import ( "context" "fmt" "testing" + "time" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/auth/postgres" + "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + inValid = "invalid" +) + +var ( + domainID = testsutil.GenerateUUID(&testing.T{}) + userID = testsutil.GenerateUUID(&testing.T{}) ) func TestAddPolicyCopy(t *testing.T) { @@ -76,3 +90,1061 @@ func TestDeletePolicyCopy(t *testing.T) { assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) } } + +func TestSave(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + cases := []struct { + desc string + domain auth.Domain + err error + }{ + { + desc: "add new domain with all fields successfully", + domain: auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + }, + err: nil, + }, + { + desc: "add the same domain again", + domain: auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + }, + err: repoerr.ErrConflict, + }, + { + desc: "add domain with empty ID", + domain: auth.Domain{ + ID: "", + Name: "test1", + Alias: "test1", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + }, + err: nil, + }, + { + desc: "add domain with malformed metadata", + domain: auth.Domain{ + ID: domainID, + Name: "test1", + Alias: "test1", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + }, + err: errors.ErrCreateEntity, + }, + } + + for _, tc := range cases { + _, err := repo.Save(context.Background(), tc.domain) + { + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } + } +} + +func TestRetrieveByID(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + domain := auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + response auth.Domain + err error + }{ + { + desc: "retrieve existing client", + domainID: domain.ID, + response: domain, + err: nil, + }, + { + desc: "retrieve non-existing client", + domainID: inValid, + response: auth.Domain{}, + err: errors.ErrNotFound, + }, + { + desc: "retrieve with empty client id", + domainID: "", + response: auth.Domain{}, + err: errors.ErrNotFound, + }, + } + + for _, tc := range cases { + d, err := repo.RetrieveByID(context.Background(), tc.domainID) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestRetreivePermissions(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + _, err = db.Exec("DELETE FROM policies") + require.Nil(t, err, fmt.Sprintf("clean policies unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + domain := auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + Permission: "admin", + } + + policy := auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: "admin", + Relation: "admin", + ObjectType: auth.DomainType, + ObjectID: domainID, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save domain %s", domain.ID)) + + err = repo.SavePolicies(context.Background(), policy) + require.Nil(t, err, fmt.Sprintf("failed to save policy %s", policy.SubjectID)) + + cases := []struct { + desc string + domainID string + policySubject string + response []string + err error + }{ + { + desc: "retrieve existing permissions with valid domaiinID and policySubject", + domainID: domain.ID, + policySubject: userID, + response: []string{"admin"}, + err: nil, + }, + { + desc: "retreieve permissions with invalid domainID", + domainID: inValid, + policySubject: userID, + response: []string{}, + err: nil, + }, + { + desc: "retreieve permissions with invalid policySubject", + domainID: domain.ID, + policySubject: inValid, + response: []string{}, + err: nil, + }, + } + + for _, tc := range cases { + d, err := repo.RetrievePermissions(context.Background(), tc.policySubject, tc.domainID) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestRetrieveAllByIDs(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + items := []auth.Domain{} + for i := 0; i < 10; i++ { + domain := auth.Domain{ + ID: testsutil.GenerateUUID(t), + Name: fmt.Sprintf(`"test%d"`, i), + Alias: fmt.Sprintf(`"test%d"`, i), + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + } + if i%5 == 0 { + domain.Status = auth.DisabledStatus + domain.Tags = []string{"test", "admin"} + domain.Metadata = map[string]interface{}{ + "test1": "test1", + } + } + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("save domain unexpected error: %s", err)) + items = append(items, domain) + } + + cases := []struct { + desc string + pm auth.Page + response auth.DomainsPage + err error + }{ + { + desc: "retrieve by ids successfully", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 2, + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + }, + Domains: []auth.Domain{items[1], items[2]}, + }, + err: nil, + }, + { + desc: "retrieve by ids with empty ids", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 0, + Offset: 0, + Limit: 0, + }, + }, + err: nil, + }, + { + desc: "retrieve by ids with invalid ids", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{inValid}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 0, + Offset: 0, + Limit: 10, + IDs: []string{inValid}, + }, + }, + err: nil, + }, + { + desc: "retrieve by ids and status", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Status: auth.DisabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + Offset: 0, + Limit: 10, + Status: auth.DisabledStatus, + IDs: []string{items[0].ID, items[1].ID}, + }, + Domains: []auth.Domain{items[0]}, + }, + }, + { + desc: "retrieve by ids and status with invalid status", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Status: 5, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 2, + Offset: 0, + Limit: 10, + Status: 5, + IDs: []string{items[0].ID, items[1].ID}, + }, + Domains: []auth.Domain{items[0], items[1]}, + }, + }, + { + desc: "retrieve by ids and tags", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Tag: "test", + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + Offset: 0, + Limit: 10, + Tag: "test", + IDs: []string{items[0].ID, items[1].ID}, + }, + Domains: []auth.Domain{items[1]}, + }, + }, + { + desc: " retrieve by ids and metadata", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test": "test", + }, + Status: auth.EnabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 2, + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test": "test", + }, + Status: auth.EnabledStatus, + }, + Domains: items[1:3], + }, + }, + { + desc: "retrieve by ids and metadata with invalid metadata", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + Status: auth.EnabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 0, + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + Status: auth.EnabledStatus, + }, + }, + }, + { + desc: "retrieve by ids and malfomed metadata", + pm: auth.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + Status: auth.EnabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{}, + }, + err: errors.ErrViewEntity, + }, + { + desc: "retrieve all by ids and id", + pm: auth.Page{ + Offset: 0, + Limit: 10, + ID: items[1].ID, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + Offset: 0, + Limit: 10, + ID: items[1].ID, + IDs: []string{items[1].ID, items[2].ID}, + }, + Domains: []auth.Domain{items[1]}, + }, + }, + { + desc: "retrieve all by ids and id with invalid id", + pm: auth.Page{ + Offset: 0, + Limit: 10, + ID: inValid, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 0, + Offset: 0, + Limit: 10, + ID: inValid, + IDs: []string{items[1].ID, items[2].ID}, + }, + }, + }, + { + desc: "retrieve all by ids and name", + pm: auth.Page{ + Offset: 0, + Limit: 10, + Name: items[1].Name, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 1, + Offset: 0, + Limit: 10, + Name: items[1].Name, + IDs: []string{items[1].ID, items[2].ID}, + }, + Domains: []auth.Domain{items[1]}, + }, + }, + { + desc: "retrieve all by ids with empty page", + pm: auth.Page{}, + response: auth.DomainsPage{}, + }, + } + + for _, tc := range cases { + d, err := repo.RetrieveAllByIDs(context.Background(), tc.pm) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestListDomains(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + items := []auth.Domain{} + rDomains := []auth.Domain{} + policies := []auth.Policy{} + for i := 0; i < 10; i++ { + domain := auth.Domain{ + ID: testsutil.GenerateUUID(t), + Name: fmt.Sprintf(`"test%d"`, i), + Alias: fmt.Sprintf(`"test%d"`, i), + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + } + if i%5 == 0 { + domain.Status = auth.DisabledStatus + domain.Tags = []string{"test", "admin"} + domain.Metadata = map[string]interface{}{ + "test1": "test1", + } + } + policy := auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: domain.ID, + } + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("save domain unexpected error: %s", err)) + items = append(items, domain) + policies = append(policies, policy) + rDomain := domain + rDomain.Permission = "domain" + rDomains = append(rDomains, rDomain) + } + + err := repo.SavePolicies(context.Background(), policies...) + require.Nil(t, err, fmt.Sprintf("failed to save policies %s", policies)) + + cases := []struct { + desc string + pm auth.Page + response auth.DomainsPage + err error + }{ + { + desc: "list all domains successfully", + pm: auth.Page{ + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 10, + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + }, + Domains: items, + }, + err: nil, + }, + { + desc: "list domains with empty page", + pm: auth.Page{ + Offset: 0, + Limit: 0, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 8, + Offset: 0, + Limit: 0, + }, + }, + err: nil, + }, + { + desc: "list domains with enabled status", + pm: auth.Page{ + Offset: 0, + Limit: 10, + Status: auth.EnabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 8, + Offset: 0, + Limit: 10, + Status: auth.EnabledStatus, + }, + Domains: []auth.Domain{items[1], items[2], items[3], items[4], items[6], items[7], items[8], items[9]}, + }, + err: nil, + }, + { + desc: "list domains with disabled status", + pm: auth.Page{ + Offset: 0, + Limit: 10, + Status: auth.DisabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 2, + Offset: 0, + Limit: 10, + Status: auth.DisabledStatus, + }, + Domains: []auth.Domain{items[0], items[5]}, + }, + err: nil, + }, + { + desc: "list domains with subject ID", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 10, + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + SubjectID: userID, + }, + Domains: rDomains, + }, + err: nil, + }, + { + desc: "list domains with subject ID and status", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Status: auth.EnabledStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 8, + Offset: 0, + Limit: 10, + Status: auth.EnabledStatus, + SubjectID: userID, + }, + Domains: []auth.Domain{rDomains[1], rDomains[2], rDomains[3], rDomains[4], rDomains[6], rDomains[7], rDomains[8], rDomains[9]}, + }, + err: nil, + }, + { + desc: "list domains with subject Id and permission", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Permission: "domain", + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 10, + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + SubjectID: userID, + Permission: "domain", + }, + Domains: rDomains, + }, + err: nil, + }, + { + desc: "list domains with subject id and tags", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Tag: "test", + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 10, + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + SubjectID: userID, + Tag: "test", + }, + Domains: rDomains, + }, + err: nil, + }, + { + desc: "list domains with subject id and metadata", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Metadata: map[string]interface{}{ + "test": "test", + }, + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{ + Total: 8, + Offset: 0, + Limit: 10, + Status: auth.AllStatus, + SubjectID: userID, + Metadata: map[string]interface{}{ + "test": "test", + }, + }, + Domains: []auth.Domain{rDomains[1], rDomains[2], rDomains[3], rDomains[4], rDomains[6], rDomains[7], rDomains[8], rDomains[9]}, + }, + }, + { + desc: "list domains with subject id and metadata with malforned metadata", + pm: auth.Page{ + Offset: 0, + Limit: 10, + SubjectID: userID, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + Status: auth.AllStatus, + }, + response: auth.DomainsPage{ + Page: auth.Page{}, + }, + err: errors.ErrViewEntity, + }, + } + + for _, tc := range cases { + d, err := repo.ListDomains(context.Background(), tc.pm) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestUpdate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + updatedName := "test1" + updatedMetadata := clients.Metadata{ + "test1": "test1", + } + updatedTags := []string{"test1"} + updatedStatus := auth.DisabledStatus + updatedAlias := "test1" + + repo := postgres.NewDomainRepository(database) + + domain := auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + d auth.DomainReq + response auth.Domain + err error + }{ + { + desc: "update existing domain name and metadata", + domainID: domain.ID, + d: auth.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: auth.Domain{ + ID: domainID, + Name: "test1", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + UpdatedAt: time.Now(), + }, + err: nil, + }, + { + desc: "update existing domain name, metadata, tags, status and alias", + domainID: domain.ID, + d: auth.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + Tags: &updatedTags, + Status: &updatedStatus, + Alias: &updatedAlias, + }, + response: auth.Domain{ + ID: domainID, + Name: "test1", + Alias: "test1", + Tags: []string{"test1"}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.DisabledStatus, + UpdatedAt: time.Now(), + }, + err: nil, + }, + { + desc: "update non-existing domain", + domainID: inValid, + d: auth.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: auth.Domain{}, + err: repoerr.ErrFailedOpDB, + }, + { + desc: "update domain with empty ID", + domainID: "", + d: auth.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: auth.Domain{}, + err: repoerr.ErrFailedOpDB, + }, + { + desc: "update domain with malformed metadata", + domainID: domainID, + d: auth.DomainReq{ + Name: &updatedName, + Metadata: &clients.Metadata{"key": make(chan int)}, + }, + response: auth.Domain{}, + err: errors.ErrUpdateEntity, + }, + } + + for _, tc := range cases { + d, err := repo.Update(context.Background(), tc.domainID, userID, tc.d) + d.UpdatedAt = tc.response.UpdatedAt + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestDelete(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + domain := auth.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: auth.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + err error + }{ + { + desc: "delete existing domain", + domainID: domain.ID, + err: nil, + }, + { + desc: "delete non-existing domain", + domainID: inValid, + err: repoerr.ErrNotFound, + }, + { + desc: "delete domain with empty ID", + domainID: "", + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + err := repo.Delete(context.Background(), tc.domainID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestCheckPolicy(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM policies") + require.Nil(t, err, fmt.Sprintf("clean policies unexpected error: %s", err)) + }) + + repo := postgres.NewDomainRepository(database) + + policy := auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: domainID, + } + + err := repo.SavePolicies(context.Background(), policy) + require.Nil(t, err, fmt.Sprintf("failed to save policy %s", policy.SubjectID)) + + cases := []struct { + desc string + policy auth.Policy + err error + }{ + { + desc: "check valid policy", + policy: policy, + err: nil, + }, + { + desc: "check policy with invalid subject type", + policy: auth.Policy{ + SubjectType: inValid, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: domainID, + }, + err: repoerr.ErrNotFound, + }, + { + desc: "check policy with invalid subject id", + policy: auth.Policy{ + SubjectType: auth.UserType, + SubjectID: inValid, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: domainID, + }, + err: repoerr.ErrNotFound, + }, + { + desc: "check policy with invalid subject relation", + policy: auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: inValid, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: domainID, + }, + err: repoerr.ErrNotFound, + }, + { + desc: "check policy with invalid relation", + policy: auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: inValid, + ObjectType: auth.DomainType, + ObjectID: domainID, + }, + err: repoerr.ErrNotFound, + }, + { + desc: "check policy with invalid object type", + policy: auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: inValid, + ObjectID: domainID, + }, + err: repoerr.ErrNotFound, + }, + { + desc: "check policy with invalid object id", + policy: auth.Policy{ + SubjectType: auth.UserType, + SubjectID: userID, + SubjectRelation: auth.AdministratorRelation, + Relation: auth.DomainRelation, + ObjectType: auth.DomainType, + ObjectID: inValid, + }, + err: repoerr.ErrNotFound, + }, + } + for _, tc := range cases { + err := repo.CheckPolicy(context.Background(), tc.policy) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} diff --git a/auth/service.go b/auth/service.go index a277a0ebea..8a1700a133 100644 --- a/auth/service.go +++ b/auth/service.go @@ -39,6 +39,9 @@ var ( // ErrFailedToRetrieveChildren failed to retrieve groups. ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups") + // ErrExpiry indicates that the token is expired. + ErrExpiry = errors.New("token is expired") + errIssueUser = errors.New("failed to issue new login key") errIssueTmp = errors.New("failed to issue new temporary key") errRevoke = errors.New("failed to remove key") @@ -76,6 +79,8 @@ type Authn interface { // implementation, and all of its decorators (e.g. logging & metrics). // Token is a string value of the actual Key and is used to authenticate // an Auth service request. + +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" type Service interface { Authn Authz @@ -151,9 +156,9 @@ func (svc service) RetrieveKey(ctx context.Context, token, id string) (Key, erro func (svc service) Identify(ctx context.Context, token string) (Key, error) { key, err := svc.tokenizer.Parse(token) - if err == ErrAPIKeyExpired { + if errors.Contains(err, ErrExpiry) { err = svc.keys.Remove(ctx, key.Issuer, key.ID) - return Key{}, errors.Wrap(ErrAPIKeyExpired, err) + return Key{}, errors.Wrap(ErrKeyExpired, err) } if err != nil { return Key{}, errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(errIdentify, err)) diff --git a/auth/service_test.go b/auth/service_test.go index 3125474f23..c8a56f52eb 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -13,11 +13,11 @@ import ( "github.com/absmach/magistrala/auth/jwt" "github.com/absmach/magistrala/auth/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/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) const ( @@ -32,18 +32,41 @@ const ( loginDuration = 30 * time.Minute refreshDuration = 24 * time.Hour invalidDuration = 7 * 24 * time.Hour + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) var ( - errIssueUser = errors.New("failed to issue new login key") - // ErrExpiry indicates that the token is expired. - ErrExpiry = errors.New("token is expired") + errIssueUser = errors.New("failed to issue new login key") + errCreateDomainPolicy = errors.New("failed to create domain policy") + errRetrieve = errors.New("failed to retrieve key data") + ErrExpiry = errors.New("token is expired") + errRollbackPolicy = errors.New("failed to rollback policy") + errAddPolicies = errors.New("failed to add policies") + errPlatform = errors.New("invalid platform id") + inValidToken = "invalid" + inValid = "invalid" + valid = "valid" + domain = auth.Domain{ + ID: validID, + Name: groupName, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + Permission: auth.AdminPermission, + CreatedBy: validID, + UpdatedBy: validID, + } +) + +var ( + krepo *mocks.KeyRepository + prepo *mocks.PolicyAgent + drepo *mocks.DomainsRepository ) -func newService() (auth.Service, *mocks.KeyRepository, string, *mocks.PolicyAgent) { - krepo := new(mocks.KeyRepository) - prepo := new(mocks.PolicyAgent) - drepo := new(mocks.DomainsRepository) +func newService() (auth.Service, string) { + krepo = new(mocks.KeyRepository) + prepo = new(mocks.PolicyAgent) + drepo = new(mocks.DomainsRepository) idProvider := uuid.NewMock() t := jwt.New([]byte(secret)) @@ -57,17 +80,70 @@ func newService() (auth.Service, *mocks.KeyRepository, string, *mocks.PolicyAgen } token, _ := t.Issue(key) - return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), krepo, token, prepo + return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), token } func TestIssue(t *testing.T) { - svc, krepo, accessToken, _ := newService() + svc, accessToken := newService() + + n := jwt.New([]byte(secret)) + apikey := auth.Key{ + IssuedAt: time.Now(), + ExpiresAt: time.Now().Add(refreshDuration), + Subject: id, + Type: auth.APIKey, + User: email, + Domain: groupName, + } + apiToken, err := n.Issue(apikey) + assert.Nil(t, err, fmt.Sprintf("Issuing API key expected to succeed: %s", err)) + + refreshkey := auth.Key{ + IssuedAt: time.Now(), + ExpiresAt: time.Now().Add(refreshDuration), + Subject: id, + Type: auth.RefreshKey, + User: email, + Domain: groupName, + } + refreshToken, err := n.Issue(refreshkey) + assert.Nil(t, err, fmt.Sprintf("Issuing refresh key expected to succeed: %s", err)) cases := []struct { desc string key auth.Key token string err error + }{ + { + desc: "issue recovery key", + key: auth.Key{ + Type: auth.RecoveryKey, + IssuedAt: time.Now(), + }, + token: "", + err: nil, + }, + } + + for _, tc := range cases { + _, err := svc.Issue(context.Background(), tc.token, tc.key) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + } + + cases2 := []struct { + desc string + key auth.Key + saveResponse auth.Key + retrieveByIDResponse auth.Domain + token string + saveErr error + checkPolicyRequest auth.PolicyReq + checkPolicyRequest1 auth.PolicyReq + checkPolicyErr error + checkPolicyErr1 error + retreiveByIDErr error + err error }{ { desc: "issue login key", @@ -75,9 +151,172 @@ func TestIssue(t *testing.T) { Type: auth.AccessKey, IssuedAt: time.Now(), }, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + token: accessToken, + err: nil, + }, + { + desc: "issue login key with domain", + key: auth.Key{ + Type: auth.AccessKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, token: accessToken, err: nil, }, + { + desc: "issue login key with failed check on platform admin", + key: auth.Key{ + Type: auth.AccessKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + token: accessToken, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyErr: errors.ErrNotFound, + retrieveByIDResponse: auth.Domain{}, + retreiveByIDErr: repoerr.ErrNotFound, + err: errors.ErrNotFound, + }, + { + desc: "issue login key with failed check on platform admin with enabled status", + key: auth.Key{ + Type: auth.AccessKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + token: accessToken, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyRequest1: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: groupName, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.MembershipPermission, + }, + checkPolicyErr: svcerr.ErrAuthorization, + retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, + err: nil, + }, + { + desc: "issue login key with membership permission", + key: auth.Key{ + Type: auth.AccessKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + token: accessToken, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyRequest1: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: groupName, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.MembershipPermission, + }, + checkPolicyErr: svcerr.ErrAuthorization, + checkPolicyErr1: nil, + retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, + err: nil, + }, + { + desc: "issue login key with membership permission with failed to authorize", + key: auth.Key{ + Type: auth.AccessKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + token: accessToken, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyRequest1: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: groupName, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.MembershipPermission, + }, + checkPolicyErr: svcerr.ErrAuthorization, + checkPolicyErr1: svcerr.ErrAuthorization, + retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, + err: svcerr.ErrAuthorization, + }, + } + for _, tc := range cases2 { + repoCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) + repoCall1 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest).Return(tc.checkPolicyErr) + repoCall2 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest1).Return(tc.checkPolicyErr1) + repoCall3 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveByIDResponse, tc.retreiveByIDErr) + _, err := svc.Issue(context.Background(), tc.token, tc.key) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + } + + cases3 := []struct { + desc string + key auth.Key + token string + saveErr error + err error + }{ { desc: "issue API key", key: auth.Key{ @@ -97,26 +336,149 @@ func TestIssue(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "issue recovery key", + desc: " issue API key with invalid key request", key: auth.Key{ - Type: auth.RecoveryKey, + Type: auth.APIKey, + IssuedAt: time.Now(), + }, + token: apiToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "issue API key with failed to save", + key: auth.Key{ + Type: auth.APIKey, + IssuedAt: time.Now(), + }, + token: accessToken, + saveErr: errors.ErrNotFound, + err: errors.ErrNotFound, + }, + } + for _, tc := range cases3 { + repoCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) + _, err := svc.Issue(context.Background(), tc.token, tc.key) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } + + cases4 := []struct { + desc string + key auth.Key + token string + checkPolicyRequest auth.PolicyReq + checkPolicyErr error + retrieveByIDErr error + err error + }{ + { + desc: "issue refresh key", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + }, + checkPolicyRequest: auth.PolicyReq{ + Subject: email, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + token: refreshToken, + err: nil, + }, + { + desc: "issue refresh token with invalid policy", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + }, + checkPolicyRequest: auth.PolicyReq{ + Subject: email, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + token: refreshToken, + checkPolicyErr: svcerr.ErrAuthorization, + retrieveByIDErr: errors.ErrNotFound, + err: svcerr.ErrAuthorization, + }, + { + desc: "issue refresh key with invalid token", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + }, + token: accessToken, + err: errIssueUser, + }, + { + desc: "issue refresh key with empty token", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + }, + token: "", + err: errRetrieve, + }, + { + desc: "issue invitation key", + key: auth.Key{ + Type: auth.InvitationKey, IssuedAt: time.Now(), }, + checkPolicyRequest: auth.PolicyReq{ + Subject: email, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, token: "", err: nil, }, + { + desc: "issue invitation key with invalid policy", + key: auth.Key{ + Type: auth.InvitationKey, + IssuedAt: time.Now(), + Domain: groupName, + }, + checkPolicyRequest: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + token: refreshToken, + checkPolicyErr: svcerr.ErrAuthorization, + retrieveByIDErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, } - - for _, tc := range cases { - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.err) + for _, tc := range cases4 { + repoCall := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest).Return(tc.checkPolicyErr) + repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retrieveByIDErr) _, err := svc.Issue(context.Background(), tc.token, tc.key) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repocall.Unset() + repoCall.Unset() + repoCall1.Unset() } } func TestRevoke(t *testing.T) { - svc, krepo, _, _ := newService() + svc, _ := newService() repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, errIssueUser) secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) repocall.Unset() @@ -138,23 +500,26 @@ func TestRevoke(t *testing.T) { err error }{ { - desc: "revoke login key", - // id: newKey.ID, + desc: "revoke login key", token: secret.AccessToken, err: nil, }, { - desc: "revoke non-existing login key", - // id: newKey.ID, + desc: "revoke non-existing login key", token: secret.AccessToken, err: nil, }, { - desc: "revoke with empty login key", - // id: newKey.ID, + desc: "revoke with empty login key", token: "", err: svcerr.ErrAuthentication, }, + { + desc: "revoke login key with failed to remove", + id: "invalidID", + token: secret.AccessToken, + err: svcerr.ErrNotFound, + }, } for _, tc := range cases { @@ -166,7 +531,7 @@ func TestRevoke(t *testing.T) { } func TestRetrieve(t *testing.T) { - svc, krepo, _, _ := newService() + svc, _ := newService() repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) @@ -200,8 +565,7 @@ func TestRetrieve(t *testing.T) { err error }{ { - desc: "retrieve login key", - // id: apiKey.ID, + desc: "retrieve login key", token: userToken.AccessToken, err: nil, }, @@ -212,20 +576,17 @@ func TestRetrieve(t *testing.T) { err: svcerr.ErrNotFound, }, { - desc: "retrieve with wrong login key", - // id: apiKey.ID, + desc: "retrieve with wrong login key", token: "wrong", err: svcerr.ErrAuthentication, }, { - desc: "retrieve with API token", - // id: apiKey.ID, + desc: "retrieve with API token", token: apiToken.AccessToken, err: svcerr.ErrAuthentication, }, { - desc: "retrieve with reset token", - // id: apiKey.ID, + desc: "retrieve with reset token", token: resetToken.AccessToken, err: svcerr.ErrAuthentication, }, @@ -240,7 +601,7 @@ func TestRetrieve(t *testing.T) { } func TestIdentify(t *testing.T) { - svc, krepo, _, prepo := newService() + svc, _ := newService() repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) repocall1 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) @@ -260,11 +621,23 @@ func TestIdentify(t *testing.T) { repocall3.Unset() repocall4 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - exp1 := time.Now().Add(-2 * time.Second) - expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1}) + exp0 := time.Now().UTC().Add(-10 * time.Second).Round(time.Second) + exp1 := time.Now().UTC().Add(-1 * time.Minute).Round(time.Second) + expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: exp0, ExpiresAt: exp1}) assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) repocall4.Unset() + te := jwt.New([]byte(secret)) + key := auth.Key{ + IssuedAt: time.Now(), + ExpiresAt: time.Now().Add(refreshDuration), + Subject: id, + Type: 7, + User: email, + Domain: groupName, + } + invalidTokenType, _ := te.Issue(key) + cases := []struct { desc string key string @@ -293,7 +666,13 @@ func TestIdentify(t *testing.T) { desc: "identify expired API key", key: expSecret.AccessToken, idt: "", - err: ErrExpiry, + err: auth.ErrKeyExpired, + }, + { + desc: "identify API key with failed to retrieve", + key: apiSecret.AccessToken, + idt: "", + err: svcerr.ErrAuthentication, }, { desc: "identify invalid key", @@ -301,78 +680,2023 @@ func TestIdentify(t *testing.T) { idt: "", err: errors.ErrAuthentication, }, + { + desc: "identify invalid key type", + key: invalidTokenType, + idt: "", + err: svcerr.ErrAuthentication, + }, } for _, tc := range cases { repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) + repocall1 := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) idt, err := svc.Identify(context.Background(), tc.key) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.idt, idt.Subject, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.idt, idt)) repocall.Unset() + repocall1.Unset() } } func TestAuthorize(t *testing.T) { - svc, _, _, prepo := newService() + svc, accessToken := newService() - repocall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - pr := auth.PolicyReq{Object: authoritiesObj, Relation: memberRelation, Subject: id} - err := svc.Authorize(context.Background(), pr) - require.Nil(t, err, fmt.Sprintf("authorizing initial %v policy expected to succeed: %s", pr, err)) + repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) + repocall1 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) + loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: id, IssuedAt: time.Now(), Domain: groupName}) + assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) repocall.Unset() -} + repocall1.Unset() + saveCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) + exp1 := time.Now().Add(-2 * time.Second) + expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1}) + assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) + saveCall.Unset() -func TestAddPolicy(t *testing.T) { - svc, _, _, prepo := newService() + repocall2 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) + repocall3 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) + emptySubject, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: "", IssuedAt: time.Now(), Domain: groupName}) + assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) + repocall2.Unset() + repocall3.Unset() - repocall := prepo.On("AddPolicies", mock.Anything, mock.Anything).Return(nil) - prs := []auth.PolicyReq{{Object: "obj", ObjectType: "object", Relation: "rel", Subject: "sub", SubjectType: "subject"}} - err := svc.AddPolicies(context.Background(), prs) - require.Nil(t, err, fmt.Sprintf("adding %v policies expected to succeed: %v", prs, err)) - repocall.Unset() - for _, pr := range prs { - repocall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - err = svc.Authorize(context.Background(), pr) - require.Nil(t, err, fmt.Sprintf("checking shared %v policy expected to be succeed: %#v", pr, err)) - repocall.Unset() + te := jwt.New([]byte(secret)) + key := auth.Key{ + IssuedAt: time.Now(), + ExpiresAt: time.Now().Add(refreshDuration), + Subject: id, + Type: auth.AccessKey, + User: email, } -} + emptyDomain, _ := te.Issue(key) -func TestDeletePolicies(t *testing.T) { - svc, _, _, prepo := newService() + cases := []struct { + desc string + policyReq auth.PolicyReq + retrieveDomainRes auth.Domain + checkPolicyReq auth.PolicyReq + checkPolicyReq1 auth.PolicyReq + checkPolicyReq2 auth.PolicyReq + checkPolicyErr error + checkPolicyErr1 error + checkPolicyErr2 error + err error + }{ + { + desc: "authorize token successfully", + policyReq: auth.PolicyReq{ + Subject: accessToken, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "authorize token for group type with empty domain", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: "", + ObjectType: auth.GroupType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: "", + ObjectType: auth.GroupType, + Permission: auth.AdminPermission, + }, + err: errors.ErrDomainAuthorization, + }, + { + desc: "authorize token with disabled domain", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Permission: auth.AdminPermission, + Object: validID, + ObjectType: auth.DomainType, + }, - repocall := prepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(nil) - prs := []auth.PolicyReq{{Object: "obj", ObjectType: "object", Relation: "rel", Subject: "sub", SubjectType: "subject"}} - err := svc.DeletePolicies(context.Background(), prs) - require.Nil(t, err, fmt.Sprintf("adding %v policies expected to succeed: %v", prs, err)) - repocall.Unset() -} + retrieveDomainRes: auth.Domain{ + ID: validID, + Name: groupName, + Status: auth.DisabledStatus, + }, + err: nil, + }, + { + desc: "authorize token with disabled domain with failed to authorize", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Permission: auth.AdminPermission, + Object: validID, + ObjectType: auth.DomainType, + }, -func TestListPolicies(t *testing.T) { - svc, _, _, prepo := newService() + retrieveDomainRes: auth.Domain{ + ID: validID, + Name: groupName, + Status: auth.DisabledStatus, + }, + checkPolicyErr1: errors.ErrDomainAuthorization, + err: errors.ErrDomainAuthorization, + }, + { + desc: "authorize token with frozen domain", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Permission: auth.AdminPermission, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + }, - pageLen := 15 + retrieveDomainRes: auth.Domain{ + ID: validID, + Name: groupName, + Status: auth.FreezeStatus, + }, + err: nil, + }, + { + desc: "authorize token with frozen domain with failed to authorize", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Permission: auth.AdminPermission, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + }, - // Add arbitrary policies to the user. - var prs []auth.PolicyReq - for i := 0; i < pageLen; i++ { - prs = append(prs, auth.PolicyReq{ - Subject: id, - SubjectType: auth.UserType, - Relation: auth.ViewerRelation, - Object: fmt.Sprintf("thing-%d", i), - ObjectType: auth.ThingType, - }) - } - repocall := prepo.On("AddPolicies", mock.Anything, mock.Anything).Return(nil) - err := svc.AddPolicies(context.Background(), prs) - assert.Nil(t, err, fmt.Sprintf("adding policies expected to succeed: %s", err)) - repocall.Unset() + retrieveDomainRes: auth.Domain{ + ID: validID, + Name: groupName, + Status: auth.FreezeStatus, + }, + checkPolicyErr1: errors.ErrDomainAuthorization, + err: errors.ErrDomainAuthorization, + }, + { + desc: "authorize token with domain with invalid status", + policyReq: auth.PolicyReq{ + Subject: emptyDomain, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Permission: auth.AdminPermission, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + }, - expectedPolicies := make([]auth.PolicyRes, pageLen) - repocall2 := prepo.On("RetrieveObjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedPolicies, mock.Anything, nil) - page, err := svc.ListObjects(context.Background(), auth.PolicyReq{Subject: id, SubjectType: auth.UserType, ObjectType: auth.ThingType, Permission: auth.ViewPermission}, "", 100) - assert.Nil(t, err, fmt.Sprintf("listing policies expected to succeed: %s", err)) - assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err)) - repocall2.Unset() + retrieveDomainRes: auth.Domain{ + ID: validID, + Name: groupName, + Status: auth.AllStatus, + }, + err: errors.ErrDomainAuthorization, + }, + + { + desc: "authorize an expired token", + policyReq: auth.PolicyReq{ + Subject: expSecret.AccessToken, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "authorize a token with an empty subject", + policyReq: auth.PolicyReq{ + Subject: emptySubject.AccessToken, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + SubjectType: auth.UserType, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "authorize a token with an empty secret and invalid type", + policyReq: auth.PolicyReq{ + Subject: emptySubject.AccessToken, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + SubjectType: auth.UserType, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformKind, + Permission: auth.AdminPermission, + }, + err: errors.ErrDomainAuthorization, + }, + { + desc: "authorize a user key successfully", + policyReq: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "authorize token with empty subject and domain object type", + policyReq: auth.PolicyReq{ + Subject: emptySubject.AccessToken, + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: auth.MagistralaObject, + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyReq: auth.PolicyReq{ + SubjectType: auth.UserType, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: errors.ErrDomainAuthorization, + }, + } + for _, tc := range cases { + repoCall := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) + repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveDomainRes, nil) + repoCall2 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq1).Return(tc.checkPolicyErr1) + repoCall3 := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := svc.Authorize(context.Background(), tc.policyReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + } + + cases2 := []struct { + desc string + policyReq auth.PolicyReq + err error + }{ + { + desc: "authorize token with invalid platform validation", + policyReq: auth.PolicyReq{ + Subject: "", + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: validID, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: errPlatform, + }, + } + for _, tc := range cases2 { + err := svc.Authorize(context.Background(), tc.policyReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestAddPolicy(t *testing.T) { + svc, _ := newService() + + cases := []struct { + desc string + pr auth.PolicyReq + err error + }{ + { + desc: "add policy successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "add policy with invalid object", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: inValid, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrInvalidPolicy, + }, + } + + for _, tc := range cases { + repocall := prepo.On("AddPolicy", mock.Anything, mock.Anything).Return(tc.err) + err := svc.AddPolicy(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repocall.Unset() + } +} + +func TestAddPolicies(t *testing.T) { + svc, _ := newService() + + cases := []struct { + desc string + pr []auth.PolicyReq + err error + }{ + { + desc: "add policy successfully", + pr: []auth.PolicyReq{ + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + }, + err: nil, + }, + { + desc: "add policy with invalid object", + pr: []auth.PolicyReq{ + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: inValid, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + }, + err: svcerr.ErrInvalidPolicy, + }, + } + + for _, tc := range cases { + repocall := prepo.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.err) + err := svc.AddPolicies(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repocall.Unset() + } +} + +func TestDeletePolicy(t *testing.T) { + svc, _ := newService() + + cases := []struct { + desc string + pr auth.PolicyReq + err error + }{ + { + desc: "delete policy successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "delete policy with invalid object", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: inValid, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrInvalidPolicy, + }, + } + + for _, tc := range cases { + repocall := prepo.On("DeletePolicy", mock.Anything, mock.Anything).Return(tc.err) + err := svc.DeletePolicy(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repocall.Unset() + } +} + +func TestDeletePolicies(t *testing.T) { + svc, _ := newService() + + cases := []struct { + desc string + pr []auth.PolicyReq + err error + }{ + { + desc: "delete policy successfully", + pr: []auth.PolicyReq{ + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + }, + err: nil, + }, + { + desc: "delete policy with invalid object", + pr: []auth.PolicyReq{ + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: inValid, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + { + Subject: id, + SubjectType: auth.UserType, + SubjectKind: auth.UsersKind, + Object: auth.MagistralaObject, + ObjectType: auth.PlatformType, + Permission: auth.AdminPermission, + }, + }, + err: svcerr.ErrInvalidPolicy, + }, + } + + for _, tc := range cases { + repocall := prepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.err) + err := svc.DeletePolicies(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repocall.Unset() + } +} + +func TestListObjects(t *testing.T) { + svc, accessToken := newService() + + pageLen := 15 + expectedPolicies := make([]auth.PolicyRes, pageLen) + + cases := []struct { + desc string + pr auth.PolicyReq + nextPageToken string + limit int32 + err error + }{ + { + desc: "list objects successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: 10, + err: nil, + }, + { + desc: "list objects with invalid request", + pr: auth.PolicyReq{ + Subject: inValid, + SubjectType: inValid, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: inValid, + }, + nextPageToken: accessToken, + limit: 10, + err: svcerr.ErrInvalidPolicy, + }, + { + desc: "list objects with limit less than zero", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: -1, + err: nil, + }, + } + for _, tc := range cases { + repocall2 := prepo.On("RetrieveObjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedPolicies, mock.Anything, tc.err) + page, err := svc.ListObjects(context.Background(), tc.pr, tc.nextPageToken, tc.limit) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("listing policies expected to succeed: %s", err)) + if err == nil { + assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err)) + } + repocall2.Unset() + } +} + +func TestListAllObjects(t *testing.T) { + svc, accessToken := newService() + + pageLen := 15 + expectedPolicies := make([]auth.PolicyRes, pageLen) + + cases := []struct { + desc string + pr auth.PolicyReq + nextPageToken string + limit int32 + err error + }{ + { + desc: "list all objects successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: 10, + err: nil, + }, + { + desc: "list all objects with invalid request", + pr: auth.PolicyReq{ + Subject: inValid, + SubjectType: inValid, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: inValid, + }, + nextPageToken: accessToken, + limit: 10, + err: svcerr.ErrInvalidPolicy, + }, + } + for _, tc := range cases { + repocall2 := prepo.On("RetrieveAllObjects", mock.Anything, mock.Anything).Return(expectedPolicies, tc.err) + page, err := svc.ListAllObjects(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("listing policies expected to succeed: %s", err)) + if err == nil { + assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err)) + } + repocall2.Unset() + } +} + +func TestCountObjects(t *testing.T) { + svc, _ := newService() + + pageLen := 15 + + repocall2 := prepo.On("RetrieveAllObjectsCount", mock.Anything, mock.Anything, mock.Anything).Return(pageLen, nil) + count, err := svc.CountObjects(context.Background(), auth.PolicyReq{Subject: id, SubjectType: auth.UserType, ObjectType: auth.ThingType, Permission: auth.ViewPermission}) + assert.Nil(t, err, fmt.Sprintf("counting policies expected to succeed: %s", err)) + assert.Equal(t, pageLen, count, fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, count, err)) + repocall2.Unset() +} + +func TestListSubjects(t *testing.T) { + svc, accessToken := newService() + + pageLen := 15 + expectedPolicies := make([]auth.PolicyRes, pageLen) + + cases := []struct { + desc string + pr auth.PolicyReq + nextPageToken string + limit int32 + err error + }{ + { + desc: "list subjects successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: 10, + err: nil, + }, + { + desc: "list subjects with invalid request", + pr: auth.PolicyReq{ + Subject: inValid, + SubjectType: inValid, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: inValid, + }, + nextPageToken: accessToken, + limit: 10, + err: svcerr.ErrInvalidPolicy, + }, + { + desc: "list subjects with limit less than zero", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: -1, + err: nil, + }, + } + for _, tc := range cases { + repocall2 := prepo.On("RetrieveSubjects", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedPolicies, mock.Anything, tc.err) + page, err := svc.ListSubjects(context.Background(), tc.pr, tc.nextPageToken, tc.limit) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("listing policies expected to succeed: %s", err)) + if err == nil { + assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err)) + } + repocall2.Unset() + } +} + +func TestListAllSubjects(t *testing.T) { + svc, accessToken := newService() + + pageLen := 15 + expectedPolicies := make([]auth.PolicyRes, pageLen) + + cases := []struct { + desc string + pr auth.PolicyReq + nextPageToken string + limit int32 + err error + }{ + { + desc: "list all subjects successfully", + pr: auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + }, + nextPageToken: accessToken, + limit: 10, + err: nil, + }, + { + desc: "list all subjects with invalid request", + pr: auth.PolicyReq{ + Subject: inValid, + SubjectType: inValid, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: inValid, + }, + nextPageToken: accessToken, + limit: 10, + err: svcerr.ErrInvalidPolicy, + }, + } + for _, tc := range cases { + repocall2 := prepo.On("RetrieveAllSubjects", mock.Anything, mock.Anything).Return(expectedPolicies, tc.err) + page, err := svc.ListAllSubjects(context.Background(), tc.pr) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("listing policies expected to succeed: %s", err)) + if err == nil { + assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err)) + } + repocall2.Unset() + } +} + +func TestCountSubjects(t *testing.T) { + svc, _ := newService() + pageLen := 15 + + repocall2 := prepo.On("RetrieveAllSubjectsCount", mock.Anything, mock.Anything, mock.Anything).Return(pageLen, nil) + count, err := svc.CountSubjects(context.Background(), auth.PolicyReq{Object: id, ObjectType: auth.ThingType, Permission: auth.ViewPermission}) + assert.Nil(t, err, fmt.Sprintf("counting policies expected to succeed: %s", err)) + assert.Equal(t, pageLen, count, fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, count, err)) + repocall2.Unset() +} + +func TestListPermissions(t *testing.T) { + svc, _ := newService() + + pr := auth.PolicyReq{ + Subject: id, + SubjectType: auth.UserType, + Relation: auth.ViewerRelation, + ObjectType: auth.ThingType, + ObjectKind: auth.ThingsKind, + Object: "", + } + filterPermisions := []string{auth.ViewPermission, auth.AdminPermission} + + repoCall1 := prepo.On("RetrievePermissions", mock.Anything, pr, filterPermisions).Return(auth.Permissions{}, nil) + _, err := svc.ListPermissions(context.Background(), pr, filterPermisions) + assert.Nil(t, err, fmt.Sprintf("listing policies expected to succeed: %s", err)) + repoCall1.Unset() +} + +func TestSwitchToPermission(t *testing.T) { + cases := []struct { + desc string + relation string + result string + }{ + { + desc: "switch to admin permission", + relation: auth.AdministratorRelation, + result: auth.AdminPermission, + }, + { + desc: "switch to editor permission", + relation: auth.EditorRelation, + result: auth.EditPermission, + }, + { + desc: "switch to viewer permission", + relation: auth.ViewerRelation, + result: auth.ViewPermission, + }, + { + desc: "switch to member permission", + relation: auth.MemberRelation, + result: auth.MembershipPermission, + }, + { + desc: "switch to group permission", + relation: auth.GroupRelation, + result: auth.GroupRelation, + }, + } + for _, tc := range cases { + result := auth.SwitchToPermission(tc.relation) + assert.Equal(t, tc.result, result, fmt.Sprintf("switching to permission expected to succeed: %s", result)) + } +} + +func TestCreateDomain(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + d auth.Domain + token string + userID string + addPolicyErr error + savePolicyErr error + saveDomainErr error + deleteDomainErr error + deletePoliciesErr error + err error + }{ + { + desc: "create domain successfully", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + err: nil, + }, + { + desc: "create domain with invalid token", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "create domain with invalid status", + d: auth.Domain{ + Status: auth.AllStatus, + }, + token: accessToken, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "create domain with failed policy request", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + addPolicyErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with failed save policy request", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + savePolicyErr: errors.ErrMalformedEntity, + err: errCreateDomainPolicy, + }, + { + desc: "create domain with failed save domain request", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + saveDomainErr: errors.ErrMalformedEntity, + err: svcerr.ErrCreateEntity, + }, + { + desc: "create domain with rollback error", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + savePolicyErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with rollback error and failed to delete policies", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + savePolicyErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with failed to create and failed rollback", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + saveDomainErr: errors.ErrMalformedEntity, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errRollbackPolicy, + }, + { + desc: "create domain with failed to create and failed rollback", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + saveDomainErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall := prepo.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPolicyErr) + repoCall1 := drepo.On("SavePolicies", mock.Anything, mock.Anything).Return(tc.savePolicyErr) + repoCall2 := prepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) + repoCall3 := drepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deleteDomainErr) + repoCall4 := drepo.On("Save", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.saveDomainErr) + _, err := svc.CreateDomain(context.Background(), tc.token, tc.d) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + } +} + +func TestRetrieveDomain(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + domainRepoErr error + domainRepoErr1 error + checkPolicyErr error + err error + }{ + { + desc: "retrieve domain successfully", + token: accessToken, + domainID: validID, + err: nil, + }, + { + desc: "retrieve domain with invalid token", + token: inValidToken, + domainID: validID, + err: svcerr.ErrAuthentication, + }, + { + desc: "retrieve domain with empty domainID", + token: accessToken, + domainID: "", + err: nil, + }, + { + desc: "retrieve non-existing domain", + token: accessToken, + domainID: inValid, + domainRepoErr: errors.ErrNotFound, + err: svcerr.ErrAuthorization, + }, + { + desc: "retrieve domain with failed to retrieve by id", + token: accessToken, + domainID: validID, + domainRepoErr1: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("RetrieveByID", mock.Anything, groupName).Return(auth.Domain{}, tc.domainRepoErr) + repoCall1 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + repoCall2 := drepo.On("RetrieveByID", mock.Anything, tc.domainID).Return(auth.Domain{}, tc.domainRepoErr1) + _, err := svc.RetrieveDomain(context.Background(), tc.token, tc.domainID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestRetrieveDomainPermissions(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + retreivePermissionsErr error + retreiveByIDErr error + checkPolicyErr error + err error + }{ + { + desc: "retrieve domain permissions successfully", + token: accessToken, + domainID: validID, + err: nil, + }, + { + desc: "retrieve domain permissions with invalid token", + token: inValidToken, + domainID: validID, + err: svcerr.ErrAuthentication, + }, + { + desc: "retrieve domain permissions with empty domainID", + token: accessToken, + domainID: "", + checkPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "retrieve domain permissions with failed to retrieve permissions", + token: accessToken, + domainID: validID, + retreivePermissionsErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "retrieve domain permissions with failed to retrieve by id", + token: accessToken, + domainID: validID, + retreiveByIDErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := prepo.On("RetrievePermissions", mock.Anything, mock.Anything, mock.Anything).Return(auth.Permissions{}, tc.retreivePermissionsErr) + repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreiveByIDErr) + repoCall2 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + _, err := svc.RetrieveDomainPermissions(context.Background(), tc.token, tc.domainID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestUpdateDomain(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + domReq auth.DomainReq + checkPolicyErr error + retrieveByIDErr error + updateErr error + err error + }{ + { + desc: "update domain successfully", + token: accessToken, + domainID: validID, + domReq: auth.DomainReq{ + Name: &valid, + Alias: &valid, + }, + err: nil, + }, + { + desc: "update domain with invalid token", + token: inValidToken, + domainID: validID, + domReq: auth.DomainReq{ + Name: &valid, + Alias: &valid, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "update domain with empty domainID", + token: accessToken, + domainID: "", + domReq: auth.DomainReq{ + Name: &valid, + Alias: &valid, + }, + checkPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "update domain with failed to retrieve by id", + token: accessToken, + domainID: validID, + domReq: auth.DomainReq{ + Name: &valid, + Alias: &valid, + }, + retrieveByIDErr: errors.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "update domain with failed to update", + token: accessToken, + domainID: validID, + domReq: auth.DomainReq{ + Name: &valid, + Alias: &valid, + }, + updateErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retrieveByIDErr) + repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) + _, err := svc.UpdateDomain(context.Background(), tc.token, tc.domainID, tc.domReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestChangeDomainStatus(t *testing.T) { + svc, accessToken := newService() + + disabledStatus := auth.DisabledStatus + + cases := []struct { + desc string + token string + domainID string + domainReq auth.DomainReq + retreieveByIDErr error + checkPolicyErr error + updateErr error + err error + }{ + { + desc: "change domain status successfully", + token: accessToken, + domainID: validID, + domainReq: auth.DomainReq{ + Status: &disabledStatus, + }, + err: nil, + }, + { + desc: "change domain status with invalid token", + token: inValidToken, + domainID: validID, + domainReq: auth.DomainReq{ + Status: &disabledStatus, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "change domain status with empty domainID", + token: accessToken, + domainID: "", + domainReq: auth.DomainReq{ + Status: &disabledStatus, + }, + retreieveByIDErr: errors.ErrNotFound, + err: svcerr.ErrAuthorization, + }, + { + desc: "change domain status with unauthorized domain ID", + token: accessToken, + domainID: validID, + domainReq: auth.DomainReq{ + Status: &disabledStatus, + }, + checkPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "change domain status with repository error on update", + token: accessToken, + domainID: validID, + domainReq: auth.DomainReq{ + Status: &disabledStatus, + }, + updateErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreieveByIDErr) + repoCall1 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) + _, err := svc.ChangeDomainStatus(context.Background(), tc.token, tc.domainID, tc.domainReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestListDomains(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + authReq auth.Page + listDomainsRes auth.DomainsPage + retreiveByIDErr error + checkPolicyErr error + listDomainErr error + err error + }{ + { + desc: "list domains successfully", + token: accessToken, + domainID: validID, + authReq: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + Status: auth.EnabledStatus, + }, + listDomainsRes: auth.DomainsPage{ + Domains: []auth.Domain{domain}, + }, + err: nil, + }, + { + desc: "list domains with invalid token", + token: inValidToken, + domainID: validID, + authReq: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + Status: auth.EnabledStatus, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "list domains with repository error on list domains", + token: accessToken, + domainID: validID, + authReq: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + Status: auth.EnabledStatus, + }, + listDomainErr: errors.ErrMalformedEntity, + err: svcerr.ErrViewEntity, + }, + } + + for _, tc := range cases { + repoCall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(tc.listDomainsRes, tc.listDomainErr) + _, err := svc.ListDomains(context.Background(), tc.token, auth.Page{}) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestAssignUsers(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + userIDs []string + relation string + checkPolicyReq auth.PolicyReq + checkPolicyReq1 auth.PolicyReq + checkPolicyReq2 auth.PolicyReq + checkpolicyErr error + checkPolicyErr1 error + checkPolicyErr2 error + addPoliciesErr error + savePoliciesErr error + deletePoliciesErr error + err error + }{ + { + desc: "assign users successfully", + token: accessToken, + domainID: validID, + userIDs: []string{validID}, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: validID, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + + err: nil, + }, + { + desc: "assign users with invalid token", + token: inValidToken, + domainID: validID, + userIDs: []string{validID}, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: validID, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "assign users with invalid domainID", + token: accessToken, + domainID: inValid, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: inValid, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: inValid, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyErr1: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "assign users with invalid userIDs", + token: accessToken, + userIDs: []string{inValid}, + domainID: validID, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: inValid, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + checkPolicyErr2: svcerr.ErrMalformedEntity, + err: svcerr.ErrMalformedEntity, + }, + { + desc: "assign users with failed to add policies to agent", + token: accessToken, + domainID: validID, + userIDs: []string{validID}, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: validID, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + addPoliciesErr: svcerr.ErrAuthorization, + err: errAddPolicies, + }, + { + desc: "assign users with failed to save policies to domain", + token: accessToken, + domainID: validID, + userIDs: []string{validID}, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: validID, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + savePoliciesErr: repoerr.ErrCreateEntity, + err: errAddPolicies, + }, + { + desc: "assign users with failed to save policies to domain and failed to delete", + token: accessToken, + domainID: validID, + userIDs: []string{validID}, + relation: auth.ViewerRelation, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.ViewPermission, + }, + checkPolicyReq2: auth.PolicyReq{ + Subject: validID, + SubjectType: auth.UserType, + SubjectKind: "", + Object: auth.MagistralaObject, + ObjectKind: "", + ObjectType: auth.PlatformType, + Permission: auth.MembershipPermission, + }, + savePoliciesErr: repoerr.ErrCreateEntity, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errAddPolicies, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("RetrieveByID", mock.Anything, groupName).Return(auth.Domain{}, nil) + repoCall1 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkpolicyErr) + repoCall2 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq1).Return(tc.checkPolicyErr1) + repoCall3 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq2).Return(tc.checkPolicyErr2) + repoCall4 := prepo.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesErr) + repoCall5 := drepo.On("SavePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.savePoliciesErr) + repoCall6 := prepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) + err := svc.AssignUsers(context.Background(), tc.token, tc.domainID, tc.userIDs, tc.relation) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + repoCall5.Unset() + repoCall6.Unset() + } +} + +func TestUnassignUsers(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + domainID string + checkPolicyReq auth.PolicyReq + checkPolicyReq1 auth.PolicyReq + checkPolicyErr error + checkPolicyErr1 error + deletePoliciesErr error + deletePoliciesErr1 error + err error + }{ + { + desc: "unassign users successfully", + token: accessToken, + domainID: validID, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "unassign users with invalid token", + token: inValidToken, + domainID: validID, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "unassign users with invalid domainID", + token: accessToken, + domainID: inValid, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: inValid, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: inValid, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + checkPolicyErr1: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "unassign users with failed to delete policies from agant", + token: accessToken, + domainID: validID, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "unassign users with failed to delete policies from domain", + token: accessToken, + domainID: validID, + checkPolicyReq: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.SharePermission, + }, + checkPolicyReq1: auth.PolicyReq{ + Domain: groupName, + Subject: "testID", + SubjectType: auth.UserType, + SubjectKind: auth.TokenKind, + Object: validID, + ObjectKind: "", + ObjectType: auth.DomainType, + Permission: auth.AdminPermission, + }, + deletePoliciesErr1: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, nil) + repoCall1 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) + repoCall2 := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq1).Return(tc.checkPolicyErr1) + repoCall3 := prepo.On("DeletePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) + repoCall4 := drepo.On("DeletePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.deletePoliciesErr1) + err := svc.UnassignUsers(context.Background(), tc.token, tc.domainID, []string{" ", " "}, auth.AdministratorRelation) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + } +} + +func TestListUsersDomains(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + token string + userID string + page auth.Page + retreiveByIDErr error + checkPolicyErr error + listDomainErr error + err error + }{ + { + desc: "list users domains successfully", + token: accessToken, + userID: validID, + page: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "list users domains successfully was admin", + token: accessToken, + userID: email, + page: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + }, + err: nil, + }, + { + desc: "list users domains with invalid token", + token: inValidToken, + userID: validID, + page: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "list users domains with invalid domainID", + token: accessToken, + userID: inValid, + page: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + }, + checkPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + }, + { + desc: "list users domains with repository error on list domains", + token: accessToken, + userID: validID, + page: auth.Page{ + Offset: 0, + Limit: 10, + Permission: auth.AdminPermission, + }, + listDomainErr: errors.ErrNotFound, + err: svcerr.ErrViewEntity, + }, + } + + for _, tc := range cases { + repoCall := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(auth.DomainsPage{}, tc.listDomainErr) + _, err := svc.ListUserDomains(context.Background(), tc.token, tc.userID, tc.page) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestEncodeDomainUserID(t *testing.T) { + cases := []struct { + desc string + domainID string + userID string + response string + }{ + { + desc: "encode domain user id successfully", + domainID: validID, + userID: validID, + response: validID + "_" + validID, + }, + { + desc: "encode domain user id with empty userID", + domainID: validID, + userID: "", + response: "", + }, + { + desc: "encode domain user id with empty domain ID", + domainID: "", + userID: validID, + response: "", + }, + { + desc: "encode domain user id with empty domain ID and userID", + domainID: "", + userID: "", + response: "", + }, + } + + for _, tc := range cases { + ar := auth.EncodeDomainUserID(tc.domainID, tc.userID) + assert.Equal(t, tc.response, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.response, ar)) + } +} + +func TestDecodeDomainUserID(t *testing.T) { + cases := []struct { + desc string + domainUserID string + respDomainID string + respUserID string + }{ + { + desc: "decode domain user id successfully", + domainUserID: validID + "_" + validID, + respDomainID: validID, + respUserID: validID, + }, + { + desc: "decode domain user id with empty domainUserID", + domainUserID: "", + respDomainID: "", + respUserID: "", + }, + { + desc: "decode domain user id with empty UserID", + domainUserID: validID, + respDomainID: validID, + respUserID: "", + }, + { + desc: "decode domain user id with invalid domainuserId", + domainUserID: validID + "_" + validID + "_" + validID + "_" + validID, + respDomainID: "", + respUserID: "", + }, + } + + for _, tc := range cases { + ar, er := auth.DecodeDomainUserID(tc.domainUserID) + assert.Equal(t, tc.respUserID, er, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respUserID, er)) + assert.Equal(t, tc.respDomainID, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respDomainID, ar)) + } } diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 2101f6c3da..0129494eb0 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -176,9 +176,9 @@ func dec(in []byte) ([]byte, error) { return in, nil } -func newService() (bootstrap.Service, *authmocks.Service, *sdkmocks.SDK) { +func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) return bootstrap.New(auth, things, sdk, encKey), auth, sdk @@ -720,7 +720,7 @@ func TestUpdateConnections(t *testing.T) { repoCall1 := sdk.On("Thing", mock.Anything, mock.Anything).Return(mgsdk.Thing{ID: c.ThingID, Credentials: mgsdk.Credentials{Secret: c.ThingKey}}, nil) repoCall2 := sdk.On("Channel", mock.Anything, mock.Anything).Return(mgsdk.Channel{}, nil) repoCall3 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) - repoCall4 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) + repoCall4 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Added: true}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPut, diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index d1feb8ce95..ea2abc0670 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -77,9 +77,9 @@ var ( } ) -func newService(t *testing.T, url string) (bootstrap.Service, *authmocks.Service, *sdkmocks.SDK) { +func newService(t *testing.T, url string) (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) svc := bootstrap.New(auth, things, sdk, encKey) diff --git a/bootstrap/service_test.go b/bootstrap/service_test.go index 84b330642c..fb6f65c010 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -56,9 +56,9 @@ var ( } ) -func newService() (bootstrap.Service, *authmocks.Service, *sdkmocks.SDK) { +func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) return bootstrap.New(auth, things, sdk, encKey), auth, sdk diff --git a/certs/service_test.go b/certs/service_test.go index 9efbd7d210..cb10740278 100644 --- a/certs/service_test.go +++ b/certs/service_test.go @@ -42,8 +42,8 @@ const ( instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" ) -func newService(t *testing.T) (certs.Service, *authmocks.Service, *sdkmocks.SDK) { - auth := new(authmocks.Service) +func newService(t *testing.T) (certs.Service, *authmocks.AuthClient, *sdkmocks.SDK) { + auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) repo := mocks.NewCertsRepository() diff --git a/cmd/users/main.go b/cmd/users/main.go index 1b53de1f10..938833ba24 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -297,7 +297,7 @@ func createAdminPolicy(ctx context.Context, clientID string, authClient magistra if err != nil { return err } - if !addPolicyRes.Authorized { + if !addPolicyRes.Added { return errors.ErrAuthorization } } diff --git a/consumers/notifiers/api/endpoint_test.go b/consumers/notifiers/api/endpoint_test.go index 7d536b563a..aab51f8d83 100644 --- a/consumers/notifiers/api/endpoint_test.go +++ b/consumers/notifiers/api/endpoint_test.go @@ -67,8 +67,8 @@ func (tr testRequest) make() (*http.Response, error) { return tr.client.Do(req) } -func newService() (notifiers.Service, *authmocks.Service) { - auth := new(authmocks.Service) +func newService() (notifiers.Service, *authmocks.AuthClient) { + auth := new(authmocks.AuthClient) repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) idp := uuid.NewMock() notif := mocks.NewNotifier() diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index 32f928f3c9..1cd8546e75 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -29,9 +29,9 @@ const ( validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) -func newService() (notifiers.Service, *authmocks.Service) { +func newService() (notifiers.Service, *authmocks.AuthClient) { repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) notifier := mocks.NewNotifier() idp := uuid.NewMock() from := "exampleFrom" diff --git a/http/api/endpoint_test.go b/http/api/endpoint_test.go index 90cfcd9752..325e4ba100 100644 --- a/http/api/endpoint_test.go +++ b/http/api/endpoint_test.go @@ -73,7 +73,7 @@ func (tr testRequest) make() (*http.Response, error) { } func TestPublish(t *testing.T) { - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) chanID := "1" ctSenmlJSON := "application/senml+json" ctSenmlCBOR := "application/senml+cbor" diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index 481482b5f4..c6a3ecf70f 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -47,7 +47,7 @@ var ( func TestCreateGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -88,7 +88,7 @@ func TestCreateGroup(t *testing.T) { Owner: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, }, { @@ -183,7 +183,7 @@ func TestCreateGroup(t *testing.T) { Parent: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, }, { @@ -210,7 +210,7 @@ func TestCreateGroup(t *testing.T) { Parent: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, err: errors.ErrAuthorization, }, @@ -299,7 +299,7 @@ func TestCreateGroup(t *testing.T) { func TestViewGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -367,7 +367,7 @@ func TestViewGroup(t *testing.T) { func TestViewGroupPerms(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -451,7 +451,7 @@ func TestViewGroupPerms(t *testing.T) { func TestUpdateGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -528,7 +528,7 @@ func TestUpdateGroup(t *testing.T) { func TestEnableGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -628,7 +628,7 @@ func TestEnableGroup(t *testing.T) { func TestDisableGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -728,7 +728,7 @@ func TestDisableGroup(t *testing.T) { func TestListMembers(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -864,7 +864,7 @@ func TestListMembers(t *testing.T) { func TestListGroups(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -1616,7 +1616,7 @@ func TestListGroups(t *testing.T) { func TestAssign(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -1656,7 +1656,7 @@ func TestAssign(t *testing.T) { Authorized: true, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, }, { @@ -1674,7 +1674,7 @@ func TestAssign(t *testing.T) { Authorized: true, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, }, { @@ -1699,7 +1699,7 @@ func TestAssign(t *testing.T) { }, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, repoParentGroupErr: nil, }, @@ -1718,7 +1718,7 @@ func TestAssign(t *testing.T) { Authorized: true, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, }, { @@ -1804,7 +1804,7 @@ func TestAssign(t *testing.T) { }, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: false, + Added: false, }, addPoliciesErr: errors.ErrAuthorization, err: errors.ErrAuthorization, @@ -1831,7 +1831,7 @@ func TestAssign(t *testing.T) { }, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, repoParentGroupErr: errors.ErrConflict, err: errors.ErrConflict, @@ -1858,7 +1858,7 @@ func TestAssign(t *testing.T) { }, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: true, + Added: true, }, deleteParentPoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: false, @@ -1926,7 +1926,7 @@ func TestAssign(t *testing.T) { Authorized: true, }, addPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: false, + Added: false, }, addPoliciesErr: errors.ErrAuthorization, err: errors.ErrAuthorization, @@ -2025,7 +2025,7 @@ func TestAssign(t *testing.T) { func TestUnassign(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { @@ -2271,7 +2271,7 @@ func TestUnassign(t *testing.T) { }, repoParentGroupErr: errors.ErrConflict, addParentPoliciesRes: &magistrala.AddPoliciesRes{ - Authorized: false, + Added: false, }, addParentPoliciesErr: errors.ErrAuthorization, err: errors.ErrConflict, @@ -2434,7 +2434,7 @@ func TestUnassign(t *testing.T) { func TestDeleteGroup(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := groups.NewService(repo, idProvider, authsvc) cases := []struct { diff --git a/invitations/service_test.go b/invitations/service_test.go index c1c1da521b..87fda2ef7e 100644 --- a/invitations/service_test.go +++ b/invitations/service_test.go @@ -32,7 +32,7 @@ var ( func TestSendInvitation(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := invitations.NewService(repo, authsvc, nil) cases := []struct { @@ -187,7 +187,7 @@ func TestSendInvitation(t *testing.T) { func TestViewInvitation(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := invitations.NewService(repo, authsvc, nil) validInvitation := invitations.Invitation{ @@ -364,7 +364,7 @@ func TestViewInvitation(t *testing.T) { func TestListInvitations(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := invitations.NewService(repo, authsvc, nil) validPage := invitations.Page{ @@ -542,7 +542,7 @@ func TestListInvitations(t *testing.T) { func TestAcceptInvitation(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := invitations.NewService(repo, authsvc, nil) userID := testsutil.GenerateUUID(t) @@ -615,7 +615,7 @@ func TestAcceptInvitation(t *testing.T) { func TestDeleteInvitation(t *testing.T) { repo := new(mocks.Repository) - authsvc := new(authmocks.Service) + authsvc := new(authmocks.AuthClient) svc := invitations.NewService(repo, authsvc, nil) cases := []struct { diff --git a/mqtt/handler_test.go b/mqtt/handler_test.go index d53d09788b..c46219cb01 100644 --- a/mqtt/handler_test.go +++ b/mqtt/handler_test.go @@ -441,12 +441,12 @@ func TestDisconnect(t *testing.T) { } } -func newHandler() (session.Handler, *authmocks.Service) { +func newHandler() (session.Handler, *authmocks.AuthClient) { logger, err := mglog.New(&logBuffer, "debug") if err != nil { log.Fatalf("failed to create logger: %s", err) } - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) eventStore := mocks.NewEventStore() return mqtt.NewHandler(mocks.NewPublisher(), eventStore, logger, auth), auth } diff --git a/pkg/sdk/go/certs_test.go b/pkg/sdk/go/certs_test.go index 04ede686fb..742f8a0ba0 100644 --- a/pkg/sdk/go/certs_test.go +++ b/pkg/sdk/go/certs_test.go @@ -37,7 +37,7 @@ var ( cfgSignHoursValid = "24h" ) -func setupCerts() (*httptest.Server, *authmocks.Service, *thmocks.Repository, error) { +func setupCerts() (*httptest.Server, *authmocks.AuthClient, *thmocks.Repository, error) { server, trepo, _, auth, _ := setupThings() config := sdk.Config{ ThingsURL: server.URL, diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index 7cc2bad9fb..b4232344f5 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -32,12 +32,12 @@ import ( "github.com/stretchr/testify/mock" ) -func setupChannels() (*httptest.Server, *mocks.Repository, *authmocks.Service) { +func setupChannels() (*httptest.Server, *mocks.Repository, *authmocks.AuthClient) { cRepo := new(thmocks.Repository) grepo := new(mocks.Repository) thingCache := new(thmocks.Cache) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) csvc := things.NewService(auth, cRepo, grepo, thingCache, idProvider) gsvc := groups.NewService(grepo, idProvider, auth) @@ -148,7 +148,7 @@ func TestCreateChannel(t *testing.T) { } for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall3 := grepo.On("Save", mock.Anything, mock.Anything).Return(convertChannel(sdk.Channel{}), tc.err) rChannel, err := mgsdk.CreateChannel(tc.channel, validToken) diff --git a/pkg/sdk/go/consumers_test.go b/pkg/sdk/go/consumers_test.go index 5a22d8686b..70e1de4825 100644 --- a/pkg/sdk/go/consumers_test.go +++ b/pkg/sdk/go/consumers_test.go @@ -34,9 +34,9 @@ var ( exampleUser1 = "email1@example.com" ) -func setupSubscriptions() (*httptest.Server, *authmocks.Service) { +func setupSubscriptions() (*httptest.Server, *authmocks.AuthClient) { repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) notifier := mocks.NewNotifier() idp := uuid.NewMock() from := "exampleFrom" diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 62760e1d8d..9e251cba1a 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -30,11 +30,11 @@ import ( "github.com/stretchr/testify/mock" ) -func setupGroups() (*httptest.Server, *mocks.Repository, *authmocks.Service) { +func setupGroups() (*httptest.Server, *mocks.Repository, *authmocks.AuthClient) { crepo := new(umocks.Repository) grepo := new(mocks.Repository) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, passRegex, true) gsvc := groups.NewService(grepo, idProvider, auth) @@ -146,7 +146,7 @@ func TestCreateGroup(t *testing.T) { } for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall3 := grepo.On("Save", mock.Anything, mock.Anything).Return(convertGroup(sdk.Group{}), tc.err) rGroup, err := mgsdk.CreateGroup(tc.group, tc.token) diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go index 49061b35a3..474c04e5c6 100644 --- a/pkg/sdk/go/message_test.go +++ b/pkg/sdk/go/message_test.go @@ -24,8 +24,8 @@ import ( "github.com/stretchr/testify/mock" ) -func setupMessages() (*httptest.Server, *authmocks.Service) { - auth := new(authmocks.Service) +func setupMessages() (*httptest.Server, *authmocks.AuthClient) { + auth := new(authmocks.AuthClient) pub := mocks.NewPublisher() handler := adapter.NewHandler(pub, mglog.NewMock(), auth) diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index f0f64fec46..17fa2f8973 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -30,12 +30,12 @@ import ( "github.com/stretchr/testify/mock" ) -func setupThings() (*httptest.Server, *mocks.Repository, *gmocks.Repository, *authmocks.Service, *mocks.Cache) { +func setupThings() (*httptest.Server, *mocks.Repository, *gmocks.Repository, *authmocks.AuthClient, *mocks.Cache) { cRepo := new(mocks.Repository) gRepo := new(gmocks.Repository) thingCache := new(mocks.Cache) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) csvc := things.NewService(auth, cRepo, gRepo, thingCache, idProvider) gsvc := groups.NewService(gRepo, idProvider, auth) @@ -46,12 +46,12 @@ func setupThings() (*httptest.Server, *mocks.Repository, *gmocks.Repository, *au return httptest.NewServer(mux), cRepo, gRepo, auth, thingCache } -func setupThingsMinimal() (*httptest.Server, *authmocks.Service) { +func setupThingsMinimal() (*httptest.Server, *authmocks.AuthClient) { cRepo := new(mocks.Repository) gRepo := new(gmocks.Repository) thingCache := new(mocks.Cache) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) csvc := things.NewService(auth, cRepo, gRepo, thingCache, idProvider) gsvc := groups.NewService(gRepo, idProvider, auth) @@ -187,7 +187,7 @@ func TestCreateThing(t *testing.T) { } for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall3 := cRepo.On("Save", mock.Anything, mock.Anything).Return(convertThings(tc.response), tc.repoErr) rThing, err := mgsdk.CreateThing(tc.client, tc.token) @@ -275,7 +275,7 @@ func TestCreateThings(t *testing.T) { } for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall3 := cRepo.On("Save", mock.Anything, mock.Anything).Return(convertThings(tc.response...), tc.err) if len(tc.things) > 0 { @@ -1263,11 +1263,11 @@ func TestShareThing(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.repoErr) - repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) if tc.token != validToken { repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) } - repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) + repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Added: true}, nil) req := sdk.UsersRelationRequest{ Relation: "viewer", UserIDs: []string{tc.channelID}, diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index f65dc037fc..97a2e5a172 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -37,11 +37,11 @@ var ( wrongID = testsutil.GenerateUUID(&testing.T{}) ) -func setupUsers() (*httptest.Server, *umocks.Repository, *gmocks.Repository, *authmocks.Service) { +func setupUsers() (*httptest.Server, *umocks.Repository, *gmocks.Repository, *authmocks.AuthClient) { crepo := new(umocks.Repository) gRepo := new(gmocks.Repository) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, passRegex, true) gsvc := groups.NewService(gRepo, idProvider, auth) @@ -186,7 +186,7 @@ func TestCreateClient(t *testing.T) { if tc.token != validToken { repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) } - repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(&magistrala.DeletePoliciesRes{Deleted: true}, nil) repoCall3 := crepo.On("Save", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) rClient, err := mgsdk.CreateUser(tc.client, tc.token) @@ -946,7 +946,7 @@ func TestUpdateClientRole(t *testing.T) { } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) - repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) + repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Added: true}, nil) repoCall4 := crepo.On("UpdateRole", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) uClient, err := mgsdk.UpdateUserRole(tc.client, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) diff --git a/readers/api/endpoint_test.go b/readers/api/endpoint_test.go index aefd1853fa..6c77b46b2a 100644 --- a/readers/api/endpoint_test.go +++ b/readers/api/endpoint_test.go @@ -47,7 +47,7 @@ var ( sum float64 = 42 ) -func newServer(repo readers.MessageRepository, ac *authmocks.Service, tc *thmocks.ThingAuthzService) *httptest.Server { +func newServer(repo readers.MessageRepository, ac *authmocks.AuthClient, tc *thmocks.ThingAuthzService) *httptest.Server { mux := api.MakeHandler(repo, ac, tc, svcName, instanceID) return httptest.NewServer(mux) } @@ -126,7 +126,7 @@ func TestReadAll(t *testing.T) { } repo := mocks.NewMessageRepository(chanID, fromSenml(messages)) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) tauth := new(thmocks.ThingAuthzService) ts := newServer(repo, auth, tauth) defer ts.Close() diff --git a/things/api/http/endpoints_test.go b/things/api/http/endpoints_test.go index 463160aabd..fc44c3e5ad 100644 --- a/things/api/http/endpoints_test.go +++ b/things/api/http/endpoints_test.go @@ -91,7 +91,7 @@ func toJSON(data interface{}) string { func newThingsServer() (*httptest.Server, *mocks.Service) { gRepo := new(gmocks.Repository) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) svc := new(mocks.Service) gsvc := groups.NewService(gRepo, idProvider, auth) diff --git a/things/service.go b/things/service.go index 1a5c3b5d3b..3799c3b8cd 100644 --- a/things/service.go +++ b/things/service.go @@ -413,7 +413,7 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid if err != nil { return errors.Wrap(errAddPolicies, err) } - if !res.Authorized { + if !res.Added { return err } return nil diff --git a/things/service_test.go b/things/service_test.go index 8e92c29c45..798a197234 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -48,8 +48,8 @@ var ( errRemovePolicies = errors.New("failed to remove the policies") ) -func newService() (things.Service, *mocks.Repository, *authmocks.Service, *mocks.Cache) { - auth := new(authmocks.Service) +func newService() (things.Service, *mocks.Repository, *authmocks.AuthClient, *mocks.Cache) { + auth := new(authmocks.AuthClient) thingCache := new(mocks.Cache) idProvider := uuid.NewMock() cRepo := new(mocks.Repository) @@ -100,7 +100,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -113,7 +113,7 @@ func TestCreateThings(t *testing.T) { }, }, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, token: validToken, err: nil, }, @@ -128,7 +128,7 @@ func TestCreateThings(t *testing.T) { Status: mgclients.EnabledStatus, }, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, token: validToken, err: nil, }, @@ -144,7 +144,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -159,7 +159,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -173,7 +173,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -186,7 +186,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -200,7 +200,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -219,7 +219,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -233,7 +233,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, saveErr: repoerr.ErrMalformedEntity, err: repoerr.ErrCreateEntity, }, @@ -247,7 +247,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, saveErr: repoerr.ErrMissingSecret, err: repoerr.ErrCreateEntity, }, @@ -262,7 +262,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: svcerr.ErrInvalidStatus, }, { @@ -303,7 +303,7 @@ func TestCreateThings(t *testing.T) { }, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Authorized: false}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: false}, addPolicyErr: svcerr.ErrInvalidPolicy, err: svcerr.ErrInvalidPolicy, }, @@ -1788,7 +1788,7 @@ func TestShare(t *testing.T) { clientID: clientID, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, { @@ -1824,7 +1824,7 @@ func TestShare(t *testing.T) { clientID: clientID, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: false}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: false}, err: nil, }, } diff --git a/twins/mocks/service.go b/twins/mocks/service.go index 6e7aef6e4e..c8a0d6fe6c 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -21,8 +21,8 @@ const publisher = "twins" var id = 0 // NewService use mock dependencies to create real twins service. -func NewService() (twins.Service, *authmocks.Service) { - auth := new(authmocks.Service) +func NewService() (twins.Service, *authmocks.AuthClient) { + auth := new(authmocks.AuthClient) twinsRepo := NewTwinRepository() twinCache := NewTwinCache() statesRepo := NewStateRepository() diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index 4a0d4d663c..742bddff7f 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -82,7 +82,7 @@ func (tr testRequest) make() (*http.Response, error) { func newUsersServer() (*httptest.Server, *mocks.Service) { gRepo := new(gmocks.Repository) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) svc := new(mocks.Service) gsvc := groups.NewService(gRepo, idProvider, auth) diff --git a/users/service.go b/users/service.go index 22cc05a19e..152659ba8d 100644 --- a/users/service.go +++ b/users/service.go @@ -618,7 +618,7 @@ func (svc service) addClientPolicy(ctx context.Context, userID string, role mgcl if err != nil { return err } - if !resp.Authorized { + if !resp.Added { return errors.ErrAuthorization } return nil @@ -667,7 +667,7 @@ func (svc service) updateClientPolicy(ctx context.Context, userID string, role m if err != nil { return errors.Wrap(errAddPolicies, err) } - if !resp.Authorized { + if !resp.Added { return errors.Wrap(svcerr.ErrAuthorization, err) } return nil diff --git a/users/service_test.go b/users/service_test.go index fefc6d63ac..7715fe1afa 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -51,9 +51,9 @@ var ( errDeletePolicies = errors.New("failed to delete policies") ) -func newService(selfRegister bool) (users.Service, *mocks.Repository, *authmocks.Service, users.Emailer) { +func newService(selfRegister bool) (users.Service, *mocks.Repository, *authmocks.AuthClient, users.Emailer) { cRepo := new(mocks.Repository) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) e := mocks.NewEmailer() return users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, selfRegister), cRepo, auth, e } @@ -77,14 +77,14 @@ func TestRegisterClient(t *testing.T) { { desc: "register new client successfully", client: client, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, token: validToken, err: nil, }, { desc: "register existing client", client: client, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, token: validToken, saveErr: repoerr.ErrConflict, @@ -100,7 +100,7 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, token: validToken, }, @@ -113,7 +113,7 @@ func TestRegisterClient(t *testing.T) { Secret: secret, }, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, token: validToken, }, @@ -131,7 +131,7 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.EnabledStatus, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, token: validToken, }, @@ -143,7 +143,7 @@ func TestRegisterClient(t *testing.T) { Secret: secret, }, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, saveErr: errors.ErrMalformedEntity, err: errors.ErrMalformedEntity, @@ -158,7 +158,7 @@ func TestRegisterClient(t *testing.T) { Secret: "", }, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: repoerr.ErrMissingSecret, }, @@ -171,7 +171,7 @@ func TestRegisterClient(t *testing.T) { Secret: "weak", }, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: nil, }, @@ -184,7 +184,7 @@ func TestRegisterClient(t *testing.T) { Secret: strings.Repeat("a", 73), }, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: repoerr.ErrMalformedEntity, }, @@ -198,7 +198,7 @@ func TestRegisterClient(t *testing.T) { }, Status: mgclients.AllStatus, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: svcerr.ErrInvalidStatus, }, @@ -212,7 +212,7 @@ func TestRegisterClient(t *testing.T) { }, Role: 2, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: svcerr.ErrInvalidRole, }, @@ -226,7 +226,7 @@ func TestRegisterClient(t *testing.T) { }, Role: mgclients.AdminRole, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: false}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: false}, err: errors.ErrAuthorization, }, { @@ -239,7 +239,7 @@ func TestRegisterClient(t *testing.T) { }, Role: mgclients.AdminRole, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, addPoliciesResponseErr: errAddPolicies, err: errAddPolicies, }, @@ -253,7 +253,7 @@ func TestRegisterClient(t *testing.T) { }, Role: mgclients.AdminRole, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, deletePoliciesResponseErr: errDeletePolicies, saveErr: repoerr.ErrConflict, @@ -269,7 +269,7 @@ func TestRegisterClient(t *testing.T) { }, Role: mgclients.AdminRole, }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, saveErr: repoerr.ErrConflict, err: svcerr.ErrAuthorization, @@ -321,7 +321,7 @@ func TestRegisterClient(t *testing.T) { client: client, identifyResponse: &magistrala.IdentityRes{UserId: validID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPoliciesResponse: &magistrala.AddPoliciesRes{Authorized: true}, + addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, token: validToken, err: nil, }, @@ -1004,7 +1004,7 @@ func TestUpdateClientRole(t *testing.T) { client: client, identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Added: true}, updateRoleResponse: client, token: validToken, err: nil, @@ -1039,7 +1039,7 @@ func TestUpdateClientRole(t *testing.T) { client: client, identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPolicyRes{Authorized: false}, + addPolicyResponse: &magistrala.AddPolicyRes{Added: false}, token: validToken, err: errors.ErrAuthorization, }, @@ -1087,7 +1087,7 @@ func TestUpdateClientRole(t *testing.T) { client: client, identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Added: true}, deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, updateRoleResponse: mgclients.Client{}, token: validToken, @@ -1099,7 +1099,7 @@ func TestUpdateClientRole(t *testing.T) { client: client, identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPolicyRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPolicyRes{Added: true}, deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, updateRoleResponse: mgclients.Client{}, token: validToken, diff --git a/ws/adapter_test.go b/ws/adapter_test.go index d10954d7b4..8b9361b2b6 100644 --- a/ws/adapter_test.go +++ b/ws/adapter_test.go @@ -34,9 +34,9 @@ var msg = messaging.Message{ Payload: []byte(`[{"n":"current","t":-5,"v":1.2}]`), } -func newService() (ws.Service, *mocks.PubSub, *authmocks.Service) { +func newService() (ws.Service, *mocks.PubSub, *authmocks.AuthClient) { pubsub := new(mocks.PubSub) - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) return ws.New(auth, pubsub), pubsub, auth } diff --git a/ws/api/endpoint_test.go b/ws/api/endpoint_test.go index 359f6bfaed..25963522d1 100644 --- a/ws/api/endpoint_test.go +++ b/ws/api/endpoint_test.go @@ -90,7 +90,7 @@ func handshake(tsURL, chanID, subtopic, thingKey string, addHeader bool) (*webso } func TestHandshake(t *testing.T) { - auth := new(authmocks.Service) + auth := new(authmocks.AuthClient) svc, pubsub := newService(auth) target := newHTTPServer(svc) defer target.Close() From 95c4cc551677bacf26b042b9cbed294eb8f9dcb1 Mon Sep 17 00:00:00 2001 From: Dusan Borovcanin Date: Thu, 25 Jan 2024 14:58:11 +0100 Subject: [PATCH 28/71] NOISSUE - Replace docs link in API docs Signed-off-by: Dusan Borovcanin --- api/openapi/auth.yml | 4 ++-- api/openapi/bootstrap.yml | 2 +- api/openapi/certs.yml | 2 +- api/openapi/consumers-notifiers.yml | 2 +- api/openapi/http.yml | 2 +- api/openapi/invitations.yml | 2 +- api/openapi/provision.yml | 2 +- api/openapi/readers.yml | 2 +- api/openapi/things.yml | 6 +++--- api/openapi/twins.yml | 2 +- api/openapi/users.yml | 4 ++-- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml index 9ab9366fa7..5d798276fd 100644 --- a/api/openapi/auth.yml +++ b/api/openapi/auth.yml @@ -24,12 +24,12 @@ tags: description: Everything about your Authentication and Authorization. externalDocs: description: Find out more about auth - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ - name: Keys description: Everything about your Keys. externalDocs: description: Find out more about keys - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /domains: diff --git a/api/openapi/bootstrap.yml b/api/openapi/bootstrap.yml index f10f73c517..2a8177a54f 100644 --- a/api/openapi/bootstrap.yml +++ b/api/openapi/bootstrap.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Configs externalDocs: description: Find out more about Configs - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /things/configs: diff --git a/api/openapi/certs.yml b/api/openapi/certs.yml index c23f00deea..e8a7e88f4d 100644 --- a/api/openapi/certs.yml +++ b/api/openapi/certs.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Certs externalDocs: description: Find out more about certs - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /certs: diff --git a/api/openapi/consumers-notifiers.yml b/api/openapi/consumers-notifiers.yml index be9c9ffcf0..067d8f96bf 100644 --- a/api/openapi/consumers-notifiers.yml +++ b/api/openapi/consumers-notifiers.yml @@ -26,7 +26,7 @@ tags: description: Everything about your Notifiers externalDocs: description: Find out more about notifiers - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /subscriptions: diff --git a/api/openapi/http.yml b/api/openapi/http.yml index 2a5944e2ac..5a5a985f09 100644 --- a/api/openapi/http.yml +++ b/api/openapi/http.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Messages externalDocs: description: Find out more about messages - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /channels/{id}/messages: diff --git a/api/openapi/invitations.yml b/api/openapi/invitations.yml index 768320445e..0dd3c3fc8d 100644 --- a/api/openapi/invitations.yml +++ b/api/openapi/invitations.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Invitations externalDocs: description: Find out more about Invitations - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /invitations: diff --git a/api/openapi/provision.yml b/api/openapi/provision.yml index cfa0edfe6d..e7d57dae24 100644 --- a/api/openapi/provision.yml +++ b/api/openapi/provision.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Provision externalDocs: description: Find out more about provision - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /mapping: diff --git a/api/openapi/readers.yml b/api/openapi/readers.yml index c734b1dbbd..47cea33ebd 100644 --- a/api/openapi/readers.yml +++ b/api/openapi/readers.yml @@ -32,7 +32,7 @@ tags: description: Everything about your Readers externalDocs: description: Find out more about readers - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /channels/{chanId}/messages: diff --git a/api/openapi/things.yml b/api/openapi/things.yml index 477a2d37c0..6460d8f379 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -24,17 +24,17 @@ tags: description: Everything about your Things externalDocs: description: Find out more about things - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ - name: Channels description: Everything about your Channels externalDocs: description: Find out more about things channels - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ - name: Policies description: Access to things policies externalDocs: description: Find out more about things policies - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /things: diff --git a/api/openapi/twins.yml b/api/openapi/twins.yml index 7398a28525..fd826ca319 100644 --- a/api/openapi/twins.yml +++ b/api/openapi/twins.yml @@ -24,7 +24,7 @@ tags: description: Everything about your Twins externalDocs: description: Find out more about twins - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 6cb178605f..4868c08e96 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -24,12 +24,12 @@ tags: description: Everything about your Users externalDocs: description: Find out more about users - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ - name: Groups description: Everything about your Groups externalDocs: description: Find out more about users groups - url: http://docs.mainflux.io/ + url: https://docs.magistrala.abstractmachines.fr/ paths: /users: From 48379d69b5aa26f866a80b5fe6c3af7d4019fe04 Mon Sep 17 00:00:00 2001 From: Sammy Kerata Oina <44265300+SammyOina@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:40:26 +0300 Subject: [PATCH 29/71] NOISSUE - Replace deprecated gRPC instrumentation method (#278) Signed-off-by: SammyOina --- internal/server/grpc/grpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/grpc/grpc.go b/internal/server/grpc/grpc.go index 86042cfe69..6a91495faa 100644 --- a/internal/server/grpc/grpc.go +++ b/internal/server/grpc/grpc.go @@ -52,7 +52,7 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser func (s *Server) Start() error { errCh := make(chan error) grpcServerOptions := []grpc.ServerOption{ - grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), + grpc.StatsHandler(otelgrpc.NewServerHandler()), } listener, err := net.Listen("tcp", s.Address) From 64a1672e675491c63d898ca0f9f0321fd0ab4323 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:02:13 +0300 Subject: [PATCH 30/71] NOISSUE - Make Check Interval Configurable (#277) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: rodneyosodo --- pkg/events/nats/publisher.go | 11 -- pkg/events/nats/publisher_test.go | 143 ++++++++++++++------------ pkg/events/nats/setup_test.go | 6 +- pkg/events/rabbitmq/publisher.go | 50 ++------- pkg/events/rabbitmq/publisher_test.go | 134 +++++++++++++----------- pkg/events/rabbitmq/setup_test.go | 13 +-- pkg/events/redis/publisher.go | 18 ++-- pkg/events/redis/publisher_test.go | 92 ++++++++++++++--- pkg/events/redis/setup_test.go | 9 +- pkg/events/store/brokers_redis.go | 2 +- 10 files changed, 252 insertions(+), 226 deletions(-) diff --git a/pkg/events/nats/publisher.go b/pkg/events/nats/publisher.go index 29c07f1e74..e711f9701c 100644 --- a/pkg/events/nats/publisher.go +++ b/pkg/events/nats/publisher.go @@ -50,8 +50,6 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er stream: stream, } - go es.StartPublishingRoutine(ctx) - return es, nil } @@ -74,15 +72,6 @@ func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error return es.publisher.Publish(ctx, es.stream, record) } -func (es *pubEventStore) StartPublishingRoutine(ctx context.Context) { - // Nats doesn't need to check for unpublished events - // since the events are published to a buffer. - // The buffer is flushed when the connection is reestablished. - // https://docs.nats.io/using-nats/developer/connecting/reconnect/buffer - - <-ctx.Done() -} - func (es *pubEventStore) Close() error { es.conn.Close() diff --git a/pkg/events/nats/publisher_test.go b/pkg/events/nats/publisher_test.go index fc864bf165..ef69550644 100644 --- a/pkg/events/nats/publisher_test.go +++ b/pkg/events/nats/publisher_test.go @@ -23,6 +23,7 @@ var ( eventsChan = make(chan map[string]interface{}) logger = mglog.NewMock() errFailed = errors.New("failed") + numEvents = 100 ) type testEvent struct { @@ -50,16 +51,19 @@ func (te testEvent) Encode() (map[string]interface{}, error) { } func TestPublish(t *testing.T) { - publisher, err := nats.NewPublisher(ctx, natsURL, stream) + _, err := nats.NewPublisher(context.Background(), "http://invaliurl.com", stream) + assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) + + publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - _, err = nats.NewSubscriber(ctx, "http://invaliurl.com", stream, consumer, logger) + _, err = nats.NewSubscriber(context.Background(), "http://invaliurl.com", stream, consumer, logger) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - subcriber, err := nats.NewSubscriber(ctx, natsURL, stream, consumer, logger) + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(ctx, handler{}) + err = subcriber.Subscribe(context.Background(), handler{}) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -122,11 +126,9 @@ func TestPublish(t *testing.T) { for _, tc := range cases { event := testEvent{Data: tc.event} - err := publisher.Publish(ctx, event) + err := publisher.Publish(context.Background(), event) switch tc.err { case nil: - assert.Nil(t, err, fmt.Sprintf("%s - got unexpected error: %s", tc.desc, err)) - receivedEvent := <-eventsChan val := int64(receivedEvent["occurred_at"].(float64)) @@ -135,67 +137,18 @@ func TestPublish(t *testing.T) { delete(tc.event, "occurred_at") } - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"], fmt.Sprintf("%s - expected temperature: %s, got: %s", tc.desc, tc.event["temperature"], receivedEvent["temperature"])) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"], fmt.Sprintf("%s - expected humidity: %s, got: %s", tc.desc, tc.event["humidity"], receivedEvent["humidity"])) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"], fmt.Sprintf("%s - expected sensor_id: %s, got: %s", tc.desc, tc.event["sensor_id"], receivedEvent["sensor_id"])) - assert.Equal(t, tc.event["status"], receivedEvent["status"], fmt.Sprintf("%s - expected status: %s, got: %s", tc.desc, tc.event["status"], receivedEvent["status"])) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"], fmt.Sprintf("%s - expected timestamp: %s, got: %s", tc.desc, tc.event["timestamp"], receivedEvent["timestamp"])) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"], fmt.Sprintf("%s - expected operation: %s, got: %s", tc.desc, tc.event["operation"], receivedEvent["operation"])) - + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) default: - assert.ErrorContains(t, err, tc.err.Error(), fmt.Sprintf("%s - expected error: %s", tc.desc, tc.err)) + assert.ErrorContains(t, err, tc.err.Error()) } } } -func TestUnavailablePublish(t *testing.T) { - _, err := nats.NewPublisher(ctx, "http://invaliurl.com", stream) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - publisher, err := nats.NewPublisher(ctx, natsURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - err = pool.Client.PauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) - - spawnGoroutines(publisher, t) - - err = pool.Client.UnpauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) - - // Wait for the events to be published. - time.Sleep(events.UnpublishedEventsCheckInterval) - - err = publisher.Close() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) -} - -func generateRandomEvent() testEvent { - return testEvent{ - Data: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), - "location": fmt.Sprintf("%f", rand.Float64()), - "status": fmt.Sprintf("%d", rand.Intn(1000)), - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - }, - } -} - -func spawnGoroutines(publisher events.Publisher, t *testing.T) { - for i := 0; i < 1e4; i++ { - go func() { - for i := 0; i < 10; i++ { - event := generateRandomEvent() - err := publisher.Publish(ctx, event) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - } - }() - } -} - func TestPubsub(t *testing.T) { subcases := []struct { desc string @@ -256,16 +209,14 @@ func TestPubsub(t *testing.T) { } for _, pc := range subcases { - subcriber, err := nats.NewSubscriber(ctx, natsURL, pc.stream, pc.consumer, logger) + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, pc.stream, pc.consumer, logger) if err != nil { assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) continue } - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - - switch err := subcriber.Subscribe(context.TODO(), pc.handler); { + switch err := subcriber.Subscribe(context.Background(), pc.handler); { case err == nil: assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) default: @@ -277,6 +228,64 @@ func TestPubsub(t *testing.T) { } } +func TestUnavailablePublish(t *testing.T) { + publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + err = subcriber.Subscribe(context.Background(), handler{}) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) + + err = pool.Client.PauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) + + spawnGoroutines(publisher, t) + + time.Sleep(1 * time.Second) + + err = pool.Client.UnpauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) + + // Wait for the events to be published. + time.Sleep(1 * time.Second) + + err = publisher.Close() + assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) + + // read all the events from the channel and assert that they are 10. + var receivedEvents []map[string]interface{} + for i := 0; i < numEvents; i++ { + event := <-eventsChan + receivedEvents = append(receivedEvents, event) + } + assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") +} + +func generateRandomEvent() testEvent { + return testEvent{ + Data: map[string]interface{}{ + "temperature": fmt.Sprintf("%f", rand.Float64()), + "humidity": fmt.Sprintf("%f", rand.Float64()), + "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), + "location": fmt.Sprintf("%f", rand.Float64()), + "status": fmt.Sprintf("%d", rand.Intn(1000)), + "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), + "operation": "create", + }, + } +} + +func spawnGoroutines(publisher events.Publisher, t *testing.T) { + for i := 0; i < numEvents; i++ { + go func() { + err := publisher.Publish(context.Background(), generateRandomEvent()) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + }() + } +} + type handler struct { fail bool } diff --git a/pkg/events/nats/setup_test.go b/pkg/events/nats/setup_test.go index d7965b336d..143e4863b9 100644 --- a/pkg/events/nats/setup_test.go +++ b/pkg/events/nats/setup_test.go @@ -20,7 +20,6 @@ var ( natsURL string stream = "tests.events" consumer = "tests-consumer" - ctx = context.Background() pool *dockertest.Pool container *dockertest.Resource ) @@ -33,7 +32,6 @@ func TestMain(m *testing.M) { } container, err = pool.RunWithOptions(&dockertest.RunOptions{ - Name: "test-nats-events", Repository: "nats", Tag: "2.10.9-alpine", Cmd: []string{"-DVV", "-js"}, @@ -47,14 +45,14 @@ func TestMain(m *testing.M) { natsURL = fmt.Sprintf("nats://%s:%s", "localhost", container.GetPort("4222/tcp")) if err := pool.Retry(func() error { - _, err = nats.NewPublisher(ctx, natsURL, stream) + _, err = nats.NewPublisher(context.Background(), natsURL, stream) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) } if err := pool.Retry(func() error { - _, err = nats.NewSubscriber(ctx, natsURL, stream, consumer, logger) + _, err = nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/pkg/events/rabbitmq/publisher.go b/pkg/events/rabbitmq/publisher.go index 37ca3e19bf..ba7d735ae6 100644 --- a/pkg/events/rabbitmq/publisher.go +++ b/pkg/events/rabbitmq/publisher.go @@ -6,7 +6,6 @@ package rabbitmq import ( "context" "encoding/json" - "sync" "time" "github.com/absmach/magistrala/pkg/events" @@ -16,11 +15,9 @@ import ( ) type pubEventStore struct { - conn *amqp.Connection - publisher messaging.Publisher - unpublishedEvents chan amqp.Return - stream string - mu sync.Mutex + conn *amqp.Connection + publisher messaging.Publisher + stream string } func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { @@ -42,16 +39,11 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er } es := &pubEventStore{ - conn: conn, - publisher: publisher, - unpublishedEvents: make(chan amqp.Return, events.MaxUnpublishedEvents), - stream: stream, + conn: conn, + publisher: publisher, + stream: stream, } - ch.NotifyReturn(es.unpublishedEvents) - - go es.StartPublishingRoutine(ctx) - return es, nil } @@ -74,36 +66,6 @@ func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error return es.publisher.Publish(ctx, es.stream, record) } -func (es *pubEventStore) StartPublishingRoutine(ctx context.Context) { - defer close(es.unpublishedEvents) - - ticker := time.NewTicker(events.UnpublishedEventsCheckInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if ok := es.conn.IsClosed(); !ok { - es.mu.Lock() - for i := len(es.unpublishedEvents) - 1; i >= 0; i-- { - record := <-es.unpublishedEvents - msg := &messaging.Message{ - Payload: record.Body, - } - if err := es.publisher.Publish(ctx, es.stream, msg); err != nil { - es.unpublishedEvents <- record - - break - } - } - es.mu.Unlock() - } - case <-ctx.Done(): - return - } - } -} - func (es *pubEventStore) Close() error { es.conn.Close() diff --git a/pkg/events/rabbitmq/publisher_test.go b/pkg/events/rabbitmq/publisher_test.go index c9c83fbf62..252784a3c0 100644 --- a/pkg/events/rabbitmq/publisher_test.go +++ b/pkg/events/rabbitmq/publisher_test.go @@ -23,6 +23,7 @@ var ( eventsChan = make(chan map[string]interface{}) logger = mglog.NewMock() errFailed = errors.New("failed") + numEvents = 100 ) type testEvent struct { @@ -50,7 +51,10 @@ func (te testEvent) Encode() (map[string]interface{}, error) { } func TestPublish(t *testing.T) { - publisher, err := rabbitmq.NewPublisher(ctx, rabbitmqURL, stream) + _, err := rabbitmq.NewPublisher(context.Background(), "http://invaliurl.com", stream) + assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) + + publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) _, err = rabbitmq.NewSubscriber("http://invaliurl.com", stream, consumer, logger) @@ -59,7 +63,7 @@ func TestPublish(t *testing.T) { subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, stream, consumer, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(ctx, handler{}) + err = subcriber.Subscribe(context.Background(), handler{}) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -122,11 +126,9 @@ func TestPublish(t *testing.T) { for _, tc := range cases { event := testEvent{Data: tc.event} - err := publisher.Publish(ctx, event) + err := publisher.Publish(context.Background(), event) switch tc.err { case nil: - assert.Nil(t, err, fmt.Sprintf("%s - got unexpected error: %s", tc.desc, err)) - receivedEvent := <-eventsChan val := int64(receivedEvent["occurred_at"].(float64)) @@ -135,12 +137,12 @@ func TestPublish(t *testing.T) { delete(tc.event, "occurred_at") } - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"], fmt.Sprintf("%s - expected temperature: %s, got: %s", tc.desc, tc.event["temperature"], receivedEvent["temperature"])) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"], fmt.Sprintf("%s - expected humidity: %s, got: %s", tc.desc, tc.event["humidity"], receivedEvent["humidity"])) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"], fmt.Sprintf("%s - expected sensor_id: %s, got: %s", tc.desc, tc.event["sensor_id"], receivedEvent["sensor_id"])) - assert.Equal(t, tc.event["status"], receivedEvent["status"], fmt.Sprintf("%s - expected status: %s, got: %s", tc.desc, tc.event["status"], receivedEvent["status"])) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"], fmt.Sprintf("%s - expected timestamp: %s, got: %s", tc.desc, tc.event["timestamp"], receivedEvent["timestamp"])) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"], fmt.Sprintf("%s - expected operation: %s, got: %s", tc.desc, tc.event["operation"], receivedEvent["operation"])) + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) default: assert.ErrorContains(t, err, tc.err.Error(), fmt.Sprintf("%s - expected error: %s", tc.desc, tc.err)) @@ -148,54 +150,6 @@ func TestPublish(t *testing.T) { } } -func TestUnavailablePublish(t *testing.T) { - _, err := rabbitmq.NewPublisher(ctx, "http://invaliurl.com", stream) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - publisher, err := rabbitmq.NewPublisher(ctx, rabbitmqURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - err = pool.Client.PauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) - - spawnGoroutines(publisher, t) - - err = pool.Client.UnpauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) - - // Wait for the events to be published. - time.Sleep(2 * events.UnpublishedEventsCheckInterval) - - err = publisher.Close() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) -} - -func generateRandomEvent() testEvent { - return testEvent{ - Data: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), - "location": fmt.Sprintf("%f", rand.Float64()), - "status": fmt.Sprintf("%d", rand.Intn(1000)), - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - }, - } -} - -func spawnGoroutines(publisher events.Publisher, t *testing.T) { - for i := 0; i < 1e4; i++ { - go func() { - for i := 0; i < 10; i++ { - event := generateRandomEvent() - err := publisher.Publish(ctx, event) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - } - }() - } -} - func TestPubsub(t *testing.T) { subcases := []struct { desc string @@ -263,9 +217,7 @@ func TestPubsub(t *testing.T) { continue } - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - - switch err := subcriber.Subscribe(ctx, pc.handler); { + switch err := subcriber.Subscribe(context.Background(), pc.handler); { case err == nil: assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) default: @@ -277,6 +229,64 @@ func TestPubsub(t *testing.T) { } } +func TestUnavailablePublish(t *testing.T) { + publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, stream, consumer, logger) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + err = subcriber.Subscribe(context.Background(), handler{}) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) + + err = pool.Client.PauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) + + spawnGoroutines(publisher, t) + + time.Sleep(1 * time.Second) + + err = pool.Client.UnpauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) + + // Wait for the events to be published. + time.Sleep(1 * time.Second) + + err = publisher.Close() + assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) + + // read all the events from the channel and assert that they are 10. + var receivedEvents []map[string]interface{} + for i := 0; i < numEvents; i++ { + event := <-eventsChan + receivedEvents = append(receivedEvents, event) + } + assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") +} + +func generateRandomEvent() testEvent { + return testEvent{ + Data: map[string]interface{}{ + "temperature": fmt.Sprintf("%f", rand.Float64()), + "humidity": fmt.Sprintf("%f", rand.Float64()), + "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), + "location": fmt.Sprintf("%f", rand.Float64()), + "status": fmt.Sprintf("%d", rand.Intn(1000)), + "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), + "operation": "create", + }, + } +} + +func spawnGoroutines(publisher events.Publisher, t *testing.T) { + for i := 0; i < numEvents; i++ { + go func() { + err := publisher.Publish(context.Background(), generateRandomEvent()) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + }() + } +} + type handler struct { fail bool } diff --git a/pkg/events/rabbitmq/setup_test.go b/pkg/events/rabbitmq/setup_test.go index a36a2063b6..4db3c7c254 100644 --- a/pkg/events/rabbitmq/setup_test.go +++ b/pkg/events/rabbitmq/setup_test.go @@ -20,9 +20,8 @@ var ( rabbitmqURL string stream = "tests.events" consumer = "tests-consumer" - ctx = context.TODO() - pool = &dockertest.Pool{} - container = &dockertest.Resource{} + pool *dockertest.Pool + container *dockertest.Resource ) func TestMain(m *testing.M) { @@ -32,12 +31,10 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - opts := dockertest.RunOptions{ - Name: "test-rabbitmq-events", + container, err = pool.RunWithOptions(&dockertest.RunOptions{ Repository: "rabbitmq", Tag: "3.12.12", - } - container, err = pool.RunWithOptions(&opts) + }) if err != nil { log.Fatalf("Could not start container: %s", err) } @@ -47,7 +44,7 @@ func TestMain(m *testing.M) { rabbitmqURL = fmt.Sprintf("amqp://%s:%s", "localhost", container.GetPort("5672/tcp")) if err := pool.Retry(func() error { - _, err = rabbitmq.NewPublisher(ctx, rabbitmqURL, stream) + _, err = rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/pkg/events/redis/publisher.go b/pkg/events/redis/publisher.go index c0e7a16888..a6f5c90aca 100644 --- a/pkg/events/redis/publisher.go +++ b/pkg/events/redis/publisher.go @@ -20,9 +20,10 @@ type pubEventStore struct { unpublishedEvents chan *redis.XAddArgs stream string mu sync.Mutex + flushPeriod time.Duration } -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { +func NewPublisher(ctx context.Context, url, stream string, flushPeriod time.Duration) (events.Publisher, error) { opts, err := redis.ParseURL(url) if err != nil { return nil, err @@ -32,9 +33,10 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er client: redis.NewClient(opts), unpublishedEvents: make(chan *redis.XAddArgs, events.MaxUnpublishedEvents), stream: stream, + flushPeriod: flushPeriod, } - go es.startPublishingRoutine(ctx) + go es.flushUnpublished(ctx) return es, nil } @@ -53,7 +55,7 @@ func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error Values: values, } - switch err := es.checkRedisConnection(ctx); err { + switch err := es.checkConnection(ctx); err { case nil: return es.client.XAdd(ctx, record).Err() default: @@ -71,16 +73,18 @@ func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error } } -func (es *pubEventStore) startPublishingRoutine(ctx context.Context) { +// flushUnpublished periodically checks the Redis connection and publishes +// the events that were not published due to a connection error. +func (es *pubEventStore) flushUnpublished(ctx context.Context) { defer close(es.unpublishedEvents) - ticker := time.NewTicker(events.UnpublishedEventsCheckInterval) + ticker := time.NewTicker(es.flushPeriod) defer ticker.Stop() for { select { case <-ticker.C: - if err := es.checkRedisConnection(ctx); err == nil { + if err := es.checkConnection(ctx); err == nil { es.mu.Lock() for i := len(es.unpublishedEvents) - 1; i >= 0; i-- { record := <-es.unpublishedEvents @@ -102,7 +106,7 @@ func (es *pubEventStore) Close() error { return es.client.Close() } -func (es *pubEventStore) checkRedisConnection(ctx context.Context) error { +func (es *pubEventStore) checkConnection(ctx context.Context) error { // A timeout is used to avoid blocking the main thread ctx, cancel := context.WithTimeout(ctx, events.ConnCheckInterval) defer cancel() diff --git a/pkg/events/redis/publisher_test.go b/pkg/events/redis/publisher_test.go index 82ef61589b..9bf226dd76 100644 --- a/pkg/events/redis/publisher_test.go +++ b/pkg/events/redis/publisher_test.go @@ -29,7 +29,7 @@ var ( eventsChan = make(chan map[string]interface{}) logger = mglog.NewMock() errFailed = errors.New("failed") - ctx = context.TODO() + numEvents = 100 ) type testEvent struct { @@ -57,19 +57,22 @@ func (te testEvent) Encode() (map[string]interface{}, error) { } func TestPublish(t *testing.T) { - err := redisClient.FlushAll(ctx).Err() + err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) - publisher, err := redis.NewPublisher(ctx, redisURL, streamName) + _, err = redis.NewPublisher(context.Background(), "http://invaliurl.com", streamName, events.UnpublishedEventsCheckInterval) + assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) + + publisher, err := redis.NewPublisher(context.Background(), redisURL, streamName, events.UnpublishedEventsCheckInterval) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - subcriber, err := redis.NewSubscriber("http://invaliurl.com", streamName, consumer, logger) + _, err = redis.NewSubscriber("http://invaliurl.com", streamName, consumer, logger) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - subcriber, err = redis.NewSubscriber(redisURL, streamName, consumer, logger) + subcriber, err := redis.NewSubscriber(redisURL, streamName, consumer, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(ctx, handler{}) + err = subcriber.Subscribe(context.Background(), handler{}) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -132,11 +135,9 @@ func TestPublish(t *testing.T) { for _, tc := range cases { event := testEvent{Data: tc.event} - err := publisher.Publish(ctx, event) + err := publisher.Publish(context.Background(), event) switch tc.err { case nil: - assert.Nil(t, err, fmt.Sprintf("%s - got unexpected error: %s", tc.desc, err)) - receivedEvent := <-eventsChan roa, err := strconv.ParseInt(receivedEvent["occurred_at"].(string), 10, 64) @@ -146,12 +147,12 @@ func TestPublish(t *testing.T) { delete(tc.event, "occurred_at") } - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"], fmt.Sprintf("%s - expected temperature: %s, got: %s", tc.desc, tc.event["temperature"], receivedEvent["temperature"])) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"], fmt.Sprintf("%s - expected humidity: %s, got: %s", tc.desc, tc.event["humidity"], receivedEvent["humidity"])) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"], fmt.Sprintf("%s - expected sensor_id: %s, got: %s", tc.desc, tc.event["sensor_id"], receivedEvent["sensor_id"])) - assert.Equal(t, tc.event["status"], receivedEvent["status"], fmt.Sprintf("%s - expected status: %s, got: %s", tc.desc, tc.event["status"], receivedEvent["status"])) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"], fmt.Sprintf("%s - expected timestamp: %s, got: %s", tc.desc, tc.event["timestamp"], receivedEvent["timestamp"])) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"], fmt.Sprintf("%s - expected operation: %s, got: %s", tc.desc, tc.event["operation"], receivedEvent["operation"])) + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) default: assert.ErrorContains(t, err, tc.err.Error(), fmt.Sprintf("%s - expected error: %s", tc.desc, tc.err)) @@ -160,7 +161,7 @@ func TestPublish(t *testing.T) { } func TestPubsub(t *testing.T) { - err := redisClient.FlushAll(ctx).Err() + err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) subcases := []struct { @@ -231,7 +232,7 @@ func TestPubsub(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - switch err := subcriber.Subscribe(context.TODO(), pc.handler); { + switch err := subcriber.Subscribe(context.Background(), pc.handler); { case err == nil: assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) default: @@ -243,6 +244,63 @@ func TestPubsub(t *testing.T) { } } +func TestUnavailablePublish(t *testing.T) { + publisher, err := redis.NewPublisher(context.Background(), redisURL, streamName, time.Second) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + subcriber, err := redis.NewSubscriber(redisURL, streamName, consumer, logger) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + + err = subcriber.Subscribe(context.Background(), handler{}) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) + + err = pool.Client.PauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) + + spawnGoroutines(publisher, t) + + time.Sleep(1 * time.Second) + + err = pool.Client.UnpauseContainer(container.Container.ID) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) + + // Wait for the events to be published. + time.Sleep(1 * time.Second) + + err = publisher.Close() + assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) + + var receivedEvents []map[string]interface{} + for i := 0; i < numEvents; i++ { + event := <-eventsChan + receivedEvents = append(receivedEvents, event) + } + assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") +} + +func generateRandomEvent() testEvent { + return testEvent{ + Data: map[string]interface{}{ + "temperature": fmt.Sprintf("%f", rand.Float64()), + "humidity": fmt.Sprintf("%f", rand.Float64()), + "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), + "location": fmt.Sprintf("%f", rand.Float64()), + "status": fmt.Sprintf("%d", rand.Intn(1000)), + "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), + "operation": "create", + }, + } +} + +func spawnGoroutines(publisher events.Publisher, t *testing.T) { + for i := 0; i < numEvents; i++ { + go func() { + err := publisher.Publish(context.Background(), generateRandomEvent()) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + }() + } +} + type handler struct { fail bool } diff --git a/pkg/events/redis/setup_test.go b/pkg/events/redis/setup_test.go index 851c3f1206..541cb2a3d1 100644 --- a/pkg/events/redis/setup_test.go +++ b/pkg/events/redis/setup_test.go @@ -7,6 +7,7 @@ package redis_test import ( + "context" "fmt" "log" "os" @@ -32,12 +33,10 @@ func TestMain(m *testing.M) { log.Fatalf("Could not connect to docker: %s", err) } - opts := dockertest.RunOptions{ - Name: "tests-redis-events", + container, err = pool.RunWithOptions(&dockertest.RunOptions{ Repository: "redis", Tag: "7.2.4-alpine", - } - container, err = pool.RunWithOptions(&opts) + }) if err != nil { log.Fatalf("Could not start container: %s", err) } @@ -53,7 +52,7 @@ func TestMain(m *testing.M) { if err := pool.Retry(func() error { redisClient = redis.NewClient(ropts) - return redisClient.Ping(ctx).Err() + return redisClient.Ping(context.Background()).Err() }); err != nil { log.Fatalf("Could not connect to docker: %s", err) } diff --git a/pkg/events/store/brokers_redis.go b/pkg/events/store/brokers_redis.go index f88f093e46..711310123e 100644 --- a/pkg/events/store/brokers_redis.go +++ b/pkg/events/store/brokers_redis.go @@ -20,7 +20,7 @@ func init() { } func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - pb, err := redis.NewPublisher(ctx, url, stream) + pb, err := redis.NewPublisher(ctx, url, stream, events.UnpublishedEventsCheckInterval) if err != nil { return nil, err } From c3742aa84c46d487afc78a4b2caa1c18cb0c5006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Fri, 26 Jan 2024 11:16:47 +0100 Subject: [PATCH 31/71] NOISSUE - Update CallHome version (#2066) Signed-off-by: Dusan Borovcanin --- go.mod | 6 ++++-- go.sum | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 9ec172a1bd..ce2f6d62fb 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ module github.com/absmach/magistrala -go 1.21 +go 1.21.5 + +toolchain go1.21.6 require ( github.com/0x6flab/namegenerator v1.2.0 - github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd + github.com/absmach/callhome v0.14.0 github.com/absmach/mproxy v0.4.2 github.com/absmach/senml v1.0.5 github.com/authzed/authzed-go v0.10.1 diff --git a/go.sum b/go.sum index b044da2ddb..3ccfe84987 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd h1:w6/9rG7Ov0aFz6BuArGW2HjWCHAlB1FyZI278rigR/c= -github.com/absmach/callhome v0.0.0-20240117170159-c5f5cccd21fd/go.mod h1:GCP7YZVCYC/LmG1pnqDcQl819ej9kXwsGgi6YvjzIDk= +github.com/absmach/callhome v0.14.0 h1:zB4tIZJ1YUmZ1VGHFPfMA/Lo6/Mv19y2dvoOiXj2BWs= +github.com/absmach/callhome v0.14.0/go.mod h1:l12UJOfibK4Muvg/AbupHuquNV9qSz/ROdTEPg7f2Vk= github.com/absmach/mproxy v0.4.2 h1:u0ORPxSrUknqbVrC+E1MdsCv/7Q5eWNG7clIwOMV5hk= github.com/absmach/mproxy v0.4.2/go.mod h1:TeXhbHdjihXLVoohSzxvIEFzWu16WDOa91LNduks/N8= github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= From 79d089d46b62e7ea7d92e76e5ce9b466db968cfc Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:24:24 +0530 Subject: [PATCH 32/71] NOISSUE - Fix Listing of things connected to a channel (#279) Signed-off-by: Arvindh --- things/api/http/endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index dd86e70f4e..a84e012042 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -138,7 +138,7 @@ func listMembersEndpoint(svc things.Service) endpoint.Endpoint { if err := req.validate(); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - + req.Page.Role = mgclients.AllRole // retrieve all things since things don't have roles page, err := svc.ListClientsByGroup(ctx, req.token, req.groupID, req.Page) if err != nil { return nil, err From ede1b39b9fd86f4fff4d7d6540d89a5e4fe87574 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Fri, 26 Jan 2024 22:39:34 +0530 Subject: [PATCH 33/71] NOISSUE - Fix messaging handler logger (#280) Signed-off-by: Arvindh --- pkg/messaging/handler/logging.go | 54 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/pkg/messaging/handler/logging.go b/pkg/messaging/handler/logging.go index 6b92ca67b9..4b1eceaf18 100644 --- a/pkg/messaging/handler/logging.go +++ b/pkg/messaging/handler/logging.go @@ -22,43 +22,51 @@ type loggingMiddleware struct { // AuthConnect implements session.Handler. func (lm *loggingMiddleware) AuthConnect(ctx context.Context) (err error) { - return lm.logAction(ctx, "AuthConnect", nil) + defer lm.logAction(ctx, "AuthConnect", nil, time.Now(), err) + return lm.svc.AuthConnect(ctx) } // AuthPublish implements session.Handler. func (lm *loggingMiddleware) AuthPublish(ctx context.Context, topic *string, payload *[]byte) (err error) { - return lm.logAction(ctx, "AuthPublish", &[]string{*topic}) + defer lm.logAction(ctx, "AuthPublish", &[]string{*topic}, time.Now(), err) + return lm.svc.AuthPublish(ctx, topic, payload) } // AuthSubscribe implements session.Handler. func (lm *loggingMiddleware) AuthSubscribe(ctx context.Context, topics *[]string) (err error) { - return lm.logAction(ctx, "AuthSubscribe", topics) + defer lm.logAction(ctx, "AuthSubscribe", topics, time.Now(), err) + return lm.svc.AuthSubscribe(ctx, topics) } // Connect implements session.Handler. func (lm *loggingMiddleware) Connect(ctx context.Context) (err error) { - return lm.logAction(ctx, "Connect", nil) + defer lm.logAction(ctx, "Connect", nil, time.Now(), err) + return lm.svc.Connect(ctx) } // Disconnect implements session.Handler. func (lm *loggingMiddleware) Disconnect(ctx context.Context) (err error) { - return lm.logAction(ctx, "Disconnect", nil) + defer lm.logAction(ctx, "Disconnect", nil, time.Now(), err) + return lm.svc.Disconnect(ctx) } // Publish logs the publish request. It logs the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Publish(ctx context.Context, topic *string, payload *[]byte) (err error) { - return lm.logAction(ctx, "Publish", &[]string{*topic}) + defer lm.logAction(ctx, "Publish", &[]string{*topic}, time.Now(), err) + return lm.svc.Publish(ctx, topic, payload) } // Subscribe implements session.Handler. func (lm *loggingMiddleware) Subscribe(ctx context.Context, topics *[]string) (err error) { - return lm.logAction(ctx, "Subscribe", topics) + defer lm.logAction(ctx, "Subscribe", topics, time.Now(), err) + return lm.svc.Subscribe(ctx, topics) } // Unsubscribe implements session.Handler. func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, topics *[]string) (err error) { - return lm.logAction(ctx, "Unsubscribe", topics) + defer lm.logAction(ctx, "Unsubscribe", topics, time.Now(), err) + return lm.svc.Unsubscribe(ctx, topics) } // LoggingMiddleware adds logging facilities to the adapter. @@ -66,21 +74,17 @@ func LoggingMiddleware(svc session.Handler, logger *slog.Logger) session.Handler return &loggingMiddleware{logger, svc} } -func (lm *loggingMiddleware) logAction(ctx context.Context, action string, topics *[]string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - } - if topics != nil { - args = append(args, slog.Any("topics", *topics)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn(action+"() failed to complete successfully", args...) - return - } - lm.logger.Info(action+"() completed successfully", args...) - }(time.Now()) - - return nil +func (lm *loggingMiddleware) logAction(ctx context.Context, action string, topics *[]string, t time.Time, err error) { + args := []any{ + slog.String("duration", time.Since(t).String()), + } + if topics != nil { + args = append(args, slog.Any("topics", *topics)) + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn(action+" failed to complete successfully", args...) + return + } + lm.logger.Info(action+" completed successfully", args...) } From c74c86dd96f77ddef8cbd57f2ef95d6b28d7aed4 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:17:00 +0530 Subject: [PATCH 34/71] MG-104: Rename/Replace filed owner with domain (#268) Signed-off-by: Arvindh --- api/openapi/things.yml | 84 ++----------- api/openapi/users.yml | 149 +----------------------- cli/things.go | 4 +- cli/users.go | 4 +- internal/groups/api/decode.go | 5 - internal/groups/api/decode_test.go | 50 ++++---- internal/groups/api/endpoint_test.go | 2 +- internal/groups/events/events.go | 16 +-- internal/groups/postgres/groups.go | 40 +++---- internal/groups/postgres/groups_test.go | 50 ++++---- internal/groups/postgres/init.go | 4 +- internal/groups/service.go | 4 +- internal/groups/service_test.go | 6 +- pkg/clients/clients.go | 5 +- pkg/clients/page.go | 2 +- pkg/clients/postgres/clients.go | 58 ++++----- pkg/clients/postgres/clients_test.go | 96 +++------------ pkg/groups/groups.go | 4 +- pkg/groups/page.go | 2 +- pkg/sdk/go/channels.go | 2 +- pkg/sdk/go/channels_test.go | 19 +-- pkg/sdk/go/groups.go | 2 +- pkg/sdk/go/groups_test.go | 23 +--- pkg/sdk/go/setup_test.go | 8 +- pkg/sdk/go/things.go | 2 +- pkg/sdk/go/things_test.go | 30 ----- pkg/sdk/go/users.go | 2 +- pkg/sdk/go/users_test.go | 19 +-- things/api/http/clients.go | 11 -- things/api/http/endpoints.go | 1 - things/api/http/endpoints_test.go | 27 ----- things/api/http/requests.go | 1 - things/events/events.go | 20 ++-- things/mocks/repository.go | 28 ----- things/postgres/clients.go | 8 +- things/postgres/clients_test.go | 74 +++++++++--- things/postgres/init.go | 6 +- things/service.go | 4 +- things/service_test.go | 130 +++++++-------------- users/api/clients.go | 12 +- users/api/endpoint_test.go | 138 ---------------------- users/api/endpoints.go | 1 - users/api/requests.go | 1 - users/events/events.go | 20 ++-- users/events/streams.go | 2 +- users/mocks/repository.go | 28 ----- users/postgres/clients.go | 12 +- users/postgres/clients_test.go | 69 ----------- users/postgres/init.go | 2 +- users/service.go | 6 +- users/service_test.go | 2 - 51 files changed, 302 insertions(+), 993 deletions(-) diff --git a/api/openapi/things.yml b/api/openapi/things.yml index 6460d8f379..4ed91b8cdf 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -79,7 +79,6 @@ paths: - $ref: "#/components/parameters/Status" - $ref: "#/components/parameters/ThingName" - $ref: "#/components/parameters/Tags" - - $ref: "#/components/parameters/Owner" security: - bearerAuth: [] responses: @@ -382,8 +381,7 @@ paths: - Channels summary: Creates new channel description: | - Creates new channel. User identified by the provided access token will - be the channel's owner. + Creates new channel in domain. requestBody: $ref: "#/components/requestBodies/ChannelCreateReq" security: @@ -418,7 +416,6 @@ paths: - $ref: "#/components/parameters/Offset" - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/ChannelName" - - $ref: "#/components/parameters/OwnerId" responses: "200": $ref: "#/components/responses/ChannelPageRes" @@ -880,14 +877,9 @@ components: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 minimum: 8 description: Free-form account secret used for acquiring auth token(s). - owner: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing owner must be exsiting in the databse. metadata: type: object - example: { "domain": "example.com" } + example: { "model": "example" } description: Arbitrary, object-encoded thing's data. status: type: string @@ -914,18 +906,13 @@ components: description: Id of parent channel, it must be existing channel. metadata: type: object - example: { "domain": "example.com" } + example: { "location": "example" } description: Arbitrary, object-encoded channels's data. status: type: string description: Channel Status format: string example: enabled - owner_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Channel owner ID must be exsiting in the databse. required: - name @@ -1039,11 +1026,11 @@ components: type: string example: ["tag1", "tag2"] description: Thing tags. - owner: + domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing owner identifier. + description: ID of the domain to which thing belongs. credentials: type: object properties: @@ -1057,7 +1044,7 @@ components: description: Thing secret password. metadata: type: object - example: { "domain": "example.com" } + example: { "model": "example" } description: Arbitrary, object-encoded thing's data. status: type: string @@ -1096,11 +1083,11 @@ components: type: string example: ["tag1", "tag2"] description: Thing tags. - owner: + domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing owner identifier. + description: ID of the domain to which thing belongs. credentials: type: object properties: @@ -1114,7 +1101,7 @@ components: description: Thing secret password. metadata: type: object - example: { "domain": "example.com" } + example: { "model": "example" } description: Arbitrary, object-encoded thing's data. status: type: string @@ -1146,11 +1133,11 @@ components: type: string example: channelName description: Free-form channel name. Channel name is unique on the given hierarchy level. - owner_id: + domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Channel owner identifier of thing that created the channel.. + description: ID of the domain to which the group belongs. parent_id: type: string format: uuid @@ -1342,16 +1329,6 @@ components: required: - secret - ThingOwner: - type: object - properties: - owner: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing owner for example email address. - required: - - owner - ChannelUpdate: type: object properties: @@ -1468,35 +1445,6 @@ components: required: false example: "thingName" - ThingIdentity: - name: identity - description: Thing's identity. - in: query - schema: - type: string - required: false - example: "admin@example.com" - - Owner: - name: owner_id - description: Thing's owner. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - ThingOwner: - name: owner - description: Unique owner identifier for a thing. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Status: name: status description: Thing account status. @@ -1577,16 +1525,6 @@ components: type: boolean default: false - OwnerId: - name: ownerId - description: Unique owner identifier for a channel. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Metadata: name: metadata description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 4868c08e96..e4dd97b3b6 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -73,8 +73,6 @@ paths: - $ref: "#/components/parameters/UserName" - $ref: "#/components/parameters/UserIdentity" - $ref: "#/components/parameters/Tags" - - $ref: "#/components/parameters/Owner" - - $ref: "#/components/parameters/UserVisibility" security: - bearerAuth: [] responses: @@ -376,7 +374,6 @@ paths: - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/GroupName" - $ref: "#/components/parameters/ParentID" - - $ref: "#/components/parameters/OwnerID" responses: "200": $ref: "#/components/responses/MembersPageRes" @@ -412,7 +409,6 @@ paths: - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/ChannelName" - $ref: "#/components/parameters/ParentID" - - $ref: "#/components/parameters/OwnerID" responses: "200": $ref: "#/components/responses/MembersPageRes" @@ -512,7 +508,6 @@ paths: - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/GroupName" - $ref: "#/components/parameters/ParentID" - - $ref: "#/components/parameters/OwnerID" responses: "200": $ref: "#/components/responses/GroupPageRes" @@ -613,7 +608,6 @@ paths: - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/GroupName" - $ref: "#/components/parameters/ParentID" - - $ref: "#/components/parameters/OwnerID" responses: "200": $ref: "#/components/responses/GroupPageRes" @@ -647,7 +641,6 @@ paths: - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/GroupName" - $ref: "#/components/parameters/ParentID" - - $ref: "#/components/parameters/OwnerID" responses: "200": $ref: "#/components/responses/GroupPageRes" @@ -890,11 +883,6 @@ components: example: password minimum: 8 description: Free-form account secret used for acquiring auth token(s). - owner: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User owner must be exsiting in the databse. metadata: type: object example: { "domain": "example.com" } @@ -931,11 +919,6 @@ components: description: Group Status format: string example: enabled - owner_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Group owner ID must be exsiting in the databse. required: - name @@ -958,11 +941,6 @@ components: type: string example: ["tag1", "tag2"] description: User tags. - owner: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User owner identifier. credentials: type: object properties: @@ -972,7 +950,7 @@ components: description: User Identity for example email address. metadata: type: object - example: { "domain": "example.com" } + example: { "address": "example" } description: Arbitrary, object-encoded user's data. status: type: string @@ -1004,11 +982,11 @@ components: type: string example: groupName description: Free-form group name. Group name is unique on the given hierarchy level. - owner_id: + domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Group owner identifier of user that created the group.. + description: ID of the domain to which the group belongs.. parent_id: type: string format: uuid @@ -1050,58 +1028,6 @@ components: xml: name: group - Memberships: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Unique group identifier generated by the service. - name: - type: string - example: groupName - description: Free-form group name. Group name is unique on the given hierarchy level. - owner_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Group owner identifier of user that created the group.. - parent_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Group parent identifier. - description: - type: string - example: long group description - description: Group description, free form text. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded groups's data. - path: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879.bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Hierarchy path, concatenated ids of group ancestors. - level: - type: integer - description: Level in hierarchy, distance from the root group. - format: int32 - example: 2 - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the group was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the group was created. - xml: - name: memberships - Members: type: object properties: @@ -1121,11 +1047,6 @@ components: type: string example: ["computations", "datasets"] description: User tags. - owner: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User owner identifier. credentials: type: object properties: @@ -1209,31 +1130,6 @@ components: - total - level - MembershipsPage: - type: object - properties: - memberships: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Memberships" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - memberships - - total - - level - MembersPage: type: object properties: @@ -1320,7 +1216,7 @@ components: example: user description: User role example. required: - - owner + - role GroupUpdate: type: object @@ -1476,26 +1372,6 @@ components: required: false example: "admin@example.com" - Owner: - name: owner_id - description: User's owner. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - UserOwner: - name: owner - description: Unique owner identifier for a user. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Status: name: status description: User account status. @@ -1605,16 +1481,6 @@ components: type: boolean default: false - OwnerID: - name: ownerID - description: Unique owner identifier for a group. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Metadata: name: metadata description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. @@ -1816,13 +1682,6 @@ components: schema: $ref: "#/components/schemas/UsersPage" - MembershipsPageRes: - description: Memberships associated with the user. - content: - application/json: - schema: - $ref: "#/components/schemas/MembershipsPage" - GroupCreateRes: description: Registered new group. headers: diff --git a/cli/things.go b/cli/things.go index 0faf5e6d54..f7c1f4b4bc 100644 --- a/cli/things.go +++ b/cli/things.go @@ -121,9 +121,9 @@ var cmdThings = []cobra.Command{ }, }, { - Use: "update [ | tags | secret | owner ] ", + Use: "update [ | tags | secret ] ", Short: "Update thing", - Long: "Updates thing with provided id, name and metadata, or updates thing tags, secret or owner\n" + + Long: "Updates thing with provided id, name and metadata, or updates thing tags, secret\n" + "Usage:\n" + "\tmagistrala-cli things update '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN\n" + "\tmagistrala-cli things update tags '{\"tag1\":\"value1\", \"tag2\":\"value2\"}' $USERTOKEN\n" + diff --git a/cli/users.go b/cli/users.go index 3439ef00e7..3857f5f28a 100644 --- a/cli/users.go +++ b/cli/users.go @@ -142,9 +142,9 @@ var cmdUsers = []cobra.Command{ }, }, { - Use: "update [ | tags | identity | owner ] ", + Use: "update [ | tags | identity ] ", Short: "Update user", - Long: "Updates either user name and metadata or user tags or user identity or user owner\n" + + Long: "Updates either user name and metadata or user tags or user identity\n" + "Usage:\n" + "\tmagistrala-cli users update '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN - updates user name and metadata\n" + "\tmagistrala-cli users update tags '[\"tag1\", \"tag2\"]' $USERTOKEN - updates user tags\n" + diff --git a/internal/groups/api/decode.go b/internal/groups/api/decode.go index 97377ebd6a..e4b618a031 100644 --- a/internal/groups/api/decode.go +++ b/internal/groups/api/decode.go @@ -268,10 +268,6 @@ func decodePageMeta(r *http.Request) (mggroups.PageMeta, error) { if err != nil { return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) } - ownerID, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } name, err := apiutil.ReadStringQuery(r, api.NameKey, "") if err != nil { return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) @@ -285,7 +281,6 @@ func decodePageMeta(r *http.Request) (mggroups.PageMeta, error) { Offset: offset, Limit: limit, Name: name, - OwnerID: ownerID, Metadata: meta, Status: st, } diff --git a/internal/groups/api/decode_test.go b/internal/groups/api/decode_test.go index b8ec6e7cd8..3e2e392b3a 100644 --- a/internal/groups/api/decode_test.go +++ b/internal/groups/api/decode_test.go @@ -44,18 +44,17 @@ func TestDecodeListGroupsRequest(t *testing.T) { }, { desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", header: map[string][]string{ "Authorization": {"Bearer 123"}, }, resp: listGroupsReq{ Page: groups.Page{ PageMeta: groups.PageMeta{ - Status: clients.EnabledStatus, - Offset: 10, - Limit: 10, - OwnerID: "random", - Name: "random", + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", Metadata: clients.Metadata{ "test": "test", }, @@ -161,18 +160,17 @@ func TestDecodeListParentsRequest(t *testing.T) { }, { desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", header: map[string][]string{ "Authorization": {"Bearer 123"}, }, resp: listGroupsReq{ Page: groups.Page{ PageMeta: groups.PageMeta{ - Status: clients.EnabledStatus, - Offset: 10, - Limit: 10, - OwnerID: "random", - Name: "random", + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", Metadata: clients.Metadata{ "test": "test", }, @@ -258,18 +256,17 @@ func TestDecodeListChildrenRequest(t *testing.T) { }, { desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", header: map[string][]string{ "Authorization": {"Bearer 123"}, }, resp: listGroupsReq{ Page: groups.Page{ PageMeta: groups.PageMeta{ - Status: clients.EnabledStatus, - Offset: 10, - Limit: 10, - OwnerID: "random", - Name: "random", + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", Metadata: clients.Metadata{ "test": "test", }, @@ -405,13 +402,12 @@ func TestDecodePageMeta(t *testing.T) { }, { desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&owner_id=random&name=random&metadata={\"test\":\"test\"}", + url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}", resp: groups.PageMeta{ - Status: clients.EnabledStatus, - Offset: 10, - Limit: 10, - OwnerID: "random", - Name: "random", + Status: clients.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", Metadata: clients.Metadata{ "test": "test", }, @@ -442,12 +438,6 @@ func TestDecodePageMeta(t *testing.T) { resp: groups.PageMeta{}, err: apiutil.ErrValidation, }, - { - desc: "valid request with invalid owner_id", - url: "http://localhost:8080?owner_id=random&owner_id=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, { desc: "valid request with invalid name", url: "http://localhost:8080?name=random&name=random", diff --git a/internal/groups/api/endpoint_test.go b/internal/groups/api/endpoint_test.go index 6444d16f97..04d4612e9a 100644 --- a/internal/groups/api/endpoint_test.go +++ b/internal/groups/api/endpoint_test.go @@ -24,7 +24,7 @@ var validGroupResp = groups.Group{ ID: testsutil.GenerateUUID(&testing.T{}), Name: valid, Description: valid, - Owner: testsutil.GenerateUUID(&testing.T{}), + Domain: testsutil.GenerateUUID(&testing.T{}), Parent: testsutil.GenerateUUID(&testing.T{}), Metadata: clients.Metadata{ "name": "test", diff --git a/internal/groups/events/events.go b/internal/groups/events/events.go index 118a4fd88a..880c7eddf3 100644 --- a/internal/groups/events/events.go +++ b/internal/groups/events/events.go @@ -46,8 +46,8 @@ func (cge createGroupEvent) Encode() (map[string]interface{}, error) { "created_at": cge.CreatedAt, } - if cge.Owner != "" { - val["owner"] = cge.Owner + if cge.Domain != "" { + val["domain"] = cge.Domain } if cge.Parent != "" { val["parent"] = cge.Parent @@ -87,8 +87,8 @@ func (uge updateGroupEvent) Encode() (map[string]interface{}, error) { if uge.ID != "" { val["id"] = uge.ID } - if uge.Owner != "" { - val["owner"] = uge.Owner + if uge.Domain != "" { + val["domain"] = uge.Domain } if uge.Parent != "" { val["parent"] = uge.Parent @@ -144,8 +144,8 @@ func (vge viewGroupEvent) Encode() (map[string]interface{}, error) { "id": vge.ID, } - if vge.Owner != "" { - val["owner"] = vge.Owner + if vge.Domain != "" { + val["domain"] = vge.Domain } if vge.Parent != "" { val["parent"] = vge.Parent @@ -207,8 +207,8 @@ func (lge listGroupEvent) Encode() (map[string]interface{}, error) { if lge.Name != "" { val["name"] = lge.Name } - if lge.OwnerID != "" { - val["owner_id"] = lge.OwnerID + if lge.DomainID != "" { + val["domain_id"] = lge.DomainID } if lge.Tag != "" { val["tag"] = lge.Tag diff --git a/internal/groups/postgres/groups.go b/internal/groups/postgres/groups.go index 4077bf5935..b653fb701a 100644 --- a/internal/groups/postgres/groups.go +++ b/internal/groups/postgres/groups.go @@ -34,9 +34,9 @@ func New(db postgres.Database) mggroups.Repository { } func (repo groupRepository) Save(ctx context.Context, g mggroups.Group) (mggroups.Group, error) { - q := `INSERT INTO groups (name, description, id, owner_id, parent_id, metadata, created_at, status) - VALUES (:name, :description, :id, :owner_id, :parent_id, :metadata, :created_at, :status) - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status;` + q := `INSERT INTO groups (name, description, id, domain_id, parent_id, metadata, created_at, status) + VALUES (:name, :description, :id, :domain_id, :parent_id, :metadata, :created_at, :status) + RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status;` dbg, err := toDBGroup(g) if err != nil { return mggroups.Group{}, err @@ -74,7 +74,7 @@ func (repo groupRepository) Update(ctx context.Context, g mggroups.Group) (mggro g.Status = mgclients.EnabledStatus q := fmt.Sprintf(`UPDATE groups SET %s updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status`, upq) + RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status`, upq) dbu, err := toDBGroup(g) if err != nil { @@ -99,7 +99,7 @@ func (repo groupRepository) Update(ctx context.Context, g mggroups.Group) (mggro func (repo groupRepository) ChangeStatus(ctx context.Context, group mggroups.Group) (mggroups.Group, error) { qc := `UPDATE groups SET status = :status, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status` + RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status` dbg, err := toDBGroup(group) if err != nil { @@ -122,7 +122,7 @@ func (repo groupRepository) ChangeStatus(ctx context.Context, group mggroups.Gro } func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (mggroups.Group, error) { - q := `SELECT id, name, owner_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups + q := `SELECT id, name, domain_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups WHERE id = :id` dbg := dbGroup{ @@ -153,7 +153,7 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( q = buildHierachy(gm) } if gm.ID == "" { - q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + q = `SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g` } q = fmt.Sprintf("%s %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;", q, query) @@ -192,7 +192,7 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, ids ...string) (mggroups.Page, error) { var q string - if (len(ids) <= 0) && (gm.PageMeta.OwnerID == "") { + if (len(ids) <= 0) && (gm.PageMeta.DomainID == "") { return mggroups.Page{PageMeta: mggroups.PageMeta{Offset: gm.Offset, Limit: gm.Limit}}, nil } query := buildQuery(gm, ids...) @@ -201,7 +201,7 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, q = buildHierachy(gm) } if gm.ID == "" { - q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + q = `SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g` } q = fmt.Sprintf("%s %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;", q, query) @@ -310,15 +310,15 @@ func buildHierachy(gm mggroups.Page) string { switch { case gm.Direction >= 0: // ancestors query = `WITH RECURSIVE groups_cte as ( - SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level from groups WHERE id = :id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level - 1 from groups x + SELECT id, COALESCE(parent_id, '') AS parent_id, domain_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level from groups WHERE id = :id + UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.domain_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level - 1 from groups x INNER JOIN groups_cte a ON a.parent_id = x.id ) SELECT * FROM groups_cte g` case gm.Direction < 0: // descendants query = `WITH RECURSIVE groups_cte as ( - SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level, CONCAT('', '', id) as path from groups WHERE id = :id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level + 1, CONCAT(path, '.', x.id) as path from groups x + SELECT id, COALESCE(parent_id, '') AS parent_id, domain_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level, CONCAT('', '', id) as path from groups WHERE id = :id + UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.domain_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level + 1, CONCAT(path, '.', x.id) as path from groups x INNER JOIN groups_cte d ON d.id = x.parent_id ) SELECT * FROM groups_cte g` } @@ -337,8 +337,8 @@ func buildQuery(gm mggroups.Page, ids ...string) string { if gm.Status != mgclients.AllStatus { queries = append(queries, "g.status = :status") } - if gm.OwnerID != "" { - queries = append(queries, "g.owner_id = :owner_id") + if gm.DomainID != "" { + queries = append(queries, "g.domain_id = :domain_id") } if len(gm.Metadata) > 0 { queries = append(queries, "g.metadata @> :metadata") @@ -353,7 +353,7 @@ func buildQuery(gm mggroups.Page, ids ...string) string { type dbGroup struct { ID string `db:"id"` ParentID *string `db:"parent_id,omitempty"` - OwnerID string `db:"owner_id,omitempty"` + DomainID string `db:"domain_id,omitempty"` Name string `db:"name"` Description string `db:"description,omitempty"` Level int `db:"level"` @@ -390,7 +390,7 @@ func toDBGroup(g mggroups.Group) (dbGroup, error) { ID: g.ID, Name: g.Name, ParentID: parentID, - OwnerID: g.Owner, + DomainID: g.Domain, Description: g.Description, Metadata: data, Path: g.Path, @@ -425,7 +425,7 @@ func toGroup(g dbGroup) (mggroups.Group, error) { ID: g.ID, Name: g.Name, Parent: parentID, - Owner: g.OwnerID, + Domain: g.DomainID, Description: g.Description, Metadata: metadata, Level: g.Level, @@ -460,7 +460,7 @@ func toDBGroupPage(pm mggroups.Page) (dbGroupPage, error) { Offset: pm.Offset, Limit: pm.Limit, ParentID: pm.ID, - OwnerID: pm.OwnerID, + DomainID: pm.DomainID, Status: pm.Status, }, nil } @@ -470,7 +470,7 @@ type dbGroupPage struct { ID string `db:"id"` Name string `db:"name"` ParentID string `db:"parent_id"` - OwnerID string `db:"owner_id"` + DomainID string `db:"domain_id"` Metadata []byte `db:"metadata"` Path string `db:"path"` Level uint64 `db:"level"` diff --git a/internal/groups/postgres/groups_test.go b/internal/groups/postgres/groups_test.go index e29745cfe0..7ade7e219e 100644 --- a/internal/groups/postgres/groups_test.go +++ b/internal/groups/postgres/groups_test.go @@ -26,7 +26,7 @@ var ( invalidID = strings.Repeat("a", 37) validGroup = mggroups.Group{ ID: testsutil.GenerateUUID(&testing.T{}), - Owner: testsutil.GenerateUUID(&testing.T{}), + Domain: testsutil.GenerateUUID(&testing.T{}), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, @@ -62,7 +62,7 @@ func TestSave(t *testing.T) { desc: "add group with invalid ID", group: mggroups.Group{ ID: invalidID, - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, @@ -72,10 +72,10 @@ func TestSave(t *testing.T) { err: repoerr.ErrMalformedEntity, }, { - desc: "add group with invalid owner", + desc: "add group with invalid domain", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: invalidID, + Domain: invalidID, Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, @@ -101,7 +101,7 @@ func TestSave(t *testing.T) { desc: "add group with invalid name", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Name: strings.Repeat("a", 1025), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, @@ -114,7 +114,7 @@ func TestSave(t *testing.T) { desc: "add group with invalid description", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 1025), Metadata: map[string]interface{}{"key": "value"}, @@ -127,7 +127,7 @@ func TestSave(t *testing.T) { desc: "add group with invalid metadata", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{ @@ -139,7 +139,7 @@ func TestSave(t *testing.T) { err: repoerr.ErrMalformedEntity, }, { - desc: "add group with empty owner", + desc: "add group with empty domain", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), Name: namegen.Generate(), @@ -154,7 +154,7 @@ func TestSave(t *testing.T) { desc: "add group with empty name", group: mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), @@ -394,7 +394,7 @@ func TestRetrieveAll(t *testing.T) { name := namegen.Generate() group := mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Parent: parentID, Name: name, Description: strings.Repeat("a", 64), @@ -573,12 +573,12 @@ func TestRetrieveAll(t *testing.T) { err: nil, }, { - desc: "retrieve groups with owner", + desc: "retrieve groups with domain", page: mggroups.Page{ PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - OwnerID: items[0].Owner, + Offset: 0, + Limit: 10, + DomainID: items[0].Domain, }, }, response: mggroups.Page{ @@ -706,7 +706,7 @@ func TestRetrieveByIDs(t *testing.T) { name := namegen.Generate() group := mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Parent: parentID, Name: name, Description: strings.Repeat("a", 64), @@ -765,12 +765,12 @@ func TestRetrieveByIDs(t *testing.T) { err: nil, }, { - desc: "retrieve groups with empty ids but with owner", + desc: "retrieve groups with empty ids but with domain", page: mggroups.Page{ PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - OwnerID: items[0].Owner, + Offset: 0, + Limit: 10, + DomainID: items[0].Domain, }, }, ids: []string{}, @@ -881,12 +881,12 @@ func TestRetrieveByIDs(t *testing.T) { err: nil, }, { - desc: "retrieve groups with owner", + desc: "retrieve groups with domain", page: mggroups.Page{ PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - OwnerID: items[0].Owner, + Offset: 0, + Limit: 10, + DomainID: items[0].Domain, }, }, ids: getIDs(items[0:20]), @@ -1063,7 +1063,7 @@ func TestAssignParentGroup(t *testing.T) { name := namegen.Generate() group := mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Parent: parentID, Name: name, Description: strings.Repeat("a", 64), @@ -1141,7 +1141,7 @@ func TestUnassignParentGroup(t *testing.T) { name := namegen.Generate() group := mggroups.Group{ ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Parent: parentID, Name: name, Description: strings.Repeat("a", 64), diff --git a/internal/groups/postgres/init.go b/internal/groups/postgres/init.go index 0cff683e51..0b799c46cb 100644 --- a/internal/groups/postgres/init.go +++ b/internal/groups/postgres/init.go @@ -17,7 +17,7 @@ func Migration() *migrate.MemoryMigrationSource { `CREATE TABLE IF NOT EXISTS groups ( id VARCHAR(36) PRIMARY KEY, parent_id VARCHAR(36), - owner_id VARCHAR(36) NOT NULL, + domain_id VARCHAR(36) NOT NULL, name VARCHAR(1024) NOT NULL, description VARCHAR(1024), metadata JSONB, @@ -25,7 +25,7 @@ func Migration() *migrate.MemoryMigrationSource { updated_at TIMESTAMP, updated_by VARCHAR(254), status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), - UNIQUE (owner_id, name), + UNIQUE (domain_id, name), FOREIGN KEY (parent_id) REFERENCES groups (id) ON DELETE SET NULL )`, }, diff --git a/internal/groups/service.go b/internal/groups/service.go index 44297b73ba..8dd6d08f04 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -60,7 +60,7 @@ func (svc service) CreateGroup(ctx context.Context, token, kind string, g groups g.ID = groupID g.CreatedAt = time.Now() - g.Owner = res.GetDomainId() + g.Domain = res.GetDomainId() if g.Parent != "" { _, err := svc.authorizeToken(ctx, auth.UserType, token, auth.EditPermission, auth.GroupType, g.Parent) if err != nil { @@ -210,7 +210,7 @@ func (svc service) ListGroups(ctx context.Context, token, memberKind, memberID s default: switch svc.checkSuperAdmin(ctx, res.GetUserId()) { case nil: - gm.PageMeta.OwnerID = res.GetDomainId() + gm.PageMeta.DomainID = res.GetDomainId() default: // If domain is disabled , then this authorization will fail for all non-admin domain users if _, err := svc.authorizeKind(ctx, "", auth.UserType, auth.UsersKind, res.GetId(), auth.MembershipPermission, auth.DomainType, res.GetDomainId()); err != nil { diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index c6a3ecf70f..d28b92ef11 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -85,7 +85,7 @@ func TestCreateGroup(t *testing.T) { repoResp: mggroups.Group{ ID: testsutil.GenerateUUID(t), CreatedAt: time.Now(), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{ Added: true, @@ -179,7 +179,7 @@ func TestCreateGroup(t *testing.T) { repoResp: mggroups.Group{ ID: testsutil.GenerateUUID(t), CreatedAt: time.Now(), - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Parent: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{ @@ -283,7 +283,7 @@ func TestCreateGroup(t *testing.T) { if err == nil { assert.NotEmpty(t, got.ID) assert.NotEmpty(t, got.CreatedAt) - assert.NotEmpty(t, got.Owner) + assert.NotEmpty(t, got.Domain) assert.WithinDuration(t, time.Now(), got.CreatedAt, 2*time.Second) ok := repocall3.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go index 2fed8e67ec..8d16fe9130 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -42,7 +42,7 @@ type Client struct { ID string `json:"id"` Name string `json:"name,omitempty"` Tags []string `json:"tags,omitempty"` - Owner string `json:"owner,omitempty"` // nullable + Domain string `json:"domain,omitempty"` Credentials Credentials `json:"credentials,omitempty"` Metadata Metadata `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` @@ -96,9 +96,6 @@ type Repository interface { // UpdateSecret updates secret for client with given identity. UpdateSecret(ctx context.Context, client Client) (Client, error) - // UpdateOwner updates owner for client with given id. - UpdateOwner(ctx context.Context, client Client) (Client, error) - // UpdateRole updates role for client with given id. UpdateRole(ctx context.Context, client Client) (Client, error) diff --git a/pkg/clients/page.go b/pkg/clients/page.go index d0321f0a88..378049fa30 100644 --- a/pkg/clients/page.go +++ b/pkg/clients/page.go @@ -12,7 +12,7 @@ type Page struct { Order string `json:"order,omitempty"` Dir string `json:"dir,omitempty"` Metadata Metadata `json:"metadata,omitempty"` - Owner string `json:"owner,omitempty"` + Domain string `json:"domain,omitempty"` Tag string `json:"tag,omitempty"` Permission string `json:"permission,omitempty"` Status Status `json:"status,omitempty"` diff --git a/pkg/clients/postgres/clients.go b/pkg/clients/postgres/clients.go index 59144c0e10..6ca5255d1e 100644 --- a/pkg/clients/postgres/clients.go +++ b/pkg/clients/postgres/clients.go @@ -39,7 +39,7 @@ func (repo *Repository) Update(ctx context.Context, client clients.Client) (clie q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`, + RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by`, upq) client.Status = clients.EnabledStatus return repo.update(ctx, client, q) @@ -48,7 +48,7 @@ func (repo *Repository) Update(ctx context.Context, client clients.Client) (clie func (repo *Repository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -56,7 +56,7 @@ func (repo *Repository) UpdateTags(ctx context.Context, client clients.Client) ( func (repo *Repository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -64,15 +64,7 @@ func (repo *Repository) UpdateIdentity(ctx context.Context, client clients.Clien func (repo *Repository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` - client.Status = clients.EnabledStatus - return repo.update(ctx, client, q) -} - -func (repo *Repository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { - q := `UPDATE clients SET owner_id = :owner_id, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -80,7 +72,7 @@ func (repo *Repository) UpdateOwner(ctx context.Context, client clients.Client) func (repo *Repository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET role = :role, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, role, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, role, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -88,13 +80,13 @@ func (repo *Repository) UpdateRole(ctx context.Context, client clients.Client) ( func (repo *Repository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET status = :status, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` return repo.update(ctx, client, q) } func (repo *Repository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { - q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status + q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients WHERE id = :id` dbc := DBClient{ @@ -120,7 +112,7 @@ func (repo *Repository) RetrieveByID(ctx context.Context, id string) (clients.Cl } func (repo *Repository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) { - q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status + q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients WHERE identity = :identity AND status = :status` dbc := DBClient{ @@ -152,7 +144,7 @@ func (repo *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clien return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status, + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, c.status, c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) dbPage, err := ToDBClientsPage(pm) @@ -248,7 +240,7 @@ func (repo *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Pag } func (repo *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { - if (len(pm.IDs) <= 0) && (pm.Owner == "") { + if (len(pm.IDs) == 0) && (pm.Domain == "") { return clients.ClientsPage{ Page: clients.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit}, }, nil @@ -258,7 +250,7 @@ func (repo *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) ( return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status, + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, c.status, c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) dbPage, err := ToDBClientsPage(pm) @@ -333,7 +325,7 @@ type DBClient struct { Name string `db:"name,omitempty"` Tags pgtype.TextArray `db:"tags,omitempty"` Identity string `db:"identity"` - Owner *string `db:"owner_id,omitempty"` // nullable + Domain string `db:"domain_id"` Secret string `db:"secret"` Metadata []byte `db:"metadata,omitempty"` CreatedAt time.Time `db:"created_at,omitempty"` @@ -357,10 +349,6 @@ func ToDBClient(c clients.Client) (DBClient, error) { if err := tags.Set(c.Tags); err != nil { return DBClient{}, err } - var owner *string - if c.Owner != "" { - owner = &c.Owner - } var updatedBy *string if c.UpdatedBy != "" { updatedBy = &c.UpdatedBy @@ -374,7 +362,7 @@ func ToDBClient(c clients.Client) (DBClient, error) { ID: c.ID, Name: c.Name, Tags: tags, - Owner: owner, + Domain: c.Domain, Identity: c.Credentials.Identity, Secret: c.Credentials.Secret, Metadata: data, @@ -397,10 +385,6 @@ func ToClient(c DBClient) (clients.Client, error) { for _, e := range c.Tags.Elements { tags = append(tags, e.String) } - var owner string - if c.Owner != nil { - owner = *c.Owner - } var updatedBy string if c.UpdatedBy != nil { updatedBy = *c.UpdatedBy @@ -411,10 +395,10 @@ func ToClient(c DBClient) (clients.Client, error) { } cli := clients.Client{ - ID: c.ID, - Name: c.Name, - Tags: tags, - Owner: owner, + ID: c.ID, + Name: c.Name, + Tags: tags, + Domain: c.Domain, Credentials: clients.Credentials{ Identity: c.Identity, Secret: c.Secret, @@ -440,7 +424,7 @@ func ToDBClientsPage(pm clients.Page) (dbClientsPage, error) { Name: pm.Name, Identity: pm.Identity, Metadata: data, - Owner: pm.Owner, + Domain: pm.Domain, Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit, @@ -455,7 +439,7 @@ type dbClientsPage struct { Limit uint64 `db:"limit"` Offset uint64 `db:"offset"` Name string `db:"name"` - Owner string `db:"owner_id"` + Domain string `db:"domain_id"` Identity string `db:"identity"` Metadata []byte `db:"metadata"` Tag string `db:"tag"` @@ -489,8 +473,8 @@ func PageQuery(pm clients.Page) (string, error) { if pm.Status != clients.AllStatus { query = append(query, "c.status = :status") } - if pm.Owner != "" { - query = append(query, "c.owner_id = :owner_id") + if pm.Domain != "" { + query = append(query, "c.domain_id = :domain_id") } if pm.Role != clients.AllRole { diff --git a/pkg/clients/postgres/clients_test.go b/pkg/clients/postgres/clients_test.go index 4ec8ab86b5..178e628ac8 100644 --- a/pkg/clients/postgres/clients_test.go +++ b/pkg/clients/postgres/clients_test.go @@ -143,9 +143,9 @@ func TestRetrieveAll(t *testing.T) { disabledClients := []mgclients.Client{} for i := uint64(0); i < nClients; i++ { client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), - Name: namegen.Generate(), + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + Name: namegen.Generate(), Credentials: mgclients.Credentials{ Identity: namegen.Generate() + emailSuffix, Secret: password, @@ -420,11 +420,11 @@ func TestRetrieveAll(t *testing.T) { }, }, { - desc: "with owner", + desc: "with domain", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Owner: expectedClients[0].Owner, + Domain: expectedClients[0].Domain, Status: mgclients.AllStatus, Role: mgclients.AllRole, }, @@ -438,11 +438,11 @@ func TestRetrieveAll(t *testing.T) { }, }, { - desc: "with wrong owner", + desc: "with wrong domain", pm: mgclients.Page{ Offset: 0, Limit: nClients, - Owner: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Status: mgclients.AllStatus, Role: mgclients.AllRole, }, @@ -635,7 +635,7 @@ func TestRetrieveAll(t *testing.T) { Name: expectedClients[0].Name, Tag: expectedClients[0].Tags[0], Identity: expectedClients[0].Credentials.Identity, - Owner: expectedClients[0].Owner, + Domain: expectedClients[0].Domain, Status: mgclients.AllStatus, Role: mgclients.AllRole, }, @@ -676,9 +676,9 @@ func TestRetrieveByIDs(t *testing.T) { for i := 0; i < num; i++ { name := namegen.Generate() client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), - Name: name, + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + Name: name, Credentials: mgclients.Credentials{ Identity: name + emailSuffix, Secret: password, @@ -737,11 +737,11 @@ func TestRetrieveByIDs(t *testing.T) { err: nil, }, { - desc: "with empty ids but with owner", + desc: "with empty ids but with domain id", page: mgclients.Page{ Offset: 0, Limit: 10, - Owner: items[0].Owner, + Domain: items[0].Domain, IDs: []string{}, }, response: mgclients.ClientsPage{ @@ -856,11 +856,11 @@ func TestRetrieveByIDs(t *testing.T) { err: nil, }, { - desc: "with owner", + desc: "with domain id", page: mgclients.Page{ Offset: 0, Limit: 10, - Owner: items[0].Owner, + Domain: items[0].Domain, IDs: getIDs(items[0:20]), }, response: mgclients.ClientsPage{ @@ -1676,66 +1676,6 @@ func TestUpdateIdentity(t *testing.T) { } } -func TestUpdateOwner(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM clients") - require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) - }) - repo := &postgres.Repository{database} - - client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo) - client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo) - - cases := []struct { - desc string - client mgclients.Client - err error - }{ - { - desc: "for enabled client", - client: mgclients.Client{ - ID: client1.ID, - Owner: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "for disabled client", - client: mgclients.Client{ - ID: client2.ID, - Owner: testsutil.GenerateUUID(t), - }, - err: errors.ErrNotFound, - }, - { - desc: "for invalid client", - client: mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: testsutil.GenerateUUID(t), - }, - err: errors.ErrNotFound, - }, - { - desc: "for empty client", - client: mgclients.Client{}, - err: errors.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) - c.client.UpdatedBy = testsutil.GenerateUUID(t) - expected, err := repo.UpdateOwner(context.Background(), c.client) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - if err == nil { - assert.Equal(t, c.client.Owner, expected.Owner) - assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt) - assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy) - } - }) - } -} - func TestChangeStatus(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") @@ -1900,9 +1840,9 @@ func generateClient(t *testing.T, status mgclients.Status, role mgclients.Role, } func save(ctx context.Context, repo *postgres.Repository, c mgclients.Client) (mgclients.Client, error) { - q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, status, role) - VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :status, :role) - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at` + q := `INSERT INTO clients (id, name, tags, domain_id, identity, secret, metadata, created_at, status, role) + VALUES (:id, :name, :tags, :domain_id, :identity, :secret, :metadata, :created_at, :status, :role) + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at` dbc, err := pgclients.ToDBClient(c) if err != nil { return mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) diff --git a/pkg/groups/groups.go b/pkg/groups/groups.go index aa24ad5d63..ef3f503d11 100644 --- a/pkg/groups/groups.go +++ b/pkg/groups/groups.go @@ -16,10 +16,10 @@ const MaxLevel = uint64(5) // Group represents the group of Clients. // Indicates a level in tree hierarchy. Root node is level 1. // Path in a tree consisting of group IDs -// Paths are unique per owner. +// Paths are unique per domain. type Group struct { ID string `json:"id"` - Owner string `json:"owner_id,omitempty"` + Domain string `json:"domain_id,omitempty"` Parent string `json:"parent_id,omitempty"` Name string `json:"name"` Description string `json:"description,omitempty"` diff --git a/pkg/groups/page.go b/pkg/groups/page.go index e0155aa5da..2f8fc8fddf 100644 --- a/pkg/groups/page.go +++ b/pkg/groups/page.go @@ -11,7 +11,7 @@ type PageMeta struct { Offset uint64 `json:"offset"` Limit uint64 `json:"limit"` Name string `json:"name,omitempty"` - OwnerID string `json:"identity,omitempty"` + DomainID string `json:"domain_id,omitempty"` Tag string `json:"tag,omitempty"` Metadata clients.Metadata `json:"metadata,omitempty"` Status clients.Status `json:"status,omitempty"` diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index dbdd121b9e..c418023616 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -17,7 +17,7 @@ const channelsEndpoint = "channels" // Channel represents magistrala channel. type Channel struct { ID string `json:"id,omitempty"` - OwnerID string `json:"owner_id,omitempty"` + DomainID string `json:"domain_id,omitempty"` ParentID string `json:"parent_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index b4232344f5..a64749028a 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -111,16 +111,6 @@ func TestCreateChannel(t *testing.T) { token: token, err: errors.NewSDKErrorWithStatus(errors.ErrCreateEntity, http.StatusInternalServerError), }, - { - desc: "create channel with invalid owner", - channel: sdk.Channel{ - Name: gName, - OwnerID: wrongID, - Status: mgclients.EnabledStatus.String(), - }, - token: token, - err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), - }, { desc: "create channel with missing name", channel: sdk.Channel{ @@ -133,7 +123,6 @@ func TestCreateChannel(t *testing.T) { desc: "create a channel with every field defined", channel: sdk.Channel{ ID: generateUUID(t), - OwnerID: "owner", ParentID: "parent", Name: "name", Description: description, @@ -194,7 +183,6 @@ func TestListChannels(t *testing.T) { limit uint64 level int name string - ownerID string metadata sdk.Metadata err errors.SDKError response []sdk.Channel @@ -661,7 +649,6 @@ func TestEnableChannel(t *testing.T) { channel := sdk.Channel{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: mgclients.Disabled, @@ -681,7 +668,6 @@ func TestEnableChannel(t *testing.T) { ch := mggroups.Group{ ID: channel.ID, Name: channel.Name, - Owner: channel.OwnerID, CreatedAt: creationTime, UpdatedAt: creationTime, Status: mgclients.DisabledStatus, @@ -714,7 +700,7 @@ func TestDisableChannel(t *testing.T) { channel := sdk.Channel{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), + DomainID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: mgclients.Enabled, @@ -734,7 +720,7 @@ func TestDisableChannel(t *testing.T) { ch := mggroups.Group{ ID: channel.ID, Name: channel.Name, - Owner: channel.OwnerID, + Domain: channel.DomainID, CreatedAt: creationTime, UpdatedAt: creationTime, Status: mgclients.EnabledStatus, @@ -768,7 +754,6 @@ func TestDeleteChannel(t *testing.T) { channel := sdk.Channel{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: mgclients.Enabled, diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index f808e5bf63..cbc6f794be 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -24,7 +24,7 @@ const ( // Paths are unique per owner. type Group struct { ID string `json:"id,omitempty"` - OwnerID string `json:"owner_id,omitempty"` + DomainID string `json:"domain_id,omitempty"` ParentID string `json:"parent_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 9e251cba1a..ca6f49a255 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -96,16 +96,6 @@ func TestCreateGroup(t *testing.T) { }, err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusInternalServerError), }, - { - desc: "create group with invalid owner", - token: token, - group: sdk.Group{ - Name: gName, - OwnerID: wrongID, - Status: clients.EnabledStatus.String(), - }, - err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), - }, { desc: "create group with missing name", token: token, @@ -119,7 +109,6 @@ func TestCreateGroup(t *testing.T) { token: token, group: sdk.Group{ ID: generateUUID(t), - OwnerID: "owner", ParentID: "parent", Name: "name", Description: description, @@ -192,7 +181,6 @@ func TestListGroups(t *testing.T) { limit uint64 level int name string - ownerID string metadata sdk.Metadata err errors.SDKError response []sdk.Group @@ -323,7 +311,6 @@ func TestListParentGroups(t *testing.T) { limit uint64 level int name string - ownerID string metadata sdk.Metadata err errors.SDKError response []sdk.Group @@ -455,7 +442,6 @@ func TestListChildrenGroups(t *testing.T) { limit uint64 level int name string - ownerID string metadata sdk.Metadata err errors.SDKError response []sdk.Group @@ -796,7 +782,7 @@ func TestEnableGroup(t *testing.T) { group := sdk.Group{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), + DomainID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: clients.Disabled, @@ -816,7 +802,7 @@ func TestEnableGroup(t *testing.T) { g := mggroups.Group{ ID: group.ID, Name: group.Name, - Owner: group.OwnerID, + Domain: group.DomainID, CreatedAt: creationTime, UpdatedAt: creationTime, Status: clients.DisabledStatus, @@ -849,7 +835,7 @@ func TestDisableGroup(t *testing.T) { group := sdk.Group{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), + DomainID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: clients.Enabled, @@ -869,7 +855,7 @@ func TestDisableGroup(t *testing.T) { g := mggroups.Group{ ID: group.ID, Name: group.Name, - Owner: group.OwnerID, + Domain: group.DomainID, CreatedAt: creationTime, UpdatedAt: creationTime, Status: clients.EnabledStatus, @@ -903,7 +889,6 @@ func TestDeleteGroup(t *testing.T) { group := sdk.Group{ ID: generateUUID(t), Name: gName, - OwnerID: generateUUID(t), CreatedAt: creationTime, UpdatedAt: creationTime, Status: clients.Enabled, diff --git a/pkg/sdk/go/setup_test.go b/pkg/sdk/go/setup_test.go index 4c5631a115..0f15596c26 100644 --- a/pkg/sdk/go/setup_test.go +++ b/pkg/sdk/go/setup_test.go @@ -148,7 +148,7 @@ func convertGroup(g sdk.Group) mggroups.Group { return mggroups.Group{ ID: g.ID, - Owner: g.OwnerID, + Domain: g.DomainID, Parent: g.ParentID, Name: g.Name, Description: g.Description, @@ -190,7 +190,7 @@ func convertClient(c sdk.User) mgclients.Client { ID: c.ID, Name: c.Name, Tags: c.Tags, - Owner: c.Owner, + Domain: c.Domain, Credentials: mgclients.Credentials(c.Credentials), Metadata: mgclients.Metadata(c.Metadata), CreatedAt: c.CreatedAt, @@ -211,7 +211,7 @@ func convertThing(c sdk.Thing) mgclients.Client { ID: c.ID, Name: c.Name, Tags: c.Tags, - Owner: c.Owner, + Domain: c.DomainID, Credentials: mgclients.Credentials(c.Credentials), Metadata: mgclients.Metadata(c.Metadata), CreatedAt: c.CreatedAt, @@ -230,7 +230,7 @@ func convertChannel(g sdk.Channel) mggroups.Group { } return mggroups.Group{ ID: g.ID, - Owner: g.OwnerID, + Domain: g.DomainID, Parent: g.ParentID, Name: g.Name, Description: g.Description, diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go index 0593d7b337..16f629416d 100644 --- a/pkg/sdk/go/things.go +++ b/pkg/sdk/go/things.go @@ -28,7 +28,7 @@ type Thing struct { Name string `json:"name,omitempty"` Credentials Credentials `json:"credentials"` Tags []string `json:"tags,omitempty"` - Owner string `json:"owner,omitempty"` + DomainID string `json:"domain_id,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 17fa2f8973..98defd07ea 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -162,7 +162,6 @@ func TestCreateThing(t *testing.T) { ID: id, Name: "name", Tags: []string{"tag1", "tag2"}, - Owner: id, Credentials: user.Credentials, Metadata: validMetadata, CreatedAt: time.Now(), @@ -173,7 +172,6 @@ func TestCreateThing(t *testing.T) { ID: id, Name: "name", Tags: []string{"tag1", "tag2"}, - Owner: id, Credentials: user.Credentials, Metadata: validMetadata, CreatedAt: time.Now(), @@ -193,7 +191,6 @@ func TestCreateThing(t *testing.T) { rThing, err := mgsdk.CreateThing(tc.client, tc.token) tc.response.ID = rThing.ID - tc.response.Owner = rThing.Owner tc.response.CreatedAt = rThing.CreatedAt tc.response.UpdatedAt = rThing.UpdatedAt rThing.Credentials.Secret = tc.response.Credentials.Secret @@ -284,7 +281,6 @@ func TestCreateThings(t *testing.T) { rThing, err := mgsdk.CreateThings(tc.things, tc.token) for i, t := range rThing { tc.response[i].ID = t.ID - tc.response[i].Owner = t.Owner tc.response[i].CreatedAt = t.CreatedAt tc.response[i].UpdatedAt = t.UpdatedAt tc.response[i].Credentials.Secret = t.Credentials.Secret @@ -319,7 +315,6 @@ func TestListThings(t *testing.T) { } mgsdk := sdk.NewSDK(conf) - owner := generateUUID(t) for i := 10; i < 100; i++ { th := sdk.Thing{ ID: generateUUID(t), @@ -332,7 +327,6 @@ func TestListThings(t *testing.T) { Status: mgclients.EnabledStatus.String(), } if i == 50 { - th.Owner = owner th.Status = mgclients.DisabledStatus.String() th.Tags = []string{"tag1", "tag2"} } @@ -348,7 +342,6 @@ func TestListThings(t *testing.T) { limit uint64 name string identifier string - ownerID string tag string metadata sdk.Metadata err errors.SDKError @@ -437,15 +430,6 @@ func TestListThings(t *testing.T) { response: []sdk.Thing{ths[0]}, err: nil, }, - { - desc: "list things with given owner", - token: validToken, - offset: 0, - limit: 1, - ownerID: owner, - response: []sdk.Thing{ths[50]}, - err: nil, - }, { desc: "list things with given status", token: validToken, @@ -473,7 +457,6 @@ func TestListThings(t *testing.T) { Offset: tc.offset, Limit: tc.limit, Name: tc.name, - OwnerID: tc.ownerID, Metadata: tc.metadata, Tag: tc.tag, } @@ -560,19 +543,6 @@ func TestListThingsByChannel(t *testing.T) { response: aThings[6:], err: nil, }, - - { - desc: "list things with given ownerID", - token: validToken, - channelID: testsutil.GenerateUUID(t), - page: sdk.PageMetadata{ - OwnerID: user.Owner, - Offset: 6, - Limit: nThing, - }, - response: aThings[6:], - err: nil, - }, { desc: "list things with given subject", token: validToken, diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go index b7a4c583d8..24ceb35c2f 100644 --- a/pkg/sdk/go/users.go +++ b/pkg/sdk/go/users.go @@ -30,7 +30,7 @@ type User struct { Name string `json:"name,omitempty"` Credentials Credentials `json:"credentials"` Tags []string `json:"tags,omitempty"` - Owner string `json:"owner,omitempty"` + Domain string `json:"-"` // ignoring Domain Field, since it will be always empty for users Metadata Metadata `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 97a2e5a172..39d6aeacfd 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -159,7 +159,6 @@ func TestCreateClient(t *testing.T) { ID: id, Name: "name", Tags: []string{"tag1", "tag2"}, - Owner: id, Credentials: user.Credentials, Metadata: validMetadata, CreatedAt: time.Now(), @@ -170,7 +169,6 @@ func TestCreateClient(t *testing.T) { ID: id, Name: "name", Tags: []string{"tag1", "tag2"}, - Owner: id, Credentials: user.Credentials, Metadata: validMetadata, CreatedAt: time.Now(), @@ -191,7 +189,6 @@ func TestCreateClient(t *testing.T) { repoCall3 := crepo.On("Save", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) rClient, err := mgsdk.CreateUser(tc.client, tc.token) tc.response.ID = rClient.ID - tc.response.Owner = rClient.Owner tc.response.CreatedAt = rClient.CreatedAt tc.response.UpdatedAt = rClient.UpdatedAt rClient.Credentials.Secret = tc.response.Credentials.Secret @@ -230,7 +227,6 @@ func TestListClients(t *testing.T) { Status: mgclients.EnabledStatus.String(), } if i == 50 { - cl.Owner = "clientowner" cl.Status = mgclients.DisabledStatus.String() cl.Tags = []string{"tag1", "tag2"} } @@ -246,7 +242,6 @@ func TestListClients(t *testing.T) { limit uint64 name string identifier string - ownerID string tag string metadata sdk.Metadata err errors.SDKError @@ -335,15 +330,7 @@ func TestListClients(t *testing.T) { response: []sdk.User{cls[0]}, err: nil, }, - { - desc: "list users with given owner", - token: validToken, - offset: 0, - limit: 1, - ownerID: "clientowner", - response: []sdk.User{cls[50]}, - err: nil, - }, + { desc: "list users with given status", token: validToken, @@ -371,7 +358,6 @@ func TestListClients(t *testing.T) { Offset: tc.offset, Limit: tc.limit, Name: tc.name, - OwnerID: tc.ownerID, Metadata: tc.metadata, Tag: tc.tag, } @@ -888,7 +874,6 @@ func TestUpdateClientRole(t *testing.T) { Credentials: sdk.Credentials{Identity: "clientidentity", Secret: secret}, Metadata: validMetadata, Status: mgclients.EnabledStatus.String(), - Owner: "owner", } client2 := user @@ -920,7 +905,7 @@ func TestUpdateClientRole(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(users.ErrFailedOwnerUpdate, users.ErrFailedOwnerUpdate), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(users.ErrFailedUpdateRole, users.ErrFailedUpdateRole), http.StatusInternalServerError), }, { desc: "update a user that can't be marshalled", diff --git a/things/api/http/clients.go b/things/api/http/clients.go index 4f3d8a1a31..33c5ec9038 100644 --- a/things/api/http/clients.go +++ b/things/api/http/clients.go @@ -157,7 +157,6 @@ func decodeViewClientPerms(_ context.Context, r *http.Request) (interface{}, err } func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { - var ownerID string s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -182,11 +181,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -196,10 +190,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - - if oid != "" { - ownerID = oid - } st, err := mgclients.ToStatus(s) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -215,7 +205,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) permission: p, listPerms: lp, userID: chi.URLParam(r, "userID"), - owner: ownerID, } return req, nil } diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index a84e012042..244cf71d9d 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -103,7 +103,6 @@ func listClientsEndpoint(svc things.Service) endpoint.Endpoint { Status: req.status, Offset: req.offset, Limit: req.limit, - Owner: req.owner, Name: req.name, Tag: req.tag, Permission: req.permission, diff --git a/things/api/http/endpoints_test.go b/things/api/http/endpoints_test.go index fc44c3e5ad..cbe12647cc 100644 --- a/things/api/http/endpoints_test.go +++ b/things/api/http/endpoints_test.go @@ -456,33 +456,6 @@ func TestListThings(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list things with owner_id", - token: validToken, - listThingsResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with duplicate owner_id", - token: validToken, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things with invalid owner_id", - token: validToken, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list things with name", token: validToken, diff --git a/things/api/http/requests.go b/things/api/http/requests.go index c1f0a10496..687127757b 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -92,7 +92,6 @@ type listClientsReq struct { limit uint64 name string tag string - owner string permission string visibility string userID string diff --git a/things/events/events.go b/things/events/events.go index a709838c4e..3d2cd953d5 100644 --- a/things/events/events.go +++ b/things/events/events.go @@ -60,8 +60,8 @@ func (cce createClientEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(cce.Tags, ",")) val["tags"] = tags } - if cce.Owner != "" { - val["owner"] = cce.Owner + if cce.Domain != "" { + val["domain"] = cce.Domain } if cce.Metadata != nil { metadata, err := json.Marshal(cce.Metadata) @@ -103,8 +103,8 @@ func (uce updateClientEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(uce.Tags, ",")) val["tags"] = tags } - if uce.Owner != "" { - val["owner"] = uce.Owner + if uce.Domain != "" { + val["domain"] = uce.Domain } if uce.Credentials.Identity != "" { val["identity"] = uce.Credentials.Identity @@ -161,8 +161,8 @@ func (vce viewClientEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(vce.Tags, ",")) val["tags"] = tags } - if vce.Owner != "" { - val["owner"] = vce.Owner + if vce.Domain != "" { + val["domain"] = vce.Domain } if vce.Credentials.Identity != "" { val["identity"] = vce.Credentials.Identity @@ -234,8 +234,8 @@ func (lce listClientEvent) Encode() (map[string]interface{}, error) { val["metadata"] = metadata } - if lce.Owner != "" { - val["owner"] = lce.Owner + if lce.Domain != "" { + val["domain"] = lce.Domain } if lce.Tag != "" { val["tag"] = lce.Tag @@ -288,8 +288,8 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { val["metadata"] = metadata } - if lcge.Owner != "" { - val["owner"] = lcge.Owner + if lcge.Domain != "" { + val["domain"] = lcge.Domain } if lcge.Tag != "" { val["tag"] = lcge.Tag diff --git a/things/mocks/repository.go b/things/mocks/repository.go index 14429f163d..d347b42098 100644 --- a/things/mocks/repository.go +++ b/things/mocks/repository.go @@ -324,34 +324,6 @@ func (_m *Repository) UpdateIdentity(ctx context.Context, client clients.Client) return r0, r1 } -// UpdateOwner provides a mock function with given fields: ctx, client -func (_m *Repository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateOwner") - } - - var r0 clients.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(clients.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // UpdateRole provides a mock function with given fields: ctx, client func (_m *Repository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) { ret := _m.Called(ctx, client) diff --git a/things/postgres/clients.go b/things/postgres/clients.go index a9ca6b94ad..69e9f066fd 100644 --- a/things/postgres/clients.go +++ b/things/postgres/clients.go @@ -54,9 +54,9 @@ func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgcl var clients []mgclients.Client for _, cli := range cs { - q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status) - VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status) - RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by` + q := `INSERT INTO clients (id, name, tags, domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status) + VALUES (:id, :name, :tags, :domain_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status) + RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` dbcli, err := pgclients.ToDBClient(cli) if err != nil { @@ -94,7 +94,7 @@ func (repo clientRepo) Save(ctx context.Context, cs ...mgclients.Client) ([]mgcl } func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mgclients.Client, error) { - q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status + q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status FROM clients WHERE secret = :secret AND status = %d`, mgclients.EnabledStatus) diff --git a/things/postgres/clients_test.go b/things/postgres/clients_test.go index 2e6b834213..fa191d32af 100644 --- a/things/postgres/clients_test.go +++ b/things/postgres/clients_test.go @@ -26,6 +26,7 @@ var ( clientIdentity = "client-identity@example.com" clientName = "client name" invalidClientID = "invalidClientID" + invalidDomainID = strings.Repeat("m", maxNameSize+10) namesgen = namegenerator.NewNameGenerator() ) @@ -37,6 +38,8 @@ func TestClientsSave(t *testing.T) { repo := postgres.NewRepository(database) uid := testsutil.GenerateUUID(t) + domainID := testsutil.GenerateUUID(t) + secret := testsutil.GenerateUUID(t) cases := []struct { desc string @@ -46,11 +49,12 @@ func TestClientsSave(t *testing.T) { { desc: "add new client successfully", client: clients.Client{ - ID: uid, - Name: clientName, + ID: uid, + Domain: domainID, + Name: clientName, Credentials: clients.Credentials{ Identity: clientIdentity, - Secret: testsutil.GenerateUUID(t), + Secret: secret, }, Metadata: clients.Metadata{}, Status: clients.EnabledStatus, @@ -58,13 +62,42 @@ func TestClientsSave(t *testing.T) { err: nil, }, { - desc: "add new client with an owner", + desc: "add new client with duplicate secret", + client: clients.Client{ + ID: uid, + Domain: domainID, + Name: clientName, + Credentials: clients.Credentials{ + Identity: clientIdentity, + Secret: secret, + }, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "add new client with duplicate secret", client: clients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: uid, - Name: clientName, + ID: uid, + Domain: domainID, + Name: clientName, Credentials: clients.Credentials{ - Identity: "withowner-client@example.com", + Identity: clientIdentity, + Secret: testsutil.GenerateUUID(t), + }, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, + }, + err: errors.ErrCreateEntity, + }, + { + desc: "add new client without domain id", + client: clients.Client{ + ID: testsutil.GenerateUUID(t), + Name: clientName, + Credentials: clients.Credentials{ + Identity: "withoutdomain-client@example.com", Secret: testsutil.GenerateUUID(t), }, Metadata: clients.Metadata{}, @@ -75,8 +108,9 @@ func TestClientsSave(t *testing.T) { { desc: "add client with invalid client id", client: clients.Client{ - ID: invalidName, - Name: clientName, + ID: invalidName, + Domain: domainID, + Name: clientName, Credentials: clients.Credentials{ Identity: "invalidid-client@example.com", Secret: testsutil.GenerateUUID(t), @@ -89,8 +123,9 @@ func TestClientsSave(t *testing.T) { { desc: "add client with invalid client name", client: clients.Client{ - ID: testsutil.GenerateUUID(t), - Name: invalidName, + ID: testsutil.GenerateUUID(t), + Name: invalidName, + Domain: domainID, Credentials: clients.Credentials{ Identity: "invalidname-client@example.com", Secret: testsutil.GenerateUUID(t), @@ -101,12 +136,12 @@ func TestClientsSave(t *testing.T) { err: errors.ErrCreateEntity, }, { - desc: "add client with invalid client owner", + desc: "add client with invalid client domain id", client: clients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: invalidName, + ID: testsutil.GenerateUUID(t), + Domain: invalidDomainID, Credentials: clients.Credentials{ - Identity: "invalidowner-client@example.com", + Identity: "invaliddomainid-client@example.com", Secret: testsutil.GenerateUUID(t), }, Metadata: clients.Metadata{}, @@ -131,7 +166,9 @@ func TestClientsSave(t *testing.T) { { desc: "add client with a missing client identity", client: clients.Client{ - ID: testsutil.GenerateUUID(t), + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + Name: "missing-client-identity", Credentials: clients.Credentials{ Identity: "", Secret: testsutil.GenerateUUID(t), @@ -143,7 +180,8 @@ func TestClientsSave(t *testing.T) { { desc: "add client with a missing client secret", client: clients.Client{ - ID: testsutil.GenerateUUID(t), + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), Credentials: clients.Credentials{ Identity: "missing-client-secret@example.com", Secret: "", diff --git a/things/postgres/init.go b/things/postgres/init.go index d59627e9c3..28e07a2cc6 100644 --- a/things/postgres/init.go +++ b/things/postgres/init.go @@ -19,7 +19,7 @@ func Migration() *migrate.MemoryMigrationSource { `CREATE TABLE IF NOT EXISTS clients ( id VARCHAR(36) PRIMARY KEY, name VARCHAR(1024), - owner_id VARCHAR(36), + domain_id VARCHAR(36) NOT NULL, identity VARCHAR(254), secret VARCHAR(4096) NOT NULL, tags TEXT[], @@ -28,8 +28,8 @@ func Migration() *migrate.MemoryMigrationSource { updated_at TIMESTAMP, updated_by VARCHAR(254), status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), - UNIQUE (owner_id, secret), - UNIQUE (owner_id, name) + UNIQUE (domain_id, secret), + UNIQUE (domain_id, name) )`, }, Down: []string{ diff --git a/things/service.go b/things/service.go index 3799c3b8cd..8130609c67 100644 --- a/things/service.go +++ b/things/service.go @@ -94,7 +94,7 @@ func (svc service) CreateThings(ctx context.Context, token string, cls ...mgclie if c.Status != mgclients.DisabledStatus && c.Status != mgclients.EnabledStatus { return []mgclients.Client{}, svcerr.ErrInvalidStatus } - c.Owner = user.GetDomainId() + c.Domain = user.GetDomainId() c.CreatedAt = time.Now() clients = append(clients, c) } @@ -185,7 +185,7 @@ func (svc service) ListClients(ctx context.Context, token, reqUserID string, pm err := svc.checkSuperAdmin(ctx, res.GetUserId()) switch { case err == nil: - pm.Owner = res.GetDomainId() + pm.Domain = res.GetDomainId() default: // If domain is disabled , then this authorization will fail for all non-admin domain users if _, err := svc.authorize(ctx, "", auth.UserType, auth.UsersKind, res.GetId(), auth.MembershipPermission, auth.DomainType, res.GetDomainId()); err != nil { diff --git a/things/service_test.go b/things/service_test.go index 798a197234..644bd6c0b7 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -37,7 +37,6 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - adminEmail = "admin@example.com" validToken = "token" inValidToken = invalid valid = "valid" @@ -88,6 +87,34 @@ func TestCreateThings(t *testing.T) { saveErr: errors.ErrConflict, err: svcerr.ErrConflict, }, + { + desc: "create a new thing without secret", + thing: mgclients.Client{ + Name: "clientWithoutSecret", + Credentials: mgclients.Credentials{ + Identity: "newclientwithoutsecret@example.com", + }, + Status: mgclients.EnabledStatus, + }, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, + err: nil, + }, + { + desc: "create a new thing without identity", + thing: mgclients.Client{ + Name: "clientWithoutIdentity", + Credentials: mgclients.Credentials{ + Identity: "newclientwithoutsecret@example.com", + }, + Status: mgclients.EnabledStatus, + }, + token: validToken, + authResponse: &magistrala.AuthorizeRes{Authorized: true}, + addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, + err: nil, + }, { desc: "create a new enabled thing with name", thing: mgclients.Client{ @@ -103,6 +130,7 @@ func TestCreateThings(t *testing.T) { addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, + { desc: "create a new disabled thing with name", thing: mgclients.Client{ @@ -222,35 +250,6 @@ func TestCreateThings(t *testing.T) { addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, err: nil, }, - { - desc: "create a new thing with invalid owner", - thing: mgclients.Client{ - Owner: wrongID, - Credentials: mgclients.Credentials{ - Identity: "newclientwithinvalidowner@example.com", - Secret: secret, - }, - }, - token: validToken, - authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, - saveErr: repoerr.ErrMalformedEntity, - err: repoerr.ErrCreateEntity, - }, - { - desc: "create a new thing with empty secret", - thing: mgclients.Client{ - Owner: testsutil.GenerateUUID(t), - Credentials: mgclients.Credentials{ - Identity: "newclientwithemptysecret@example.com", - }, - }, - token: validToken, - authResponse: &magistrala.AuthorizeRes{Authorized: true}, - addPolicyResponse: &magistrala.AddPoliciesRes{Added: true}, - saveErr: repoerr.ErrMissingSecret, - err: repoerr.ErrCreateEntity, - }, { desc: "create a new thing with invalid status", thing: mgclients.Client{ @@ -321,7 +320,7 @@ func TestCreateThings(t *testing.T) { tc.thing.CreatedAt = expected[0].CreatedAt tc.thing.UpdatedAt = expected[0].UpdatedAt tc.thing.Credentials.Secret = expected[0].Credentials.Secret - tc.thing.Owner = expected[0].Owner + tc.thing.Domain = expected[0].Domain tc.thing.UpdatedBy = expected[0].UpdatedBy assert.Equal(t, tc.thing, expected[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.thing, expected[0])) } @@ -1361,12 +1360,13 @@ func TestListMembers(t *testing.T) { nClients := uint64(10) aClients := []mgclients.Client{} - owner := testsutil.GenerateUUID(t) + domainID := testsutil.GenerateUUID(t) for i := uint64(0); i < nClients; i++ { identity := fmt.Sprintf("member_%d@example.com", i) client := mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Name: identity, + ID: testsutil.GenerateUUID(t), + Domain: domainID, + Name: identity, Credentials: mgclients.Credentials{ Identity: identity, Secret: "password", @@ -1374,9 +1374,6 @@ func TestListMembers(t *testing.T) { Tags: []string{"tag1", "tag2"}, Metadata: mgclients.Metadata{"role": "client"}, } - if i%3 == 0 { - client.Owner = owner - } aClients = append(aClients, client) } aClients[0].Permissions = []string{"admin"} @@ -1400,12 +1397,9 @@ func TestListMembers(t *testing.T) { err error }{ { - desc: "list members with authorized token", - token: validToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - Owner: adminEmail, - }, + desc: "list members with authorized token", + token: validToken, + groupID: testsutil.GenerateUUID(t), identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, listObjectsResponse: &magistrala.ListObjectsRes{}, @@ -1436,7 +1430,6 @@ func TestListMembers(t *testing.T) { Offset: 6, Limit: nClients, Status: mgclients.AllStatus, - Owner: adminEmail, }, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, @@ -1457,12 +1450,9 @@ func TestListMembers(t *testing.T) { err: nil, }, { - desc: "list members with an invalid token", - token: authmocks.InvalidValue, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - Owner: adminEmail, - }, + desc: "list members with an invalid token", + token: authmocks.InvalidValue, + groupID: testsutil.GenerateUUID(t), identifyResponse: &magistrala.IdentityRes{}, response: mgclients.MembersPage{ Page: mgclients.Page{ @@ -1475,12 +1465,9 @@ func TestListMembers(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "list members with an invalid id", - token: validToken, - groupID: wrongID, - page: mgclients.Page{ - Owner: adminEmail, - }, + desc: "list members with an invalid id", + token: validToken, + groupID: wrongID, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, listObjectsResponse: &magistrala.ListObjectsRes{}, @@ -1497,36 +1484,10 @@ func TestListMembers(t *testing.T) { err: svcerr.ErrNotFound, }, { - desc: "list members for an owner", - token: validToken, - groupID: testsutil.GenerateUUID(t), - page: mgclients.Page{ - Owner: adminEmail, - }, - identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, - authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - listObjectsResponse: &magistrala.ListObjectsRes{}, - listPermissionsResponse: &magistrala.ListPermissionsRes{}, - retreiveAllByIDsResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - }, - Clients: []mgclients.Client{aClients[0], aClients[3], aClients[6], aClients[9]}, - }, - response: mgclients.MembersPage{ - Page: mgclients.Page{ - Total: 4, - }, - Members: []mgclients.Client{aClients[0], aClients[3], aClients[6], aClients[9]}, - }, - err: nil, - }, - { - desc: "list members for an owner with permissions", + desc: "list members with permissions", token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ - Owner: adminEmail, ListPerms: true, }, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, @@ -1552,7 +1513,6 @@ func TestListMembers(t *testing.T) { token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ - Owner: adminEmail, ListPerms: true, }, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, @@ -1565,7 +1525,6 @@ func TestListMembers(t *testing.T) { token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ - Owner: adminEmail, ListPerms: true, }, identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, @@ -1579,7 +1538,6 @@ func TestListMembers(t *testing.T) { token: validToken, groupID: testsutil.GenerateUUID(t), page: mgclients.Page{ - Owner: adminEmail, ListPerms: true, }, retreiveAllByIDsResponse: mgclients.ClientsPage{ diff --git a/users/api/clients.go b/users/api/clients.go index 0f9075c3e5..84283d4e72 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -217,10 +217,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } + order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -229,6 +226,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } + st, err := mgclients.ToStatus(s) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) @@ -242,7 +240,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) name: n, identity: i, tag: t, - owner: oid, order: order, dir: dir, } @@ -494,10 +491,6 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err if err != nil { return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } - oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } st, err := mgclients.ToStatus(s) if err != nil { return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) @@ -518,7 +511,6 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err Identity: i, Name: n, Tag: t, - Owner: oid, Permission: p, ListPerms: lp, }, nil diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index 742bddff7f..0691810aa0 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -439,33 +439,6 @@ func TestListClients(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list users with owner_id", - token: validToken, - listUsersResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list users with duplicate owner_id", - token: validToken, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid owner_id", - token: validToken, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with name", token: validToken, @@ -1898,36 +1871,6 @@ func TestListUsersByUserGroupId(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list users with owner_id", - token: validToken, - groupID: validID, - listUsersResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list users with duplicate owner_id", - token: validToken, - groupID: validID, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid owner_id", - token: validToken, - groupID: validID, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with name", token: validToken, @@ -2220,33 +2163,6 @@ func TestListUsersByChannelID(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list users with owner_id", - token: validToken, - listUsersResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list users with duplicate owner_id", - token: validToken, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid owner_id", - token: validToken, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with name", token: validToken, @@ -2542,33 +2458,6 @@ func TestListUsersByDomainID(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list users with owner_id", - token: validToken, - listUsersResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list users with duplicate owner_id", - token: validToken, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid owner_id", - token: validToken, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with name", token: validToken, @@ -2872,33 +2761,6 @@ func TestListUsersByThingID(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrValidation, }, - { - desc: "list users with owner_id", - token: validToken, - listUsersResponse: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 1, - }, - Clients: []mgclients.Client{client}, - }, - query: fmt.Sprintf("owner_id=%s", validID), - status: http.StatusOK, - err: nil, - }, - { - desc: "list users with duplicate owner_id", - token: validToken, - query: "owner_id=1&owner_id=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid owner_id", - token: validToken, - query: "owner_id=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with name", token: validToken, diff --git a/users/api/endpoints.go b/users/api/endpoints.go index 50f7d91c87..b8bb81101a 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -75,7 +75,6 @@ func listClientsEndpoint(svc users.Service) endpoint.Endpoint { Status: req.status, Offset: req.offset, Limit: req.limit, - Owner: req.owner, Name: req.name, Tag: req.tag, Metadata: req.metadata, diff --git a/users/api/requests.go b/users/api/requests.go index 9c52064f3e..394cca1240 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -60,7 +60,6 @@ type listClientsReq struct { name string tag string identity string - owner string metadata mgclients.Metadata order string dir string diff --git a/users/events/events.go b/users/events/events.go index b02d18ddfc..95bba47903 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -65,8 +65,8 @@ func (cce createClientEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(cce.Tags, ",")) val["tags"] = tags } - if cce.Owner != "" { - val["owner"] = cce.Owner + if cce.Domain != "" { + val["domain"] = cce.Domain } if cce.Metadata != nil { metadata, err := json.Marshal(cce.Metadata) @@ -163,8 +163,8 @@ func (vce viewClientEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(vce.Tags, ",")) val["tags"] = tags } - if vce.Owner != "" { - val["owner"] = vce.Owner + if vce.Domain != "" { + val["domain"] = vce.Domain } if vce.Credentials.Identity != "" { val["identity"] = vce.Credentials.Identity @@ -210,8 +210,8 @@ func (vpe viewProfileEvent) Encode() (map[string]interface{}, error) { tags := fmt.Sprintf("[%s]", strings.Join(vpe.Tags, ",")) val["tags"] = tags } - if vpe.Owner != "" { - val["owner"] = vpe.Owner + if vpe.Domain != "" { + val["domain"] = vpe.Domain } if vpe.Credentials.Identity != "" { val["identity"] = vpe.Credentials.Identity @@ -269,8 +269,8 @@ func (lce listClientEvent) Encode() (map[string]interface{}, error) { val["metadata"] = metadata } - if lce.Owner != "" { - val["owner"] = lce.Owner + if lce.Domain != "" { + val["domain"] = lce.Domain } if lce.Tag != "" { val["tag"] = lce.Tag @@ -321,8 +321,8 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { val["metadata"] = metadata } - if lcge.Owner != "" { - val["owner"] = lcge.Owner + if lcge.Domain != "" { + val["domain"] = lcge.Domain } if lcge.Tag != "" { val["tag"] = lcge.Tag diff --git a/users/events/streams.go b/users/events/streams.go index 4e0da1f36a..c810413c28 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -68,7 +68,7 @@ func (es *eventStore) UpdateClientRole(ctx context.Context, token string, user m return user, err } - return es.update(ctx, "owner", user) + return es.update(ctx, "role", user) } func (es *eventStore) UpdateClientTags(ctx context.Context, token string, user mgclients.Client) (mgclients.Client, error) { diff --git a/users/mocks/repository.go b/users/mocks/repository.go index 6a71d302fd..2bc4c65098 100644 --- a/users/mocks/repository.go +++ b/users/mocks/repository.go @@ -287,34 +287,6 @@ func (_m *Repository) UpdateIdentity(ctx context.Context, client clients.Client) return r0, r1 } -// UpdateOwner provides a mock function with given fields: ctx, client -func (_m *Repository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateOwner") - } - - var r0 clients.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(clients.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // UpdateRole provides a mock function with given fields: ctx, client func (_m *Repository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) { ret := _m.Called(ctx, client) diff --git a/users/postgres/clients.go b/users/postgres/clients.go index dd1abfae9c..5a65de6917 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -46,9 +46,9 @@ func NewRepository(db postgres.Database) Repository { } func (repo clientRepo) Save(ctx context.Context, c mgclients.Client) (mgclients.Client, error) { - q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, status, role) - VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :status, :role) - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at` + q := `INSERT INTO clients (id, name, tags, identity, secret, metadata, created_at, status, role) + VALUES (:id, :name, :tags, :identity, :secret, :metadata, :created_at, :status, :role) + RETURNING id, name, tags, identity, metadata, status, created_at` dbc, err := pgclients.ToDBClient(c) if err != nil { return mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) @@ -92,7 +92,7 @@ func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) erro } func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.Client, error) { - q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status, role + q := `SELECT id, name, tags, identity, secret, metadata, created_at, updated_at, updated_by, status, role FROM clients WHERE id = :id` dbc := pgclients.DBClient{ @@ -128,7 +128,7 @@ func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgcl return mgclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) } - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status, c.role, + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, c.status, c.role, c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) dbPage, err := pgclients.ToDBClientsPage(pm) @@ -177,7 +177,7 @@ func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgcl func (repo clientRepo) UpdateRole(ctx context.Context, client mgclients.Client) (mgclients.Client, error) { query := `UPDATE clients SET role = :role, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, role, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, metadata, status, role, created_at, updated_at, updated_by` dbc, err := pgclients.ToDBClient(client) if err != nil { diff --git a/users/postgres/clients_test.go b/users/postgres/clients_test.go index 4a5d7f0455..29d7239602 100644 --- a/users/postgres/clients_test.go +++ b/users/postgres/clients_test.go @@ -57,21 +57,6 @@ func TestClientsSave(t *testing.T) { }, err: nil, }, - { - desc: "add new client with an owner", - client: mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: uid, - Name: namesgen.Generate(), - Credentials: mgclients.Credentials{ - Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), - Secret: password, - }, - Metadata: mgclients.Metadata{}, - Status: mgclients.EnabledStatus, - }, - err: nil, - }, { desc: "add client with duplicate client identity", client: mgclients.Client{ @@ -128,20 +113,6 @@ func TestClientsSave(t *testing.T) { }, err: errors.ErrMalformedEntity, }, - { - desc: "add client with invalid client owner", - client: mgclients.Client{ - ID: testsutil.GenerateUUID(t), - Owner: invalidName, - Credentials: mgclients.Credentials{ - Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), - Secret: password, - }, - Metadata: mgclients.Metadata{}, - Status: mgclients.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, { desc: "add client with invalid client identity", client: mgclients.Client{ @@ -328,8 +299,6 @@ func TestRetrieveAll(t *testing.T) { repo := cpostgres.NewRepository(database) - ownerID := testsutil.GenerateUUID(t) - num := 200 var items, enabledClients []mgclients.Client for i := 0; i < num; i++ { @@ -345,7 +314,6 @@ func TestRetrieveAll(t *testing.T) { Tags: []string{"tag1"}, } if i%50 == 0 { - client.Owner = ownerID client.Metadata = map[string]interface{}{ "key": "value", } @@ -577,43 +545,6 @@ func TestRetrieveAll(t *testing.T) { Clients: items, }, }, - { - desc: "retrieve with owner id", - pageMeta: mgclients.Page{ - Owner: ownerID, - Offset: 0, - Limit: 5, - Role: mgclients.AllRole, - Status: mgclients.AllStatus, - }, - page: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 4, - Offset: 0, - Limit: 5, - }, - Clients: []mgclients.Client{items[0], items[50], items[100], items[150]}, - }, - err: nil, - }, - { - desc: "retrieve with invalid owner id", - pageMeta: mgclients.Page{ - Owner: invalidName, - Offset: 0, - Limit: 200, - Role: mgclients.AllRole, - }, - page: mgclients.ClientsPage{ - Page: mgclients.Page{ - Total: 0, - Offset: 0, - Limit: 200, - }, - Clients: []mgclients.Client{}, - }, - err: nil, - }, { desc: "retrieve by tags", pageMeta: mgclients.Page{ diff --git a/users/postgres/init.go b/users/postgres/init.go index 3edda715f6..9e8a4903ff 100644 --- a/users/postgres/init.go +++ b/users/postgres/init.go @@ -21,7 +21,7 @@ func Migration() *migrate.MemoryMigrationSource { `CREATE TABLE IF NOT EXISTS clients ( id VARCHAR(36) PRIMARY KEY, name VARCHAR(254) NOT NULL UNIQUE, - owner_id VARCHAR(36), + domain_id VARCHAR(36), identity VARCHAR(254) NOT NULL UNIQUE, secret TEXT NOT NULL, tags TEXT[], diff --git a/users/service.go b/users/service.go index 152659ba8d..5f703cbe16 100644 --- a/users/service.go +++ b/users/service.go @@ -28,8 +28,8 @@ var ( // ErrFailedPolicyUpdate indicates a failure to update user policy. ErrFailedPolicyUpdate = errors.New("failed to update user policy") - // ErrFailedOwnerUpdate indicates a failure to update user policy. - ErrFailedOwnerUpdate = errors.New("failed to update user owner") + // ErrFailedUpdateRole indicates a failure to update user role. + ErrFailedUpdateRole = errors.New("failed to update user role") // ErrAddPolicies indictaed a failre to add policies. errAddPolicies = errors.New("failed to add policies") @@ -393,7 +393,7 @@ func (svc service) UpdateClientRole(ctx context.Context, token string, cli mgcli if errRollback := svc.updateClientPolicy(ctx, cli.ID, mgclients.UserRole); errRollback != nil { return mgclients.Client{}, errors.Wrap(err, errors.Wrap(repoerr.ErrRollbackTx, errRollback)) } - return mgclients.Client{}, errors.Wrap(ErrFailedOwnerUpdate, err) + return mgclients.Client{}, errors.Wrap(ErrFailedUpdateRole, err) } return client, nil } diff --git a/users/service_test.go b/users/service_test.go index 7715fe1afa..474502942b 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -287,7 +287,6 @@ func TestRegisterClient(t *testing.T) { tc.client.CreatedAt = expected.CreatedAt tc.client.UpdatedAt = expected.UpdatedAt tc.client.Credentials.Secret = expected.Credentials.Secret - tc.client.Owner = expected.Owner tc.client.UpdatedBy = expected.UpdatedBy assert.Equal(t, tc.client, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected)) ok := repoCall2.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) @@ -364,7 +363,6 @@ func TestRegisterClient(t *testing.T) { tc.client.CreatedAt = expected.CreatedAt tc.client.UpdatedAt = expected.UpdatedAt tc.client.Credentials.Secret = expected.Credentials.Secret - tc.client.Owner = expected.Owner tc.client.UpdatedBy = expected.UpdatedBy assert.Equal(t, tc.client, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected)) ok := repoCall5.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) From 56ed2c2f63b4ede2ee20d712223f663d4c53f92b Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:53:05 +0300 Subject: [PATCH 35/71] NOISSUE - Simplify docker deployment (#275) Signed-off-by: rodneyosodo Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- Makefile | 56 +----------------- README.md | 2 +- docker/.env | 6 +- docker/README.md | 98 ++++++++++++++++++++++++++++++- docker/brokers/README.md | 34 ----------- docker/brokers/docker-compose.yml | 51 ---------------- docker/brokers/nats.yml | 16 ----- docker/brokers/profiles/README.md | 24 -------- docker/brokers/profiles/nats.yml | 19 ------ docker/brokers/rabbitmq.yml | 14 ----- docker/docker-compose.yml | 53 +++++++++++++---- docker/es/docker-compose.yml | 17 ------ docker/policy/model.conf | 17 ------ 13 files changed, 145 insertions(+), 262 deletions(-) delete mode 100644 docker/brokers/README.md delete mode 100644 docker/brokers/docker-compose.yml delete mode 100644 docker/brokers/nats.yml delete mode 100644 docker/brokers/profiles/README.md delete mode 100644 docker/brokers/profiles/nats.yml delete mode 100644 docker/brokers/rabbitmq.yml delete mode 100644 docker/es/docker-compose.yml delete mode 100644 docker/policy/model.conf diff --git a/Makefile b/Makefile index d95a679061..d1de472928 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,6 @@ DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:aln DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config DEFAULT_DOCKER_COMPOSE_COMMAND := up GRPC_MTLS_CERT_FILES_EXISTS = 0 -DOCKER_PROFILE ?= $(MG_MQTT_BROKER_TYPE)_$(MG_MESSAGE_BROKER_TYPE) MOCKERY_VERSION=v2.38.0 ifneq ($(MG_MESSAGE_BROKER_TYPE),) MG_MESSAGE_BROKER_TYPE := $(MG_MESSAGE_BROKER_TYPE) @@ -29,12 +28,6 @@ else MG_MESSAGE_BROKER_TYPE=nats endif -ifneq ($(MG_MQTT_BROKER_TYPE),) - MG_MQTT_BROKER_TYPE := $(MG_MQTT_BROKER_TYPE) -else - MG_MQTT_BROKER_TYPE=nats -endif - ifneq ($(MG_ES_TYPE),) MG_ES_TYPE := $(MG_ES_TYPE) else @@ -111,7 +104,7 @@ clean: cleandocker: # Stops containers and removes containers, networks, volumes, and images created by up - docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans + docker compose -f docker/docker-compose.yml -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans ifdef pv # Remove unused volumes @@ -208,53 +201,10 @@ endif endif endif -define edit_docker_config - sed -i "s/MG_MQTT_BROKER_TYPE=.*/MG_MQTT_BROKER_TYPE=$(1)/" docker/.env - sed -i "s/MG_MQTT_BROKER_HEALTH_CHECK=.*/MG_MQTT_BROKER_HEALTH_CHECK=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_HEALTH_CHECK}/" docker/.env - sed -i "s/MG_MQTT_ADAPTER_WS_TARGET_PATH=.*/MG_MQTT_ADAPTER_WS_TARGET_PATH=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_WS_TARGET_PATH}/" docker/.env - sed -i "s/MG_MESSAGE_BROKER_TYPE=.*/MG_MESSAGE_BROKER_TYPE=$(2)/" docker/.env - sed -i "s,file: .*.yml,file: brokers/$(2).yml," docker/brokers/docker-compose.yml - sed -i "s,MG_MESSAGE_BROKER_URL=.*,MG_MESSAGE_BROKER_URL=$$\{MG_$(shell echo ${MG_MESSAGE_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env - sed -i "s,MG_MQTT_ADAPTER_MQTT_QOS=.*,MG_MQTT_ADAPTER_MQTT_QOS=$$\{MG_$(shell echo ${MG_MQTT_BROKER_TYPE} | tr 'a-z' 'A-Z')_MQTT_QOS\}," docker/.env -endef - -change_config: -ifeq ($(DOCKER_PROFILE),nats_nats) - sed -i "s/- broker/- nats/g" docker/docker-compose.yml - sed -i "s/- rabbitmq/- nats/g" docker/docker-compose.yml - sed -i "s,MG_NATS_URL=.*,MG_NATS_URL=nats://nats:$$\{MG_NATS_PORT}," docker/.env - $(call edit_docker_config,nats,nats) -else ifeq ($(DOCKER_PROFILE),nats_rabbitmq) - sed -i "s/nats/broker/g" docker/docker-compose.yml - sed -i "s,MG_NATS_URL=.*,MG_NATS_URL=nats://nats:$$\{MG_NATS_PORT}," docker/.env - sed -i "s/rabbitmq/broker/g" docker/docker-compose.yml - $(call edit_docker_config,nats,rabbitmq) -else ifeq ($(DOCKER_PROFILE),vernemq_nats) - sed -i "s/nats/broker/g" docker/docker-compose.yml - sed -i "s/rabbitmq/broker/g" docker/docker-compose.yml - sed -i "s,MG_NATS_URL=.*,MG_NATS_URL=nats://broker:$$\{MG_NATS_PORT}," docker/.env - $(call edit_docker_config,vernemq,nats) -else ifeq ($(DOCKER_PROFILE),vernemq_rabbitmq) - sed -i "s/nats/broker/g" docker/docker-compose.yml - sed -i "s/rabbitmq/broker/g" docker/docker-compose.yml - $(call edit_docker_config,vernemq,rabbitmq) -else - $(error Invalid DOCKER_PROFILE $(DOCKER_PROFILE)) -endif - -run: check_certs change_config -ifeq ($(MG_ES_TYPE), redis) - sed -i "s/MG_ES_TYPE=.*/MG_ES_TYPE=redis/" docker/.env - sed -i "s/MG_ES_URL=.*/MG_ES_URL=$$\{MG_REDIS_URL}/" docker/.env - docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) --profile redis -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) -else - sed -i "s,MG_ES_TYPE=.*,MG_ES_TYPE=$$\{MG_MESSAGE_BROKER_TYPE}," docker/.env - sed -i "s,MG_ES_URL=.*,MG_ES_URL=$$\{MG_$(shell echo ${MG_MESSAGE_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env - docker compose -f docker/docker-compose.yml --env-file docker/.env --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) -endif +run: check_certs + docker compose -f docker/docker-compose.yml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) run_addons: check_certs - $(call change_config) $(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC)))) @for SVC in $(RUN_ADDON_ARGS); do \ MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ diff --git a/README.md b/README.md index 806b2dfdaf..499b1669b3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Developing Magistrala will also require: Once the prerequisites are installed, execute the following commands from the project's root: ```bash -docker compose -f docker/docker-compose.yml --env-file docker/.env --profile nats_nats -p git_github_com_absmach_magistrala_git_ up +docker compose -f docker/docker-compose.yml --env-file docker/.env -p git_github_com_absmach_magistrala_git_ up ``` This will bring up the Magistrala docker services and interconnect them. This command can also be executed using the project's included Makefile: diff --git a/docker/.env b/docker/.env index a24687f15d..1de267cbe7 100644 --- a/docker/.env +++ b/docker/.env @@ -12,7 +12,7 @@ MG_NGINX_MQTTS_PORT=8883 MG_NATS_PORT=4222 MG_NATS_HTTP_PORT=8222 MG_NATS_JETSTREAM_KEY=u7wFoAPgXpDueXOFldBnXDh4xjnSOyEJ2Cb8Z5SZvGLzIZ3U4exWhhoIBZHzuNvh -MG_NATS_URL=nats://broker:${MG_NATS_PORT} +MG_NATS_URL=nats://nats:${MG_NATS_PORT} # Configs for nats as MQTT broker MG_NATS_HEALTH_CHECK=http://nats:${MG_NATS_HTTP_PORT}/healthz MG_NATS_WS_TARGET_PATH= @@ -25,7 +25,7 @@ MG_RABBITMQ_USER=magistrala MG_RABBITMQ_PASS=magistrala MG_RABBITMQ_COOKIE=magistrala MG_RABBITMQ_VHOST=/ -MG_RABBITMQ_URL=amqp://${MG_RABBITMQ_USER}:${MG_RABBITMQ_PASS}@broker:${MG_RABBITMQ_PORT}${MG_RABBITMQ_VHOST} +MG_RABBITMQ_URL=amqp://${MG_RABBITMQ_USER}:${MG_RABBITMQ_PASS}@rabbitmq:${MG_RABBITMQ_PORT}${MG_RABBITMQ_VHOST} ## Message Broker MG_MESSAGE_BROKER_TYPE=nats @@ -55,7 +55,7 @@ MG_REDIS_URL=redis://es-redis:${MG_REDIS_TCP_PORT}/0 ## Event Store MG_ES_TYPE=${MG_MESSAGE_BROKER_TYPE} -MG_ES_URL=${MG_NATS_URL} +MG_ES_URL=${MG_MESSAGE_BROKER_URL} ## Jaeger MG_JAEGER_COLLECTOR_OTLP_ENABLED=true diff --git a/docker/README.md b/docker/README.md index 1f8b2c1799..9361bcc0c8 100644 --- a/docker/README.md +++ b/docker/README.md @@ -10,7 +10,7 @@ Follow the [official documentation](https://docs.docker.com/compose/install/). ## Usage -Run following commands from project root directory. +Run the following commands from the project root directory. ```bash docker-compose -f docker/docker-compose.yml up @@ -21,3 +21,99 @@ docker-compose -f docker/addons//docker-compose.yml up ``` To pull docker images from a specific release you need to change the value of `MG_RELEASE_TAG` in `.env` before running these commands. + +## Broker Configuration + +Magistrala supports configurable MQTT broker and Message broker, which also acts as an events store. Magistrala uses two types of brokers: + +1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'VerneMQ' or 'NATS'. +2. MESSAGE_BROKER: Manages message exchange between Magistrala core, optional, and external services. This can either be 'NATS' or 'RabbitMQ'. This is used to store messages for distributed processing. + +Events store: This is used by Magistrala services to store events for distributed processing. Magistrala uses a single service to be the message broker and events store. This can either be 'NATS' or 'RabbitMQ'. Redis can also be used as an events store, but it requires a message broker to be deployed along with it for message exchange. + +This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. + +The current deployment strategy for Magistrala in `docker/docker-compose.yml` is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. + +Therefore, the following combinations are possible: + +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis +- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ +- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis +- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS +- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis + +For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker: + +```bash +MG_MESSAGE_BROKER_TYPE=rabbitmq make dockers +``` + +```env +MG_MESSAGE_BROKER_TYPE=rabbitmq +MG_MESSAGE_BROKER_URL=${MG_RABBITMQ_URL} +``` + +For Redis as an events store, you would need to run RabbitMQ or NATS as a message broker. For example, to use Redis as an events store with rabbitmq as a message broker: + +```bash +MG_ES_TYPE=redis MG_MESSAGE_BROKER_TYPE=rabbitmq make dockers +``` + +```env +MG_MESSAGE_BROKER_TYPE=rabbitmq +MG_MESSAGE_BROKER_URL=${MG_RABBITMQ_URL} +MG_ES_TYPE=redis +MG_ES_URL=${MG_REDIS_URL} +``` + +For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker: + +```env +MG_MQTT_BROKER_TYPE=nats +MG_MQTT_BROKER_HEALTH_CHECK=${MG_NATS_HEALTH_CHECK} +MG_MQTT_ADAPTER_MQTT_QOS=${MG_NATS_MQTT_QOS} +MG_MQTT_ADAPTER_MQTT_TARGET_HOST=${MG_MQTT_BROKER_TYPE} +MG_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 +MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${MG_MQTT_BROKER_HEALTH_CHECK} +MG_MQTT_ADAPTER_WS_TARGET_HOST=${MG_MQTT_BROKER_TYPE} +MG_MQTT_ADAPTER_WS_TARGET_PORT=8080 +MG_MQTT_ADAPTER_WS_TARGET_PATH=${MG_NATS_WS_TARGET_PATH} +``` + +### RabbitMQ configuration + +```yaml +services: + rabbitmq: + image: rabbitmq:3.12.12-management-alpine + container_name: magistrala-rabbitmq + restart: on-failure + environment: + RABBITMQ_ERLANG_COOKIE: ${MG_RABBITMQ_COOKIE} + RABBITMQ_DEFAULT_USER: ${MG_RABBITMQ_USER} + RABBITMQ_DEFAULT_PASS: ${MG_RABBITMQ_PASS} + RABBITMQ_DEFAULT_VHOST: ${MG_RABBITMQ_VHOST} + ports: + - ${MG_RABBITMQ_PORT}:${MG_RABBITMQ_PORT} + - ${MG_RABBITMQ_HTTP_PORT}:${MG_RABBITMQ_HTTP_PORT} + networks: + - magistrala-base-net +``` + +### Redis configuration + +```yaml +services: + redis: + image: redis:7.2.4-alpine + container_name: magistrala-es-redis + restart: on-failure + networks: + - magistrala-base-net + volumes: + - magistrala-broker-volume:/data +``` diff --git a/docker/brokers/README.md b/docker/brokers/README.md deleted file mode 100644 index f2f6314c3c..0000000000 --- a/docker/brokers/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Brokers Docker Compose - -Magistrala supports configurable MQTT broker and Message broker. - -## MQTT Broker - -Magistrala supports VerneMQ and Nats as an MQTT broker. - -## Message Broker - -Magistrala supports NATS and RabbitMQ as a message broker. - -## Profiles - -This directory contains 4 docker-compose profiles for running Magistrala with different combinations of MQTT and message brokers. - -The profiles are: - -- `vernemq_nats` - VerneMQ as an MQTT broker and Nats as a message broker -- `vernemq_rabbitmq` - VerneMQ as an MQTT broker and RabbitMQ as a message broker -- `nats_nats` - Nats as an MQTT broker and Nats as a message broker -- `nats_rabbitmq` - Nats as an MQTT broker and RabbitMQ as a message broker - -The following command will run VerneMQ as an MQTT broker and Nats as a message broker: - -```bash -MG_MQTT_BROKER_TYPE=vernemq MG_MESSAGE_BROKER_TYPE=nats make run -``` - -The following command will run VerneMQ as an MQTT broker and RabbitMQ as a message broker: - -```bash -MG_MQTT_BROKER_TYPE=vernemq MG_MESSAGE_BROKER_TYPE=rabbitmq make run -``` diff --git a/docker/brokers/docker-compose.yml b/docker/brokers/docker-compose.yml deleted file mode 100644 index 419b3b6815..0000000000 --- a/docker/brokers/docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This file configures Magistrala brokers. Magistrala uses two types of brokers: -# 1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. -# 2. MESSAGE_BROKER: Manages communication between adapters and Magistrala writer services. -# -# MQTT_BROKER can be either 'vernemq' or 'nats'. -# MESSAGE_BROKER can be either 'nats' or 'rabbitmq'. -# -# Each broker has a unique profile for configuration. The available profiles are: -# - vernemq_nats: Uses 'vernemq' as MQTT_BROKER and 'nats' as MESSAGE_BROKER. -# - vernemq_rabbitmq: Uses 'vernemq' as MQTT_BROKER and 'rabbitmq' as MESSAGE_BROKER. -# - nats_nats: Uses 'nats' as both MQTT_BROKER and MESSAGE_BROKER. -# - nats_rabbitmq: Uses 'nats' as MQTT_BROKER and 'rabbitmq' as MESSAGE_BROKER. -# - -include: - - path: brokers/profiles/nats.yml - env_file: docker/.env - -services: - vernemq: - image: magistrala/vernemq:${MG_RELEASE_TAG} - container_name: magistrala-vernemq - restart: on-failure - environment: - DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${MG_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} - DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${MG_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} - networks: - - magistrala-base-net - volumes: - - magistrala-mqtt-broker-volume:/var/lib/vernemq - profiles: - - vernemq_nats - - vernemq_rabbitmq - - broker: - extends: - file: brokers/nats.yml - service: broker - container_name: magistrala-broker - restart: on-failure - networks: - - magistrala-base-net - volumes: - - magistrala-broker-volume:/data - profiles: - - vernemq_nats - - vernemq_rabbitmq - - nats_rabbitmq diff --git a/docker/brokers/nats.yml b/docker/brokers/nats.yml deleted file mode 100644 index 264a5f3d2a..0000000000 --- a/docker/brokers/nats.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -services: - broker: - image: nats:2.10.9-alpine - command: "--config=/etc/nats/nats.conf" - volumes: - - ./../nats/:/etc/nats - environment: - - MG_NATS_PORT=${MG_NATS_PORT} - - MG_NATS_HTTP_PORT=${MG_NATS_HTTP_PORT} - - MG_NATS_JETSTREAM_KEY=${MG_NATS_JETSTREAM_KEY} - ports: - - ${MG_NATS_PORT}:${MG_NATS_PORT} - - ${MG_NATS_HTTP_PORT}:${MG_NATS_HTTP_PORT} diff --git a/docker/brokers/profiles/README.md b/docker/brokers/profiles/README.md deleted file mode 100644 index 502e22ce25..0000000000 --- a/docker/brokers/profiles/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Nats Docker Profiles - -This directory contains the docker-compose profiles for running Nats as an MQTT broker. It is separated from the main profile at `../docker-compose.yml` because of name conflicts with the Nats message broker. - -The configuration is the same as for the main profile, except that the MQTT broker is set to `nats` instead of `vernemq`. - -The profiles are: - -- `nats_nats.yml` - Nats as an MQTT broker and Nats as a message broker -- `nats_rabbit.yml` - Nats as an MQTT broker and RabbitMQ as a message broker - -They are automatically included in the main profile, so you can run them depending on the profile you want to use: - -The following command will run Nats as an MQTT broker and Nats as a message broker: - -```bash -MG_MQTT_BROKER_TYPE=nats MG_MESSAGE_BROKER_TYPE=nats make run -``` - -The following command will run Nats as an MQTT broker and RabbitMQ as a message broker: - -```bash -MG_MQTT_BROKER_TYPE=nats MG_MESSAGE_BROKER_TYPE=rabbit make run -``` diff --git a/docker/brokers/profiles/nats.yml b/docker/brokers/profiles/nats.yml deleted file mode 100644 index 3f747cfcd2..0000000000 --- a/docker/brokers/profiles/nats.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This file is used to configure NATS broker. -# It used when running nats both as an MQTT and Message broker. -services: - nats: - extends: - file: brokers/nats.yml - service: broker - container_name: magistrala-nats - restart: on-failure - networks: - - magistrala-base-net - volumes: - - magistrala-broker-volume:/data - profiles: - - nats_nats - - nats_rabbitmq diff --git a/docker/brokers/rabbitmq.yml b/docker/brokers/rabbitmq.yml deleted file mode 100644 index 109f7df186..0000000000 --- a/docker/brokers/rabbitmq.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -services: - broker: - image: rabbitmq:3.12.12-management-alpine - environment: - RABBITMQ_ERLANG_COOKIE: ${MG_RABBITMQ_COOKIE} - RABBITMQ_DEFAULT_USER: ${MG_RABBITMQ_USER} - RABBITMQ_DEFAULT_PASS: ${MG_RABBITMQ_PASS} - RABBITMQ_DEFAULT_VHOST: ${MG_RABBITMQ_VHOST} - ports: - - ${MG_RABBITMQ_PORT}:${MG_RABBITMQ_PORT} - - ${MG_RABBITMQ_HTTP_PORT}:${MG_RABBITMQ_HTTP_PORT} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c64b3204b7..8a54d49ab7 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,19 +12,12 @@ volumes: magistrala-users-db-volume: magistrala-things-db-volume: magistrala-things-redis-volume: - magistrala-mqtt-broker-volume: magistrala-broker-volume: - magistrala-es-volume: + magistrala-mqtt-broker-volume: magistrala-spicedb-db-volume: magistrala-auth-db-volume: magistrala-invitations-db-volume: -include: - - path: brokers/docker-compose.yml - env_file: docker/.env - - path: es/docker-compose.yml - env_file: docker/.env - services: spicedb: image: "authzed/spicedb:v1.29.0" @@ -259,6 +252,7 @@ services: - mqtt-adapter - http-adapter - ws-adapter + - coap-adapter things-db: image: postgres:16.1-alpine @@ -292,6 +286,8 @@ services: depends_on: - things-db - users + - auth + - nats restart: on-failure environment: MG_THINGS_LOG_LEVEL: ${MG_THINGS_LOG_LEVEL} @@ -394,6 +390,8 @@ services: container_name: magistrala-users depends_on: - users-db + - auth + - nats restart: on-failure environment: MG_USERS_LOG_LEVEL: ${MG_USERS_LOG_LEVEL} @@ -474,7 +472,8 @@ services: container_name: magistrala-mqtt depends_on: - things - - broker + - vernemq + - nats restart: on-failure environment: MG_MQTT_ADAPTER_LOG_LEVEL: ${MG_MQTT_ADAPTER_LOG_LEVEL} @@ -525,7 +524,7 @@ services: container_name: magistrala-http depends_on: - things - - broker + - nats restart: on-failure environment: MG_HTTP_ADAPTER_LOG_LEVEL: ${MG_HTTP_ADAPTER_LOG_LEVEL} @@ -570,7 +569,7 @@ services: container_name: magistrala-coap depends_on: - things - - broker + - nats restart: on-failure environment: MG_COAP_ADAPTER_LOG_LEVEL: ${MG_COAP_ADAPTER_LOG_LEVEL} @@ -620,7 +619,7 @@ services: container_name: magistrala-ws depends_on: - things - - broker + - nats restart: on-failure environment: MG_WS_ADAPTER_LOG_LEVEL: ${MG_WS_ADAPTER_LOG_LEVEL} @@ -660,6 +659,36 @@ services: bind: create_host_path: true + vernemq: + image: magistrala/vernemq:${MG_RELEASE_TAG} + container_name: magistrala-vernemq + restart: on-failure + environment: + DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${MG_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} + DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${MG_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} + networks: + - magistrala-base-net + volumes: + - magistrala-mqtt-broker-volume:/var/lib/vernemq + + nats: + image: nats:2.10.9-alpine + container_name: magistrala-nats + restart: on-failure + command: "--config=/etc/nats/nats.conf" + environment: + - MG_NATS_PORT=${MG_NATS_PORT} + - MG_NATS_HTTP_PORT=${MG_NATS_HTTP_PORT} + - MG_NATS_JETSTREAM_KEY=${MG_NATS_JETSTREAM_KEY} + ports: + - ${MG_NATS_PORT}:${MG_NATS_PORT} + - ${MG_NATS_HTTP_PORT}:${MG_NATS_HTTP_PORT} + volumes: + - magistrala-broker-volume:/data + - ./nats:/etc/nats + networks: + - magistrala-base-net + ui: image: magistrala/ui:${MG_RELEASE_TAG} container_name: magistrala-ui diff --git a/docker/es/docker-compose.yml b/docker/es/docker-compose.yml deleted file mode 100644 index 719b2c06a3..0000000000 --- a/docker/es/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -volumes: - magistrala-es-redis-volume: - -services: - es-redis: - image: redis:7.2.4-alpine - container_name: magistrala-es-redis - restart: on-failure - networks: - - magistrala-base-net - volumes: - - magistrala-es-volume:/data - profiles: - - redis diff --git a/docker/policy/model.conf b/docker/policy/model.conf deleted file mode 100644 index 3d2ce58974..0000000000 --- a/docker/policy/model.conf +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = ( g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) ) || r.sub == 'admin@example.com' From 5c88207125211ed8bec439ee65727343d6689f0f Mon Sep 17 00:00:00 2001 From: Sammy Kerata Oina <44265300+SammyOina@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:54:47 +0300 Subject: [PATCH 36/71] NOISSUE - Replace deprecated gRPC metrics collection with stats handler (#281) Signed-off-by: SammyOina --- pkg/auth/connect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/auth/connect.go b/pkg/auth/connect.go index 7e90dff102..193501e7e4 100644 --- a/pkg/auth/connect.go +++ b/pkg/auth/connect.go @@ -101,7 +101,7 @@ func (c *client) Secure() string { // connect creates new gRPC client and connect to gRPC server. func connect(cfg Config) (*grpc.ClientConn, security, error) { opts := []grpc.DialOption{ - grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), } secure := withoutTLS tc := insecure.NewCredentials() From bd0f8599aec61c4607157d1026f200a250e8cd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Mon, 29 Jan 2024 18:25:34 +0100 Subject: [PATCH 37/71] NOISSUE - Remove CHANGELOG (#2067) Signed-off-by: Dusan Borovcanin --- CHANGELOG.md | 708 --------------------------------------------------- 1 file changed, 708 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index edd5e6c5d8..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,708 +0,0 @@ -# Magistrala Changelog - -## Generation -Magistrala release notes for the latest release can be obtained via: -``` -make changelog -``` - -Otherwise, whole log in a similar format can be observed via: -``` -git log --pretty=oneline --abbrev-commit -``` - -## 0.13.0 - 15. APR 2022. -### Features and Bugfixes -- NOISSUE - Update changelog for release 0.13.0 -- Update VerneMQ release (#1593) -- NOISSUE - Update changelog and readme for release 0.13.0 -- MF-1582 - Fix lora-adapter MQTT client (#1583) -- NOISSUE - Fix CoAP adapter (#1572) -- NOISSUE - Unify MG_INFLUX_READER_DB_HOST and MG_INFLUX_WRITER_DB_HOST envars (#1585) -- MF-1580 - Influxdb Writer changes format of update-time to string (#1581) -- MF-1575 Add 'Name' field to ListMembers response in things svc (#1576) -- MF-1565 - Document Bearer, Thing and Basic Authorization header (#1566) -- MF-1567 - Use Bearer, Thing or Basic scheme in Authorization header (#1568) -- MF-1348 - Add transport errors logging (#1544) -- NOISSUE - Add nats wrapper for COAP (#1569) -- MF-1469 - Indicate proper authentication scheme in Authorization header (#1523) -- MF-1240 - Return to service transport layer only service errors (#1559) -- Update dependencies (#1564) -- NOISSUE - Separate Keto hosts for read and write (#1563) -- MF-1551 - Fix Cobra usage commands and clean unnecessary struct types (#1558) -- MF-1257 - Access messages from readers endpoint with user access token (#1470) -- NOISSUE - Refactor MQTT subscriber (#1561) -- MF-1059 - Add TLS support for email (#1560) -- MF-1261 - Use StatusUnauthorized for authn and StatusForbidden for authz (#1538) -- NOISSUE - Fix auth members list response (#1555) -- MF-1263 - Move repeating errors to the separate package (#1540) -- NOISSUE - Add API keys functions to CLI (#1537) -- Fix SDK for group members (#1553) -- NOISSUE - Fix Swagger UI (#1552) -- MF-1008 - Make token duration configurable (#1550) -- MF-1308 - Use IETF Health Check standard (#1541) -- Fix user listing access control (#1546) -- Update dependencies (#1545) -- MF-1478 - TimescaleDB writer and reader add-on (#1542) -- MF-1149 - Add AsyncAPI MQTT API doc (#1539) -- MF-1535 - Add API keys functions to SDK (#1536) -- NOISSUE - Add view and list serials endpoints in certs service (#1483) -- MF-1516 - Fix API key issuing (#1530) -- NOISSUE - Add disconnect endpoint in nginx conf (#1528) -- NOISSUE - Add timestamp transformation rules for specifc JSON fields (#1514) -- MF-1425 - Support external UUIDs for Things and Channels (#1518) -- MF-1521 - Fix email headers (#1522) -- Fix SenML lib dependency version (#1519) -- Bump vernemq to 1.12.3 (#1520) -- NOISSUE - Remove auth URL from SDK (#1511) -- NOISSUE - Apply policies to Channels (#1505) -- remove dead code (#1503) -- NOISSUE - Fix listing (#1502) -- NOISSUE - Listing Policies (#1498) -- Fix standalone mode (#1497) -- MF-1489 - Add API for deleting policies (#1491) -- NOISSUE - Update group sharing policies (#1494) -- NOISSUE - Refactor InfluxDB Reader: explicit check event + add safe conversion (#1460) -- NOISSUE - Update users create command for CLI (#1495) -- NOISSUE - Update self register environment variable name (#1493) -- Bring back the job add -- NOISSUE - Fix assigning invalid group policy (#1487) -- MF-1443 - Add policies (#1482) -- NOISSUE - Fix retrieving all users (#1477) -- MF-1468 - Fix ThingsURL in Certs Service (#1474) -- NOISSUE - Refactor single-user mode (#1471) -- Fix UpdateChannelHandler for Redis producer (#1473) -- NOISSUE - Add SMPP notifier (#1464) -- NOISSUE - Update dependencies (#1453) -- NOISSUE - Fix security warnings for dependencies (#1452) -- Bump docker-compose version in prereq (#1449) -- NOISSUE - Fix bootstraping (#1448) -- MF 1413 - Use per-service URL in SDK (#1444) -- MF-1439 - Add support for Basic Authentication in HTTP Adapter (#1441) -- MF-1421 - Make flattening of JSON transformer only available on InfluxDB (#1432) -- NOISSUE - Update the /disconnect endpoint HTTP method as PUT (#1438) -- MF-1389 - Add /disconnect endpoint in Things service (#1433) -- NOISSUE - Fix httputil implementation in users service (#1434) -- Fix fetching user members of an empty group (#1436) -- Change to user friendly docs urls (#1430) -- NOISSUE - Use github action for showing OpenAPI spec with Swagger UI (#1427) -- Fix JSON Transformer empty format handling (#1429) -- Update README -- NOISSUE - Update docker-compose images to latest release (#1419) -- MF-1378 - Update dependencies (#1379) - -## 0.12.1 - 05. MAY 2021. -### Features and Bugfixes -- NOISSUE - Refactor SDK memberships and fix openapi for memberships. -- NOISSUE - Fix incorrect influxdb credentials -- MF-1408 - Fix error handling for Thing update SQL(#1408) -- MF-1288 - Add tests for JSON messages in message writers and readers -- NOISSUE - Fix Postgres Reader order -- NOISSUE - Fix nginx configuration for groups -- NOISSUE - Add tests and connection route-map to lora-adapter -- MF-1403 - Change vernemq building source revision -- NOISSUE - Rm content-type check from list endpoint - -## 0.12.0 - 29. MAR 2021. -### Features and Bugfixes -- MF-1394 - SDK groups (#1396) -- NOISSUE - fix response for passwd endpoints (#1393) -- NOISSUE - dont retrieve groups (#1392) -- MF-1368 - Add internal http api package for query params reading (#1384) -- MF-1390 - Fix docker-compose env_file (#1391) -- NOISSUE - put order direction in response body (#1387) -- NOISSUE - Certs service refactor (#1369) -- MF-1357 - Add new endpoint for searching things (#1383) -- NOISSUE - Add missing auth port in nginx enrypoint.sh (#1380) -- MF-1346 - Create Groups API - add grouping of entities (#1334) -- NOISSUE - Fix certs and vault deployment, reorganize and remove unnecessary vars (#1368) -- MF-1317 - Configurable regexp rule for password (#1355) -- Fix CoAP Adapter README (#1376) -- NOISSUE - Fix default values for port and x509 provision (#1367) -- NOISSUE - Added missing endpoints for users service (#1372) -- MF-1365 - Add ADOPTERS.md file (#1371) -- Fix grpc endpoint parameter permutation (#1370) -- NOISSUE - Add IsChannelOwner grpc endpoint (#1366) -- MF-1362 - Sort Things and Channels connections by name (#1363) -- MF-1314 - Add value comparison filters for readers (#1353) -- Fix env configuration and documentation (#1360) -- NOISSUE - Support disabling Email Agent authentication (#1356) -- NOISSUE - Upgrade Mongo, Cassandra and Influx docker images (#1354) -- NOISSUE - Add READMEs to pkg packages (#1352) -- NOISSUE - Correct README (#1349) -- MF-1342 - Use environment variables in docker-compose to use tagged version of image (#1343) -- MF-1311 - Add Notifications service (#1324) -- MF-1344 - Fix links to API documentations #1345 -- NOISSUE - Upgrade influxdb and postgres docker images (#1341) -- NOISSUE - Revert cli to use user token from command args (#1339) -- MF-1276 - Fix openapi IDs and Keys format (#1338) -- MF-1061 - Add PageMetadata to readers (#1333) -- NOISSUE - Fix run script and compiler warnings (#1336) -- Fix Postgres writer transaction handling (#1335) -- Make Transformer type configurable (#1331) -- MF-1061 - Implement v, vb, vs, vd and from/to mongodb-reader filters (#1326) -- NOISSUE - Rename package aliases uuidProvider into uuid (#1323) -- MF-1034 - Wrapping MQTT client (#1318) -- MF-1061 - Fix cassandra-reader count for json format (#1327) -- MF-1061 - Implement v, vb, vs, vd and from/to cassandra-reader filters (#1325) -- NOISSUE - Switch to Consumers interface (#1316) -- MF-1061 - Implement protocol, name, v, vb, vs, vd and from/to Postgres reader… (#1322) -- MF-1061 - Add name, protocol and publisher tests to influxdb-reader (#1320) -- NOISSUE - Fix Auth typo (#1319) -- NOISSUE - Add health check for MQTT broker (#1305) -- MF-1264 - Add support for JSON readers (#1295) -- NOISSUE - Merge authz and authn into new service auth (#1313) -- MF-1061 - Implement InfluxDB filters value, v, vb, vs, vd, from, to (#1312) -- NOISSUE - Correct readers openapi.yml (#1310) -- NOISSUE - Fix MQTT Forwarder client id (#1309) -- NOISSUE - Fix dates not being init properly on save, change path construction, replace UUID with ULID for group ID (#1300) -- NOISSUE - Remove authz from docker comp (#1307) -- Shorten descriptions and add formats (#1306) -- NOISSUE - remove owner id from user table and object (#1303) -- NOISSUE - Add missing fields to openapi specs and enclose http codes in single quotes (#1302) -- MF-1290 - Sort Things and Channels by name (#1293) -- MF-1248 - Add access policies for users (#1246) -- Fixes, without spaces. (#1296) -- Add different CNs for CA and certs (#1292) -- MF-397 - Introduce Thing Groups (#1259) -- Add Enhancement section to the issue template (#1284) -- Fix hardcoded env var values (#1283) -- NOISSUE - Improve AuthN service docs (#1282) -- MF-1268 - CLI improvements (#1274) -- NOISSSUE - Vault integration as an addon. (#1266) -- Fix naming in Authn API tests (#1275) -- MF-1244 - Return UserID alongside with user Email in Identify response (#1245) -- NOISSUE - Fix ViewGroup and UpdateGroup (#1269) -- NOISSUE - Add ListUsers, ViewUser and ViewProfile methods (#1262) -- NOISSUE - Rm users http package (#1256) -- NOISSUE - Remove content-type check from decodeListUserGroupsRequest (#1255) -- NOISSUE - Migrate swaggers to openapi 3 spec (#1250) -- Update MQTT Broker Docker scripts (#1253) -- update mproxy version (#1251) -- NOISSUE - Fix group retrieval when parent id is not specified (#1247) -- NOISSUE - Add new endpoint to retrieve configuration to be used as a template. (#1242) -- NOISSUE - Add user groups (#1228) -- MF-1237 - Return to transport only things service errors (#1236) -- MF-928 - Change CoAP lib (#1233) -- NOISSUE - Simplify make cleandocker (#1230) -- NOISSUE - Fix malformed Swagger API specs (#1229) -- MF-435 - Add support for env file loading (#1223) -- update certs docs (#1227) -- NOISSUE - Fix certs update in bootstrap config and make content handling in config.toml user friendly (#1221) -- NOISSUE - Fix typo in authorization.js (#1226) -- MF-983 - Add HTTP query param to connections list endpoints to fetch disconnected Things or Channels (#1217) -- MF-1179 - Add a certificate service and certs endpoint to SDK (#1188) -- NOISUE - Fix cache error when key is not in Redis (#1220) -- MF-1199 - Add NATS messaging tests (#1209) -- NOISSUE: Fix emailer (#1219) -- NOISSUE - Update dependencies (#1218) -- NOISSUE - Add subtopic wildcard for twin attribute's definition (#1214) -- fix envs for nginx (#1215) -- Remove twin mqtt related obsolete var and fix es-redis address (#1213) -- NOISSUE - Remove unused `MG_THINGS_SECRET` env var (#1211) -- NOISSUE - Fix some typos (#1212) -- NOISSUE - Remove unknown Bootstrap requests (#1210) -- NOISSUE - Use `pgcrypto` instead `uuid-ossp` for UUIDs generation (version 4) (#1208) -- MF-1198 - Add errors package tests (#1207) -- MF-1025 - timeout env in sec, use parseduration (#1206) -- MF-1201 - Fix MG_THINGS_AUTH_GRPC_URL mongo reader ENVAR (#1203) -- NOISSUE - Fix CI (#1204) -- MF-1180 - Add redis based twins and states cache (#1184) -- MF-739 - Add ID to the User entity (#1152) -- NOISSUE - Fix default db name for storage databases (#1194) -- NOISSUE - Add `MG_DOCKER_IMAGE_NAME_PREFIX` to Makefile (#1173) -- MF-1154 - Move UUID provider to project root (#1172) -- Fix typo in error messages (#1193) -- MF-1190 - Add pkg for library packages (#1191) -- MF-1177 - Implement caching in MQTT adapter (#1187) -- NOISSUE - Refactor provision tool (#1189) - -## 0.11.0 - 29. MAY 2020. -### Features and Bugfixes -- Add VerneMQ docker image build from source (#1178) -- MF-994 - Add tracing middleware for twins and states repos (#1181) -- MF-995 - Add Twins tests for endpoint list twins and list states (#1174) -- NOISSUE - Update dependencies (#1176) -- MF-1163 - Fix influxdb-reader to use nanoseconds precision (#1171) -- Rename environment variable MG_MQTT_ADAPTER_PORT to MG_MQTT_ADAPTER_MQTT_PORT in docker environment (#1170) -- Remove thing related code from twins service (#1169) -- MF-997 - Add twins service swagger file (#1167) -- MF-1079 - Add MQTT forwarder (#1164) -- MF-1159 - add gateway metadata update in provision method (#1160) -- MF-1055 - rollback/release transaction on error (#1166) -- NOISSUE - Use log level error for VermeMQ docker (#1162) -- NOISSUE - Fix default nats pubsub subject (#1153) -- MF-1125 - Document Provision service (#1143) -- NOISSUE - Fix bootstrap SDK args naming (#1151) -- Use VerneMQ default log level (#1150) -- NOISSUE - Update provision service (#1133) -- NOISSUE - Refactor messaging (#1141) -- Add JSON tags to SDK entities (#1146) -- NOISSUE - Update CLI README.md (#1139) -- NOISSUE - Update mProxy version (#1137) -- fix nginx, channel connect (#1136) -- Remove concurrency flag for golangci-lint (#1134) -- MF-1088 - Remove message payload content type (#1121) -- MF-1129 - Use snake_case for Lora and OPC-UA metadata fields (#1130) -- MF-1128 - Add golangci-linter to a CI script (#1131) -- MF-1123 - Move Provision service to monorepo (#1132) -- MF-845 - Add FOSSA badge for licensing (#1127) -- MF-1087 - Remove WebSocket adapter (#1120) -- NOISSUE - Use HTTP Status in SDK error messages (#1119) -- NOISSUE - Fix bootstrap token naming and interfaces named args (#1117) -- MF-1115 - Improve the SDK error encoding (#1118) -- MF-862 - Add boostrap CRUD to SDK and CLI (#1114) -- NOISSUE - Update coding style in Things service (#1116) -- NOISSUE - Remove defers from TestMain (#1111) -- NOISSUE - Create func to encode SDK errors (#1110) -- MF-1078 - Add timestamp to published messages and use it in Transformer (#1106) -- Fix prometheus namespace in postgres reader & writer (#1109) -- NOISSUE - Implement errors package in senml transformer, readers and writers (#1108) -- NOISSUE - Implement errors package in Authentication service (#1105) -- MF-1103 - API key should ignore empty expiration time (#1104) -- MF-1096 - Fix AuthN and Things Auth ENVARS (#1066) -- fix Contains function for nil arguments (#1102) -- MF-1099 - Add email subdomain validator (#1101) -- MF-1091 - Use channels. as broker prefix (#1098) -- MF-1090 - Use named Interfaces args (#1097) -- NOISSUE - Create broker package for NATS (#1080) -- NOISSUE - Implement errors package in bootstrap service (#1093) -- NOISSUE - Fix writers loadSubjectsConfig if file is missing (#1094) -- NOISSUE - Adding subtopics filtering in writer services (#1072) -- NOISSUE - Improve errors package (#1086) -- NOISSUE - Enable MQTT over WS in docker composition (#1085) -- NOISSUE - Rm unused opc-ua envars (#1083) -- MF-798 - Add utf8 support for email validation (#1082) -- Remove unused Tokenizer interface (#1084) -- Update mqtt adapter imports (#1081) -- NOISSUE - Update state based on SenML time value (#1075) -- NOISSUE - Fix StatusBadDecodingError for opc-ua browse (#1074) -- Save senml array msg to multiple states (#1073) -- NOISSUE - Fix opc-ua message type handling (#1071) -- NOISSUE - Add Publisher field to MQTT adapter (#1067) -- NOISSUE - Fix users CLI (#1062) -- NOISSUE - Fix SDK Messages response (#1064) -- Merged MQTT docker compose in core composition file (#1060) -- MF-1016 - Add UserUpdate and UpdatePassword to sdk and CLI (#1057) -- Update mProxy (#1058) -- MF-1053 - Add disconnect event to MQTT adapter (#1056) -- Fix data type for data_value in databases (#1054) -- NOISSUE - Fix opc-ua subscriptions store (#1052) -- NOISSUE - Fix connect CLI command and remove ConnectThing func from SDK (#1051) -- NOISSUE - Update Vernemq image repository (#1050) -- Removed VerneMQ auth plugin, Aedes impl. Added mproxy support in docker (#1049) -- NOISSUE - Add default subscription nodeID and Interval ENVAR (#1046) -- MF-415 - Merge mProxy support (#1045) -- NOISSUE - Remove twins-service mqtt dependency and publish notifs to nats (#1042) -- Add arbitrary SenML value type saving to twin state (#1039) -- Fixed Aedes dependencies (#1036) -- MF-998 - Add Twins service to Makefile and docker-compose.yml (#1035) -- MF-1032 - Fix redis docker volume of opcua-adapter (#1033) -- NOISSUE - add nats conf (#1031) -- MF-442 - Add SSL encryption to the MongoDB, InfluxDB and Cassanda readers (#1024) -- NOISSUE - Add opc-ua type handling and unsubscription (#1029) -- NOISSUE - Add aggregate attribute-based search for twin retrieval (#1027) -- NOISSUE - Fix metadata in add Things endpoint (#1028) -- NOISSUE - Fix minimal password length (#1023) -- MF-1020 - Change default password for CLI provision test (#1021) -- NOISSUE - Add subtopic to opcua messages (#1022) -- NOISSUE - Add details to browsed OPC-UA nodes (#1019) -- NOISSUE Fix obsolete attribute persistance (#1018) -- Fix twins update revision counter (#1011) -- Fixed docs instructions in README (#1010) -- Fix copyright year (#1009) -- Fix issuing recovery key (#1007) -- Removed gatling load-test (#1005) -- Removed old k8s manifests (#1004) -- NOISSUE - Remove UI from docker-compose (#1001) -- NOISSUE - Store successfull OPC-UA subscriptions (#999) -- MF-730 - Add digital twin service for things (#855) -- Fix Redis event naming (#996) -- NOISSUE - Add a Browse endpoint in opcua-adapter (#988) -- NOISSUE - Add Redis ES Username/Pass for VerneMQ (#991) -- MF-982 - Add error when connecting empty channels or things (#985) - -## 0.10.0 - 17. DEC 2019. -### Features -- MF-932 - User API keys (#941) -- NOISSUE - Use opcua server timestamp in opcua-adapter messages (#980) -- Simplify CI script (#979) -- NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID (#975) -- Move docs to a separate repo (#976) -- NOISSUE - Support multiple types values in opcua-adapter (#973) -- Migrate from dep to go modules (#971) -- NOISSUE - Add Node IdentifierType config in opcua-adapter (#967) -- NOISSUE - Remove messages limit in influxdb-reader (#968) -- MF-898 - Add bulk connect to CLI and SDK (#956) -- MF-538 - Improve logging and API errors (#866) -- NOISSUE - Remove Elm UI (#953) -- MF-898 - Add bulk connections endpoint (#948) -- MF-898 - Change thing's service to use bulk connect (#946) -- MF-898 - Add transactions to postgres connect (#940) -- Add missing user service tests (#945) -- Remove Normalizer service from compose (#937) -- MF-919 - Magistrala message updates (#924) -- NOISSUE - Remove ARM multi-arch images (#929) -- MF-906 - Change single creation endpoints to use bulk service calls (#927) -- MF-922 - Add UpdateUser endpoint (#923) -- MF-780 - Use Normalizer as a lib (#915) -- NOISSUE - Switch to grpcbox for VerneMQ (#914) -- Change channels to chs (#918) -- MF-484 - Add bulk provisioning for things and channels (#889) -- MF-899 - Update README and official docs (#910) -- NOISSUE - Fix Redis envars (#903) -- Add disconnect on gen_server terminate() (#913) -- MF-890 - Add OPC-UA docs (#904) -- NOISSUE - Update Protobuf version (#902) -- MF-886 - Add OPC-UA adapter (#878) -- MF-532 - Password reset (#873) -- MF-785 - Change CanAccess to CanAccessByKey (#894) -- NOISSUE - Add MQTT UserName check on register and InstanceId in Redis (#884) -- Add MQTT troubleshooting section (#882) -- MF-875 - Add tracing to official documentation (#877) -- MF-788 - Remove date and minimize copyright comments (#876) -- MF-787 - Add tags to user, thing, and channel spans (#869) -- Update docker-compose version for addons (#874) -- MF-859 - Channels metadata search (#867) -- MF-858 Users metadata (#861) -- NOISSUE - Simplify MQTT benchmarking tool (#852) -- NOISSUE - Upgrade Go version to 1.13 in container images (#868) -- MF-820 - Fetch messages for a particular device (#843) -- Update gorilla websocket version (#865) -- NOISSUE - Update aedes version and fix Dockerfile (#863) -- NOISSUE - Search by metadata (#849) -- MF-846 - Install python in docker build for aedes mqtt image (#860) -- NOISSUE - Clean NginX files, move .gitignores to dirs (#853) -- NOISSUE - Add docker-compose for MQTT cluster (#841) -- Add debug logs to the WS adapter (#848) -- NOISSUE - Add measuring time from pub to sub (#839) -- NOISSUE - update mqtt prov tool and some refactor (#831) -- NOISSUE - Use Thing ID to update certs data (#827) -- NOISSUE - Improve VerneMQ plugin code, add configurable gRPC pool size (#836) -- NOISSUE - Use gRPC for VerneMQ (#835) -- Switch secure of WS connection according to secure of http connection of UI (#829) -- NOISSUE - Use current hostname instead of localhost for a WebSocket connection in the UI (#826) -- NOISSUE - Improve MQTT benchmarking tools (#828) -- NOISSUE - update mqtt benchmark (#824) -- Add encryption key to env vars table (#823) -- NOISSUE - Add version endpoint to MQTT adapter (#816) -- MF-295 add mqtt benchmark tool (#817) -- update mqtts commands (#815) -- NOISSUE - Support encrypted bootstrap (#796) -- Add config to writers docs (#812) -- NOISSUE - Add VerneMQ support (#809) -- NOISSUE - Add content type as part of MQTT subscription topic (#810) - -### Bugfixes -- Fix MQTT protobuf filename(#981) -- MF-950 - Runtime error in normalizer - CBOR SenML (#974) -- NOISSUE - Fix opcua-adapter events warnings (#965) -- NOISSUE - Fix opcua-adapter events decode (#951) -- Fix subtopic handling in VerneMQ (#962) -- NOISSUE - Fix Update User (#959) -- NOISSUE - Fix make dockers (#957) -- Add dev_ back to make dockers_dev (#955) -- NOISSUE - Fix docs (#952) -- MF-916 - Fix Things and Channels counters (#947) -- MF-942 - Fix email template logic (#944) -- NOISSUE - Fix HTTP header for Things and Channels creation (#939) -- NOISSUE - Fix docker ui image name (#938) -- NOISSUE - Fix lora-adapter (#936) -- NOISSUE - Fix lora creation events (#933) -- Fix doc for ENV vars in README (#920) -- Fix compilation (#911) -- Revert "NOISSUE - Make event sourcing optional (#907)" (#909) -- NOISSUE - Make event sourcing optional (#907) -- NOISSUE - Fix InfluxDB env vars (#908) -- Fix Elm version for ARM Docker images (#905) -- Fix Elm version in Dockerfile (#901) -- NOISSUE - fix security doc (#897) -- NOISSUE - Fix typo in docs and README (#891) -- Fix Nginx mTLS configuration (#885) -- Fix provision tool connect error handling (#879) -- Fix: Correct 404 and Content-Type Issues in MQTT Version Endpoint (#837) -- NOISSUE - Fix proto files in VerneMQ (#834) -- NOISSUE - Fix hackney HTTP request (#833) -- Add socket pool and fix pattern matching (#830) -- Fix typo (#814) - -## 0.9.0 - 19. JUL 2019. -### Features -- Create and push docker manifest for new release from Makefile (#794) -- MF-399 - Add open tracing support (#782) -- MF-783 - Allow access checking by a thing ID (#784) -- NOISSUE - Add authorization HTTP API to things service (#772) -- Remove cli executable from repo (#776) -- NOISSUE - Use .env vars in docker-compose (#770) -- MF-663 - enable nginx port conf from docker env (#769) -- Update docs (#766) -- NOISSUE - Remove installing non-existent package in ci (#758) -- NOISSUE - Add searchable Channels name (#754) -- MF-466 - ARM docker deployment (#756) -- Add missing Websocket.js into docker ui image (#755) -- NOISSUE - Add searchable Things name (#750) -- NOISSUE - Add certificate fields to the Bootstrap service (#752) -- Update grpc and protobuf deps in mqtt adapter (#751) -- MF-742 - Things to support single user scenario (#749) -- MF-732 - Add Postgres reader (#740) -- MF-722 - Change UUID lib (#746) -- Add performance improvement to writer filtering (#744) -- NOISSUE - Update nginx version (#748) -- MF-574 - Add missing environment variables to Cassandra writer (#745) -- NOISSUE - Add compile test to CI (#743) -- MF-708 - Assign Writer(s) to a channel (#737) -- MF-732 - Add PostgreSQL writer (#733) -- NOISSUE - Add readers pagination in SDK (#736) -- Add UI websocket open/close and send/receive (#728) -- MF-707 - Allow custom Thing key (#726) -- MF-525 - Add pagination response to the readers (#729) -- NOISSUE - Rm Things type from lora-adapter (#727) -- skip deleting of persistent volumes by default (#723) -- MF-488 - Remove Thing type (app or device) (#718) -- Remove empty channels check (#720) -- MF-655 Proper usage of docker volumes (#657) -- NOISSUE - Improve UI styling (#719) -- MF-715 - Conflict on updating connection with a valid list of channels (#716) -- MF-711 - Create separate Redis instance for ES (#717) -- NOISSUE - Update event fields naming (#713) -- MF-698 - Add missing info and docs about sys event sourcing (#712) -- MF-549 - Change metadata format from JSON string to JSON object (#706) -- NOISSUE - Replace repeating code by card gen func (#697) -- Update Bootstrap service docker-compose.yml (#700) -- Remove Debug function (#699) -- MF-687 - Add event sourcing to Bootstrap service (#695) -- NOISSUE - Remove debugging message from response of handle error function (#696) -- Add event stream to MQTT adapter for conn status (#692) -- NOISSUE - Improve UI style (#691) -- Update docs structure (#686) -- Use images instead of carousel (#685) -- NOISSUE - Update docs (#683) -- MF-662 - Change menu style (#678) -- MF-651 - X509 Mutual TLS authentication (#676) -- Update Aedes version for MQTT adapter (#677) -- MF-661 - Bootstrap pagination in UI (#672) -- Update subtopics section in documentation (#670) -- Remove default base URL value (#671) - -### Bugfixes -- NOISSUE - Fix Readers logs (#735) -- NOISSUE - Fix Docker for ARM (#760) -- NOISSUE - Fix count when search by name is performed (#767) -- NOISSUE - Typo fix (#777) -- NOISSUE - Fix Postgres logs in Things service (#734) -- Fix CI with fixed plugin versions (#747) -- fix building problems (#741) -- fix docker-compose env (#775) -- Fix MG_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781) -- Fix MQTT raw message deserialization (#753) -- fix variant option for manifest annotate (#765) -- fix to makefile for OSX/Darwin (#724) -- Fix .dockerignore file by removing index.html (#725) -- Fix things and channels metadata create and edit & remove thing type (#721) -- Fix Bootstrap service event map keys (#705) -- Fix logging in publish event callback (#694) -- Fix InfluxDB time bug (#689) -- Fix users service to work in offline mode (#795) -- fix mainflux_id parameter in bootstrap swagger (#789) -- Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679) -- Use errors and null packets in authorized pub/sub (#773) -- NOISSUE - Fix CoAP adapter (#779) - - -### Summary -https://github.com/absmach/magistrala/milestone/10?closed=1 - -## 0.8.0 - 20. MAR 2019. -### Features -- MF-571 - Add Env.elm to set custom base URL (#654) -- NOISSUE Added docs about docker-compose config overriding (#653) -- MF-539 - Improve Bootstrap Service documentation (#646) -- MF-596 - Add subtopic to RawMessage (#642) -- NOISSUE - Prevent infinite loop in lora-adapter if Redis init fail (#647) -- Corrected grammar and rephrased a few sentences to read nicely (#641) -- MF-571 - Elm UI (#632) -- MF-552 - Use event sourcing to keep Bootstrap service in sync with Things service (#603) -- MF-540 - Add pagination in API responses for Bootstrap service (#575) -- MF-600 - Handle custom LoRa Server application decoder (#608) -- update docker-compose (#590) -- Update generated code (#602) -- Add generated files check (#601) -- MF-597 - Removed legacy code as not needed anymore (#598) -- NOISSUE - Added normalizer service to run script (#594) -- Changed RawMessage (#587) -- NOISSUE - fix CLI log (#581) -- MF-519 - Refine Message (#567) -- NOISSUE - Add name field for Bootstrap Config (#564) -- Fix non-SenML message routing in normalizer (#573) -- NOISSUE - Update authors list (#569) -- Update lora.md (#568) -- NOISSUE- Improve LoRa doc (#562) -- MF-551 - Add metadata fields to Bootstrap Channels (#563) -- Fix MQTT adapter by setting subscription queue (#561) -- MF-558 - Add MQTT subtopics documentation (#559) -- Fix regexp for SUB (#557) -- Simplify MQTT topipc regexp (#555) -- MF-429 -Enabled MQTT subtopic's (#554) -- Add env var for number of concurrent messages (#545) -- NOISSUE - Update doc and fix empty key bug (#544) -- MF-370 - Simplify and refine CI (#541) -- NOISSUE - Add connection commands to CLI (#542) -- NOISSUE - Refine docs (#537) -- Update licnese year (#533) -- MF-513 - Add Bootstrapping service (#524) -- Add dedicated env vars for event sourcing (#536) -- NOISSUE - Fix docs (#535) -- Add lora doc to getting-started.md (#529) -- MF-483 - Enable channels and devices corresponding lists in backend (#520) -- Add missing components doc to architecture.md (#531) - -### Bugfixes -- MF-639 Split Content-Type header field on semicolon and evaluate all substrings (#644) -- MF-656 - Change bootstrap service port to 8200 (#658) -- Replace crossOrigin with relative path and fix messaging bug (#645) -- MF-579 Things & Channels returns 404 when not found or ID is malformed, not 500 (#633) -- Fix run command in dev guide (#605) -- MF-583 - Correct cmd/mongodb-reader HTTPServer log Info (#584) -- Fix Dusan Maldenovic GitHub (#570) -- Fix CLI docs (#566) -- Fix pagination response for empty page (#547) -- Fix swagger and provisioning docs (#546) -- NOISSUE - Fix event sourcing client on LoRa adapter (#527) -- Fix MQTT adapter scaling issue (#526) -- NOISSUE - Fix subtopic regex and restrict empty subtopic parts (#659) -- Fix missing css in container ui (#638) -- NOISSUE - Fix lora-adapter Object decode (#610) -- NOISSUE - Fix users logs in main.go (#577) -- NOISSUE - Fix normalizer exposed port in docker-compose (#548) - -### Summary -https://github.com/absmach/magistrala/milestone/9?closed=1 - - -## 0.7.0 - 08. DEC 2018. -### Features - -- MF-486 - Add provisioning command to CLI (#487) -- Fix lora-adapter event store handlers (#492) -- NOISSUE - Add LoRa route map validation and fix LoRa messages URL (#491) -- MF-475 - Replace increment ID with UUID (#490) -- MF-166 - Add lora-adapter service (#481) -- NOISSUE - Add Makefile target to clean old imgs (#485) -- MF-473 - Add metadata field to channel (#476) -- Make CoAP ping period configurable (#469) -- Add nginx ingress config to k8s services (#472) -- Add CoAP section in getting-started (#468) -- NOISSUE - Move CLI documentation from getting started guide to separate page (#470) -- NOISSUE - Update Getting Started doc with CLI usage (#465) -- Update CoAP docs with URL example (#463) -- MF-447 - Add event sourcing to things service (#460) -- Add TLS support to CoAP adapter and all readers (#459) -- MF-417 - Implement SDK tests (#438) -- MF-454 - Use message Time field as a time for InfluxDB points (#455) -- NOISSUE - Add .dockerignore to project root (#457) -- Update docker-compose so that every service has debug log level (#453) -- NOISSUE - Add TLS flag for Magistrala services (#452) -- MF-448 - Option for Postgres SSL Mode (#449) -- MF-443 Update project dependencies (#444) -- MF-426 - Add optional MG_CA_CERTS env variable to allow GRPC client to use TLS certs (#430) -- Expose the InfluxDB and Cassandra ports to host (#441) -- MF-374 - Bring back CoAP adapter (#413) - -### Bugfixes -- gRPC Load Balancing between http-adapter and things (#387) -- MF-407 - Values of zero are being omitted (#434) - -### Summary -https://github.com/absmach/magistrala/milestone/8?closed=1 - - -## 0.6.0 - 26. OCT 2018. -### Features - -- Added Go SDK (#357) -- Updated NATS version (#412) -- Added debbug level to MFX logger (#379) -- Added Documentation for readers (#389) -- Added Redis cache to improve performance (#382) - - -## 0.5.1 - 05. SEP 2018. -### Features -- Improve performance by adding Redis cache (#382) - -### Bugfixes -- Mixed up name and type of the things (#375) -- Fix MQTT topic (#380) - - -## 0.5.0 - 28. AUG 2018 -### Features -- InfluxDB Reader (#311) -- Cassandra Reader (#313) -- MongoDB Reader (#344) -- MQTT Persistance via Redis (#328) -- CLI integrated into monorepo (#216) -- Normalizer logging (#333) -- WS swagger doc (#337) -- Payload renamed to Metadata (#343) -- Protobuf files added (#363) -- SPDX headers added (#325) - -### Bugfixes -- Docker network for InfluxDB (#346) -- Vendor correct gRPC version (#340) - -### Summary -https://github.com/absmach/magistrala/milestone/6?closed=1 - - -## 0.4.0 - 01. JUN 2018. -* Integrated MQTT adapter (#165 ) -* Support for storing messages in MongoDB (#237) -* Support for storing messages in InfluxDB (#236) -* Use UUID PKs with auto-incremented values (#269 ) -* Replaced JWT with plain string tokens in things service (#268 ) -* Emit non-SenML messages (#239 ) -* Support for Grafana (#296) -* Added WS Load test (#299 ) - - -## 0.3.0 - 14. MAY 2018. -- CoAP API for message exchange (#186) -- Split `manager` service into `clients` and `users` (#266) -- Replaced ORM with raw SQL (#265) -- Setup Kubernetes (#226, #273) -- Fix docker compose (#274) -- Integrated `dashflux` into monorepo (#258) -- Integrated (*non-compatible*) `mqtt` into monorepo (#260) - - -## 0.2.3 - 24. APR 2018. -- Fix examples in the documentation (#243) -- Add service name in info response (#241) -- Improve code coverage in WS adapter (#242) - - -## 0.2.2 - 23. APR 2018. -- Setup load testing scenarios (#225) - - -## 0.2.1 - 22. APR 2018. -- Fixed `Content-Type` header checking (#238) - -## 0.2.0 - 18. APR 2018 -- Protobuf message serialization (#192) -- Websocket API for exchanging messages (#188) -- Channel & client retrieval paging (#227) -- Service instrumentation (#213) -- `go-kit` based JSON logger (#212) -- Project documentation (#218, #220) -- API tests (#211, #224) - - -## 0.1.2 - 18. MAR 2018. -### Bug fixes - -- Fixed go lint warnings (#189) -- Compose failing startup (#185) -- Added missing service startup messages (#190) From 4635b14dd707d2d736f6fba1863428106e291986 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:19:49 +0100 Subject: [PATCH 38/71] NOISSUE - Bump github.com/opencontainers/runc from 1.1.10 to 1.1.12 (#2070) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce2f6d62fb..69b3397290 100644 --- a/go.mod +++ b/go.mod @@ -137,7 +137,7 @@ require ( github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.1.10 // indirect + github.com/opencontainers/runc v1.1.12 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pion/dtls/v2 v2.2.9 // indirect github.com/pion/logging v0.2.2 // indirect diff --git a/go.sum b/go.sum index 3ccfe84987..7ad59cad7d 100644 --- a/go.sum +++ b/go.sum @@ -384,8 +384,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= -github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= From e882cd5e5245370ea20d9299f034e3a1a9c32619 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:21:33 +0300 Subject: [PATCH 39/71] NOISSUE - Pin the version of `golangci-lint` (#2077) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/tests.yml | 4 ++-- auth/postgres/domains.go | 2 +- auth/service.go | 2 +- bootstrap/service.go | 1 - internal/groups/postgres/groups.go | 2 +- internal/server/grpc/grpc.go | 2 +- pkg/messaging/rabbitmq/publisher.go | 1 - things/api/http/requests.go | 4 ++-- twins/mocks/twins.go | 2 +- 9 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7122d58260..c53660975c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,9 +24,9 @@ jobs: cache-dependency-path: "go.sum" - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: - version: latest + version: v1.56.1 - name: Build all Binaries run: | diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index b3fe875668..a7d77e3b37 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -128,7 +128,7 @@ func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id stri // RetrieveAllByIDs retrieves for given Domain IDs . func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { var q string - if len(pm.IDs) <= 0 { + if len(pm.IDs) == 0 { return auth.DomainsPage{}, nil } query, err := buildPageQuery(pm) diff --git a/auth/service.go b/auth/service.go index 8a1700a133..9f9f130fce 100644 --- a/auth/service.go +++ b/auth/service.go @@ -948,7 +948,7 @@ func DecodeDomainUserID(domainUserID string) (string, string) { return duid[0], duid[1] case len(duid) == 1: return duid[0], "" - case len(duid) <= 0 || len(duid) > 2: + case len(duid) == 0 || len(duid) > 2: fallthrough default: return "", "" diff --git a/bootstrap/service.go b/bootstrap/service.go index ad6bd25f01..d0d0399a2c 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -135,7 +135,6 @@ func (bs bootstrapService) Add(ctx context.Context, token string, cfg Config) (C } cfg.Channels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), token) - if err != nil { return Config{}, errors.Wrap(errConnectionChannels, err) } diff --git a/internal/groups/postgres/groups.go b/internal/groups/postgres/groups.go index b653fb701a..f0f332bff3 100644 --- a/internal/groups/postgres/groups.go +++ b/internal/groups/postgres/groups.go @@ -192,7 +192,7 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, ids ...string) (mggroups.Page, error) { var q string - if (len(ids) <= 0) && (gm.PageMeta.DomainID == "") { + if (len(ids) == 0) && (gm.PageMeta.DomainID == "") { return mggroups.Page{PageMeta: mggroups.PageMeta{Offset: gm.Offset, Limit: gm.Limit}}, nil } query := buildQuery(gm, ids...) diff --git a/internal/server/grpc/grpc.go b/internal/server/grpc/grpc.go index 6a91495faa..8a6d6e9b48 100644 --- a/internal/server/grpc/grpc.go +++ b/internal/server/grpc/grpc.go @@ -104,7 +104,7 @@ func (s *Server) Start() error { } creds = grpc.Creds(credentials.NewTLS(tlsConfig)) switch { - case len(mtlsCA) > 0: + case mtlsCA != "": s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS/mTLS cert %s , key %s and %s", s.Name, s.Address, s.Config.CertFile, s.Config.KeyFile, mtlsCA)) default: s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS cert %s and key %s", s.Name, s.Address, s.Config.CertFile, s.Config.KeyFile)) diff --git a/pkg/messaging/rabbitmq/publisher.go b/pkg/messaging/rabbitmq/publisher.go index 0003758246..3f52d38f18 100644 --- a/pkg/messaging/rabbitmq/publisher.go +++ b/pkg/messaging/rabbitmq/publisher.go @@ -79,7 +79,6 @@ func (pub *publisher) Publish(ctx context.Context, topic string, msg *messaging. AppId: "magistrala-publisher", Body: data, }) - if err != nil { return err } diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 687127757b..9f481e9497 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -339,7 +339,7 @@ func (req *thingShareRequest) validate() error { if req.thingID == "" { return errors.ErrMalformedEntity } - if req.Relation == "" || len(req.UserIDs) <= 0 { + if req.Relation == "" || len(req.UserIDs) == 0 { return errors.ErrCreateEntity } return nil @@ -356,7 +356,7 @@ func (req *thingUnshareRequest) validate() error { if req.thingID == "" { return errors.ErrMalformedEntity } - if req.Relation == "" || len(req.UserIDs) <= 0 { + if req.Relation == "" || len(req.UserIDs) == 0 { return errors.ErrCreateEntity } return nil diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index 6f85a195a1..77d35e4159 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -99,7 +99,7 @@ func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offs if (uint64)(len(items)) >= limit { break } - if len(name) > 0 && v.Name != name { + if name != "" && v.Name != name { continue } if !strings.HasPrefix(k, owner) { From 30b741bb1fb6f990fd2a5c2f08e32dcbbee7e040 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Thu, 15 Feb 2024 17:47:02 +0300 Subject: [PATCH 40/71] NOISSUE - Add Postgres DB for UI (#2082) Signed-off-by: rodneyosodo --- docker/.env | 10 ++++++++++ docker/docker-compose.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/docker/.env b/docker/.env index 1de267cbe7..c433e7f103 100644 --- a/docker/.env +++ b/docker/.env @@ -252,11 +252,21 @@ MG_READER_URL=http://mongodb-reader:9007 MG_THINGS_URL=http://things:9000 MG_USERS_URL=http://users:9002 MG_INVITATIONS_URL=http://invitations:9020 +MG_DOMAINS_URL=http://auth:8189 MG_BOOTSTRAP_URL=http://bootstrap:9013 MG_UI_HOST_URL=http://localhost:9095 MG_UI_VERIFICATION_TLS=false MG_UI_CONTENT_TYPE=application/senml+json MG_UI_INSTANCE_ID= +MG_UI_DB_HOST=ui-db +MG_UI_DB_PORT=5432 +MG_UI_DB_USER=magistrala +MG_UI_DB_PASS=magistrala +MG_UI_DB_NAME=ui +MG_UI_DB_SSL_MODE=disable +MG_UI_DB_SSL_CERT= +MG_UI_DB_SSL_KEY= +MG_UI_DB_SSL_ROOT_CERT= ## Addons Services ### Bootstrap diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8a54d49ab7..2e501cd887 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -17,6 +17,7 @@ volumes: magistrala-spicedb-db-volume: magistrala-auth-db-volume: magistrala-invitations-db-volume: + magistrala-ui-db-volume: services: spicedb: @@ -707,7 +708,33 @@ services: MG_UI_VERIFICATION_TLS: ${MG_UI_VERIFICATION_TLS} MG_UI_CONTENT_TYPE: ${MG_UI_CONTENT_TYPE} MG_UI_INSTANCE_ID: ${MG_UI_INSTANCE_ID} + MG_UI_DB_HOST: ${MG_UI_DB_HOST} + MG_UI_DB_PORT: ${MG_UI_DB_PORT} + MG_UI_DB_USER: ${MG_UI_DB_USER} + MG_UI_DB_PASS: ${MG_UI_DB_PASS} + MG_UI_DB_NAME: ${MG_UI_DB_NAME} + MG_UI_DB_SSL_MODE: ${MG_UI_DB_SSL_MODE} + MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} + MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} + MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: - magistrala-base-net + + ui-db: + image: postgres:16.1-alpine + container_name: magistrala-ui-db + restart: on-failure + command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + environment: + POSTGRES_USER: ${MG_UI_DB_USER} + POSTGRES_PASSWORD: ${MG_UI_DB_PASS} + POSTGRES_DB: ${MG_UI_DB_NAME} + MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + ports: + - 6007:5432 + networks: + - magistrala-base-net + volumes: + - magistrala-ui-db-volume:/var/lib/postgresql/data From de3481feca4303e22cacae0138c67072dd4922fe Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:50:47 +0530 Subject: [PATCH 41/71] NOISSUE - Fix provision configuration loading (#2078) Signed-off-by: Arvindh --- cmd/provision/main.go | 34 ++++++++++------------ docker/addons/provision/docker-compose.yml | 1 - provision/README.md | 7 ++--- provision/api/endpoint.go | 7 ++++- provision/api/requests.go | 4 +++ provision/api/responses.go | 21 +++++++++++++ provision/config.go | 30 +++++++++---------- provision/config_test.go | 1 - provision/configs/config.toml | 4 +-- provision/service.go | 8 ++--- 10 files changed, 68 insertions(+), 49 deletions(-) diff --git a/cmd/provision/main.go b/cmd/provision/main.go index 6057fd4359..8798f562c0 100644 --- a/cmd/provision/main.go +++ b/cmd/provision/main.go @@ -135,26 +135,22 @@ func loadConfig() (provision.Config, error) { return provision.Config{}, errFailedToReadBootstrapContent } } - - cfg = provision.Config{ - Bootstrap: provision.Bootstrap{ - Content: content, - }, - // This is default conf for provision if there is no config file - Channels: []mggroups.Group{ - { - Name: "control-channel", - Metadata: map[string]interface{}{"type": "control"}, - }, { - Name: "data-channel", - Metadata: map[string]interface{}{"type": "data"}, - }, + cfg.Bootstrap.Content = content + + cfg.Channels = []mggroups.Group{ + { + Name: "control-channel", + Metadata: map[string]interface{}{"type": "control"}, + }, { + Name: "data-channel", + Metadata: map[string]interface{}{"type": "data"}, }, - Things: []mgclients.Client{ - { - Name: "thing", - Metadata: map[string]interface{}{"external_id": "xxxxxx"}, - }, + } + + cfg.Things = []mgclients.Client{ + { + Name: "thing", + Metadata: map[string]interface{}{"external_id": "xxxxxx"}, }, } diff --git a/docker/addons/provision/docker-compose.yml b/docker/addons/provision/docker-compose.yml index 918ab83ffa..f4e1a526ea 100644 --- a/docker/addons/provision/docker-compose.yml +++ b/docker/addons/provision/docker-compose.yml @@ -35,7 +35,6 @@ services: MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL} MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING} MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL} - MG_PROVISION_BS_SVC_WHITELIST_URL: ${MG_PROVISION_BS_SVC_WHITELIST_URL} MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING} MG_PROVISION_BS_AUTO_WHITELIST: ${MG_PROVISION_BS_AUTO_WHITELIST} MG_PROVISION_BS_CONTENT: ${MG_PROVISION_BS_CONTENT} diff --git a/provision/README.md b/provision/README.md index daedb07c8e..3446efae31 100644 --- a/provision/README.md +++ b/provision/README.md @@ -30,9 +30,8 @@ default values. | MG_PROVISION_SERVER_KEY | Magistrala gRPC secure server key | | | MG_PROVISION_USERS_LOCATION | Users service URL | | | MG_PROVISION_THINGS_LOCATION | Things service URL | | -| MG_PROVISION_BS_SVC_URL | Magistrala Bootstrap service URL | | -| MG_PROVISION_BS_SVC_WHITELIST_URL | Magistrala Bootstrap service whitelist URL | | -| MG_PROVISION_CERTS_SVC_URL | Certificates service URL | | +| MG_PROVISION_BS_SVC_URL | Magistrala Bootstrap service URL | | +| MG_PROVISION_CERTS_SVC_URL | Certificates service URL | | | MG_PROVISION_X509_PROVISIONING | Should X509 client cert be provisioned | false | | MG_PROVISION_BS_CONFIG_PROVISIONING | Should thing config be saved in Bootstrap service | true | | MG_PROVISION_BS_AUTO_WHITELIST | Should thing be auto whitelisted | true | @@ -99,7 +98,7 @@ Provision service can be run as a standalone or in docker composition as addon t Standalone: ```bash -MG_PROVISION_BS_SVC_URL=http://localhost:9013/things \ +MG_PROVISION_BS_SVC_URL=http://localhost:9013 \ MG_PROVISION_THINGS_LOCATION=http://localhost:9000 \ MG_PROVISION_USERS_LOCATION=http://localhost:9002 \ MG_PROVISION_CONFIG_FILE=docker/addons/provision/configs/config.toml \ diff --git a/provision/api/endpoint.go b/provision/api/endpoint.go index 495ea6f3f9..ffe2087654 100644 --- a/provision/api/endpoint.go +++ b/provision/api/endpoint.go @@ -44,6 +44,11 @@ func getMapping(svc provision.Service) endpoint.Endpoint { return nil, errors.Wrap(apiutil.ErrValidation, err) } - return svc.Mapping(req.token) + res, err := svc.Mapping(req.token) + if err != nil { + return nil, err + } + + return mappingRes{Data: res}, nil } } diff --git a/provision/api/requests.go b/provision/api/requests.go index 5bbcd98ee6..390653f418 100644 --- a/provision/api/requests.go +++ b/provision/api/requests.go @@ -21,6 +21,10 @@ func (req provisionReq) validate() error { return apiutil.ErrBearerKey } + if req.Name == "" { + return apiutil.ErrMissingName + } + return nil } diff --git a/provision/api/responses.go b/provision/api/responses.go index 4acd358d72..87c105225c 100644 --- a/provision/api/responses.go +++ b/provision/api/responses.go @@ -4,6 +4,7 @@ package api import ( + "encoding/json" "net/http" "github.com/absmach/magistrala" @@ -32,3 +33,23 @@ func (res provisionRes) Headers() map[string]string { func (res provisionRes) Empty() bool { return false } + +type mappingRes struct { + Data interface{} +} + +func (res mappingRes) Code() int { + return http.StatusOK +} + +func (res mappingRes) Headers() map[string]string { + return map[string]string{} +} + +func (res mappingRes) Empty() bool { + return false +} + +func (res mappingRes) MarshalJSON() ([]byte, error) { + return json.Marshal(res.Data) +} diff --git a/provision/config.go b/provision/config.go index 78dc45f7fb..d0f6683b83 100644 --- a/provision/config.go +++ b/provision/config.go @@ -17,21 +17,20 @@ var errFailedToReadConfig = errors.New("failed to read config file") // ServiceConf represents service config. type ServiceConf struct { - Port string `toml:"port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` - LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"` - TLS bool `toml:"tls" env:"MG_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"` - ServerCert string `toml:"server_cert" env:"MG_PROVISION_SERVER_CERT" envDefault:""` - ServerKey string `toml:"server_key" env:"MG_PROVISION_SERVER_KEY" envDefault:""` - ThingsURL string `toml:"things_url" env:"MG_PROVISION_THINGS_LOCATION" envDefault:"http://localhost"` - UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_LOCATION" envDefault:"http://localhost"` - HTTPPort string `toml:"http_port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` - MgUser string `toml:"mg_user" env:"MG_PROVISION_USER" envDefault:"test@example.com"` - MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"` - MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""` - MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""` - MgBSURL string `toml:"mg_bs_url" env:"MG_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000/things/configs"` - MgWhiteListURL string `toml:"mg_white_list" env:"MG_PROVISION_BS_SVC_WHITELIST_URL" envDefault:"http://localhost:9000/things/state"` - MgCertsURL string `toml:"mg_certs_url" env:"MG_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"` + Port string `toml:"port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` + LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"` + TLS bool `toml:"tls" env:"MG_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"` + ServerCert string `toml:"server_cert" env:"MG_PROVISION_SERVER_CERT" envDefault:""` + ServerKey string `toml:"server_key" env:"MG_PROVISION_SERVER_KEY" envDefault:""` + ThingsURL string `toml:"things_url" env:"MG_PROVISION_THINGS_LOCATION" envDefault:"http://localhost"` + UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_LOCATION" envDefault:"http://localhost"` + HTTPPort string `toml:"http_port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` + MgUser string `toml:"mg_user" env:"MG_PROVISION_USER" envDefault:"test@example.com"` + MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"` + MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""` + MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""` + MgBSURL string `toml:"mg_bs_url" env:"MG_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"` + MgCertsURL string `toml:"mg_certs_url" env:"MG_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"` } // Bootstrap represetns the Bootstrap config. @@ -60,7 +59,6 @@ type Cert struct { // Config struct of Provision. type Config struct { - LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"` File string `toml:"file" env:"MG_PROVISION_CONFIG_FILE" envDefault:"config.toml"` Server ServiceConf `toml:"server" mapstructure:"server"` Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"` diff --git a/provision/config_test.go b/provision/config_test.go index aae1dedef4..9ba0a37e4c 100644 --- a/provision/config_test.go +++ b/provision/config_test.go @@ -18,7 +18,6 @@ import ( var ( validConfig = provision.Config{ - LogLevel: "info", Server: provision.ServiceConf{ Port: "9016", LogLevel: "info", diff --git a/provision/configs/config.toml b/provision/configs/config.toml index f679affd7e..38455eb239 100644 --- a/provision/configs/config.toml +++ b/provision/configs/config.toml @@ -16,10 +16,9 @@ file = "config.toml" http_port = "8190" mg_api_key = "" mg_bs_url = "http://localhost:9013" - mg_certs_url = "http://localhost:9019/certs" + mg_certs_url = "http://localhost:9019" mg_pass = "" mg_user = "" - mg_white_list = "http://localhost:9013/things/state" mqtt_url = "" port = "" server_cert = "" @@ -46,4 +45,3 @@ file = "config.toml" [channels.metadata] type = "data" - diff --git a/provision/service.go b/provision/service.go index f9e844c517..0edad638a0 100644 --- a/provision/service.go +++ b/provision/service.go @@ -158,7 +158,7 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin for _, channel := range ps.conf.Channels { ch := sdk.Channel{ - Name: channel.Name, + Name: name + "_" + channel.Name, Metadata: sdk.Metadata(channel.Metadata), } ch, err := ps.sdk.CreateChannel(ch, token) @@ -340,11 +340,11 @@ func (ps *provisionService) errLog(err error) { func clean(ps *provisionService, things []sdk.Thing, channels []sdk.Channel, token string) { for _, t := range things { - _, err := ps.sdk.DisableThing(t.ID, token) + err := ps.sdk.DeleteThing(t.ID, token) ps.errLog(err) } for _, c := range channels { - _, err := ps.sdk.DisableChannel(c.ID, token) + err := ps.sdk.DeleteChannel(c.ID, token) ps.errLog(err) } } @@ -357,7 +357,7 @@ func (ps *provisionService) recover(e *error, ths *[]sdk.Thing, chs *[]sdk.Chann if errors.Contains(err, ErrFailedThingRetrieval) || errors.Contains(err, ErrFailedChannelCreation) { for _, th := range things { - _, err := ps.sdk.DisableThing(th.ID, token) + err := ps.sdk.DeleteThing(th.ID, token) ps.errLog(err) } return From aafff212101bf95987ce8a580054759103a5e7df Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:33:21 +0530 Subject: [PATCH 42/71] NOISSUE - Fix Bootstrap thing creation flow (#2083) Signed-off-by: Arvindh --- bootstrap/api/endpoint_test.go | 5 ++- bootstrap/api/transport.go | 13 +++--- bootstrap/events/producer/streams_test.go | 5 ++- bootstrap/service.go | 49 ++++++++++---------- bootstrap/service_test.go | 4 +- cli/bootstrap.go | 4 +- cmd/bootstrap/main.go | 3 +- pkg/sdk/go/bootstrap.go | 55 +++++++++++++++++++++-- pkg/sdk/go/sdk.go | 4 +- pkg/sdk/mocks/sdk.go | 18 ++++---- 10 files changed, 106 insertions(+), 54 deletions(-) diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 0129494eb0..8d6f568c0d 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -29,6 +29,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -180,8 +181,8 @@ func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) - - return bootstrap.New(auth, things, sdk, encKey), auth, sdk + idp := uuid.NewMock() + return bootstrap.New(auth, things, sdk, encKey, idp), auth, sdk } func newBootstrapServer(svc bootstrap.Service) *httptest.Server { diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index ff3eee6a65..740275c8f3 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -23,11 +23,12 @@ import ( ) const ( - contentType = "application/json" - offsetKey = "offset" - limitKey = "limit" - defOffset = 0 - defLimit = 10 + contentType = "application/json" + byteContentType = "application/octet-stream" + offsetKey = "offset" + limitKey = "limit" + defOffset = 0 + defLimit = 10 ) var ( @@ -259,7 +260,7 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface } func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Type", byteContentType) w.WriteHeader(http.StatusOK) if b, ok := response.([]byte); ok { if _, err := w.Write(b); err != nil { diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index ea2abc0670..9f7f058b2f 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -23,6 +23,7 @@ import ( "github.com/absmach/magistrala/pkg/events/store" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/go-redis/redis/v8" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -81,8 +82,8 @@ func newService(t *testing.T, url string) (bootstrap.Service, *authmocks.AuthCli things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) - - svc := bootstrap.New(auth, things, sdk, encKey) + idp := uuid.NewMock() + svc := bootstrap.New(auth, things, sdk, encKey, idp) publisher, err := store.NewPublisher(context.Background(), url, streamID) require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) svc = producer.NewEventStoreMiddleware(svc, publisher) diff --git a/bootstrap/service.go b/bootstrap/service.go index d0d0399a2c..5730054ccf 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -104,19 +104,21 @@ type ConfigReader interface { } type bootstrapService struct { - auth magistrala.AuthServiceClient - configs ConfigRepository - sdk mgsdk.SDK - encKey []byte + auth magistrala.AuthServiceClient + configs ConfigRepository + sdk mgsdk.SDK + encKey []byte + idProvider magistrala.IDProvider } // New returns new Bootstrap service. -func New(auth magistrala.AuthServiceClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte) Service { +func New(auth magistrala.AuthServiceClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { return &bootstrapService{ - configs: configs, - sdk: sdk, - auth: auth, - encKey: encKey, + configs: configs, + sdk: sdk, + auth: auth, + encKey: encKey, + idProvider: idp, } } @@ -152,8 +154,10 @@ func (bs bootstrapService) Add(ctx context.Context, token string, cfg Config) (C saved, err := bs.configs.Save(ctx, cfg, toConnect) if err != nil { + // If id is empty, then a new thing has been created function - bs.thing(id, token) + // So, on bootstrap config save error , delete the newly created thing. if id == "" { - if _, errT := bs.sdk.DisableThing(cfg.ThingID, token); errT != nil { + if errT := bs.sdk.DeleteThing(cfg.ThingID, token); errT != nil { err = errors.Wrap(err, errT) } } @@ -381,29 +385,24 @@ func (bs bootstrapService) identify(ctx context.Context, token string) (string, // Method thing retrieves Magistrala Thing creating one if an empty ID is passed. func (bs bootstrapService) thing(id, token string) (mgsdk.Thing, error) { - var thing mgsdk.Thing - var err error - var sdkErr errors.SDKError - - thing.ID = id + // If Thing ID is not provided, then create new thing. if id == "" { - thing, sdkErr = bs.sdk.CreateThing(mgsdk.Thing{}, token) + id, err := bs.idProvider.ID() if err != nil { + return mgsdk.Thing{}, errors.Wrap(errCreateThing, err) + } + thing, sdkErr := bs.sdk.CreateThing(mgsdk.Thing{ID: id, Name: "Bootstrapped Thing " + id}, token) + if sdkErr != nil { return mgsdk.Thing{}, errors.Wrap(errCreateThing, errors.New(sdkErr.Err().Msg())) } + return thing, nil } - thing, sdkErr = bs.sdk.Thing(thing.ID, token) + // If Thing ID is provided, then retrieve thing + thing, sdkErr := bs.sdk.Thing(id, token) if sdkErr != nil { - err = errors.New(sdkErr.Error()) - if id != "" { - if _, sdkErr2 := bs.sdk.DisableThing(thing.ID, token); sdkErr2 != nil { - err = errors.Wrap(errors.New(sdkErr.Msg()), errors.New(sdkErr2.Msg())) - } - } - return mgsdk.Thing{}, errors.Wrap(ErrThings, err) + return mgsdk.Thing{}, errors.Wrap(ErrThings, sdkErr) } - return thing, nil } diff --git a/bootstrap/service_test.go b/bootstrap/service_test.go index fb6f65c010..6848b32100 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -23,6 +23,7 @@ import ( svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -60,8 +61,9 @@ func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) + idp := uuid.NewMock() - return bootstrap.New(auth, things, sdk, encKey), auth, sdk + return bootstrap.New(auth, things, sdk, encKey, idp), auth, sdk } func enc(in []byte) ([]byte, error) { diff --git a/cli/bootstrap.go b/cli/bootstrap.go index 9130fd070a..0c1d9c03d8 100644 --- a/cli/bootstrap.go +++ b/cli/bootstrap.go @@ -146,7 +146,7 @@ var cmdBootstrap = []cobra.Command{ }, }, { - Use: "bootstrap [ | secure ]", + Use: "bootstrap [ | secure ]", Short: "Bootstrap config", Long: `Returns Config to the Thing with provided external ID using external key. secure - Retrieves a configuration with given external ID and encrypted external key.`, @@ -156,7 +156,7 @@ var cmdBootstrap = []cobra.Command{ return } if args[0] == "secure" { - c, err := sdk.BootstrapSecure(args[1], args[2]) + c, err := sdk.BootstrapSecure(args[1], args[2], args[3]) if err != nil { logError(err) return diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index 2262dd4db3..78200f43cc 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -178,8 +178,9 @@ func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db } sdk := mgsdk.NewSDK(config) + idp := uuid.New() - svc := bootstrap.New(authClient, repoConfig, sdk, []byte(cfg.EncKey)) + svc := bootstrap.New(authClient, repoConfig, sdk, []byte(cfg.EncKey), idp) publisher, err := store.NewPublisher(ctx, cfg.ESURL, streamID) if err != nil { diff --git a/pkg/sdk/go/bootstrap.go b/pkg/sdk/go/bootstrap.go index 3278d3a61e..5501070924 100644 --- a/pkg/sdk/go/bootstrap.go +++ b/pkg/sdk/go/bootstrap.go @@ -4,8 +4,13 @@ package sdk import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" "encoding/json" "fmt" + "io" "net/http" "strings" @@ -233,18 +238,60 @@ func (sdk mgSDK) Bootstrap(externalID, externalKey string) (BootstrapConfig, err return bc, nil } -func (sdk mgSDK) BootstrapSecure(externalID, externalKey string) (BootstrapConfig, errors.SDKError) { +func (sdk mgSDK) BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) { url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, secureEndpoint, externalID) - _, body, err := sdk.processRequest(http.MethodGet, url, ThingPrefix+externalKey, nil, nil, http.StatusOK) + encExtKey, err := bootstrapEncrypt([]byte(externalKey), cryptoKey) if err != nil { - return BootstrapConfig{}, err + return BootstrapConfig{}, errors.NewSDKError(err) + } + + _, body, sdkErr := sdk.processRequest(http.MethodGet, url, ThingPrefix+encExtKey, nil, nil, http.StatusOK) + if sdkErr != nil { + return BootstrapConfig{}, sdkErr } + decBody, decErr := bootstrapDecrypt(body, cryptoKey) + if decErr != nil { + return BootstrapConfig{}, errors.NewSDKError(decErr) + } var bc BootstrapConfig - if err := json.Unmarshal(body, &bc); err != nil { + if err := json.Unmarshal(decBody, &bc); err != nil { return BootstrapConfig{}, errors.NewSDKError(err) } return bc, nil } + +func bootstrapEncrypt(in []byte, cryptoKey string) (string, error) { + block, err := aes.NewCipher([]byte(cryptoKey)) + if err != nil { + return "", err + } + ciphertext := make([]byte, aes.BlockSize+len(in)) + iv := ciphertext[:aes.BlockSize] + + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], in) + return hex.EncodeToString(ciphertext), nil +} + +func bootstrapDecrypt(in []byte, cryptoKey string) ([]byte, error) { + ciphertext := in + + block, err := aes.NewCipher([]byte(cryptoKey)) + if err != nil { + return nil, err + } + if len(ciphertext) < aes.BlockSize { + return nil, err + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + return ciphertext, nil +} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 98e3e6aab5..1bb8ff3033 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -916,9 +916,9 @@ type SDK interface { // BootstrapSecure retrieves a configuration with given external ID and encrypted external key. // // example: - // bootstrap, _ := sdk.BootstrapSecure("externalID", "externalKey") + // bootstrap, _ := sdk.BootstrapSecure("externalID", "externalKey", "cryptoKey") // fmt.Println(bootstrap) - BootstrapSecure(externalID, externalKey string) (BootstrapConfig, errors.SDKError) + BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) // Bootstraps retrieves a list of managed configs. // diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index e8eb18f56b..eadcb80409 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -176,9 +176,9 @@ func (_m *SDK) Bootstrap(externalID string, externalKey string) (sdk.BootstrapCo return r0, r1 } -// BootstrapSecure provides a mock function with given fields: externalID, externalKey -func (_m *SDK) BootstrapSecure(externalID string, externalKey string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(externalID, externalKey) +// BootstrapSecure provides a mock function with given fields: externalID, externalKey, cryptoKey +func (_m *SDK) BootstrapSecure(externalID string, externalKey string, cryptoKey string) (sdk.BootstrapConfig, errors.SDKError) { + ret := _m.Called(externalID, externalKey, cryptoKey) if len(ret) == 0 { panic("no return value specified for BootstrapSecure") @@ -186,17 +186,17 @@ func (_m *SDK) BootstrapSecure(externalID string, externalKey string) (sdk.Boots var r0 sdk.BootstrapConfig var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(externalID, externalKey) + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { + return rf(externalID, externalKey, cryptoKey) } - if rf, ok := ret.Get(0).(func(string, string) sdk.BootstrapConfig); ok { - r0 = rf(externalID, externalKey) + if rf, ok := ret.Get(0).(func(string, string, string) sdk.BootstrapConfig); ok { + r0 = rf(externalID, externalKey, cryptoKey) } else { r0 = ret.Get(0).(sdk.BootstrapConfig) } - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(externalID, externalKey) + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(externalID, externalKey, cryptoKey) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) From 84a1ca92b4f356d4d58a080b0dd5193fbdb92998 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:29:55 +0530 Subject: [PATCH 43/71] NOISSUE - Vault operations with app role authentication (#2084) Signed-off-by: Arvindh Signed-off-by: arvindh123 --- certs/README.md | 77 +++---- certs/mocks/pki.go | 5 + certs/pki/vault.go | 109 +++++++++- cmd/certs/main.go | 16 +- docker/.env | 67 ++++-- docker/README.md | 15 ++ docker/addons/certs/docker-compose.yml | 9 +- docker/addons/vault/.gitignore | 1 + docker/addons/vault/README.md | 102 ++++++--- ...magistrala_things_certs_issue.template.hcl | 32 +++ docker/addons/vault/vault-set-pki.sh | 144 ------------- docker/addons/vault/vault_copy_certs.sh | 33 +++ .../{vault-init.sh => vault_copy_env.sh} | 9 +- docker/addons/vault/vault_create_approle.sh | 95 +++++++++ docker/addons/vault/vault_init.sh | 18 ++ docker/addons/vault/vault_set_pki.sh | 199 ++++++++++++++++++ .../{vault-unseal.sh => vault_unseal.sh} | 0 docker/docker-compose.yml | 18 +- docker/nginx/entrypoint.sh | 1 + docker/nginx/nginx-key.conf | 10 +- docker/nginx/nginx-x509.conf | 19 +- go.mod | 9 +- go.sum | 44 ++++ 23 files changed, 764 insertions(+), 268 deletions(-) create mode 100644 docker/addons/vault/magistrala_things_certs_issue.template.hcl delete mode 100755 docker/addons/vault/vault-set-pki.sh create mode 100755 docker/addons/vault/vault_copy_certs.sh rename docker/addons/vault/{vault-init.sh => vault_copy_env.sh} (84%) create mode 100755 docker/addons/vault/vault_create_approle.sh create mode 100755 docker/addons/vault/vault_init.sh create mode 100755 docker/addons/vault/vault_set_pki.sh rename docker/addons/vault/{vault-unseal.sh => vault_unseal.sh} (100%) diff --git a/certs/README.md b/certs/README.md index d279b93650..ad89e581c2 100644 --- a/certs/README.md +++ b/certs/README.md @@ -30,38 +30,41 @@ curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Beare The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ------------------------- | --------------------------------------------------------------------------- | ----------------------------------- | -| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | -| MG_CERTS_HTTP_HOST | Service Certs host | "" | -| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | -| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | -| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | -| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | -| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | -| MG_CERTS_VAULT_HOST | Vault host | "" | -| MG_VAULT_PKI_INT_PATH | Vault PKI intermediate path | pki_int | -| MG_VAULT_CA_ROLE_NAME | Vault PKI role name | magistrala | -| MG_VAULT_TOKEN | Vault token | "" | -| MG_CERTS_DB_HOST | Database host | localhost | -| MG_CERTS_DB_PORT | Database port | 5432 | -| MG_CERTS_DB_PASS | Database password | magistrala | -| MG_CERTS_DB_USER | Database user | magistrala | -| MG_CERTS_DB_NAME | Database name | certs | -| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | -| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | -| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | -| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | -| MG_THINGS_URL | Things service URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_CERTS_INSTANCE_ID | Service instance ID | "" | + +| Variable | Description | Default | +| :---------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | +| MG_CERTS_HTTP_HOST | Service Certs host | "" | +| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | +| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | +| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | +| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | +| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | +| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | +| MG_CERTS_VAULT_HOST | Vault host | http://vault:8200 | +| MG_CERTS_VAULT_NAMESPACE | Vault namespace in which pki is present | magistrala | +| MG_CERTS_VAULT_APPROLE_ROLEID | Vault AppRole auth RoleID | magistrala | +| MG_CERTS_VAULT_APPROLE_SECRET | Vault AppRole auth Secret | magistrala | +| MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH | Vault PKI path for issuing Things Certificates | pki_int | +| MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME | Vault PKI Role Name for issuing Things Certificates | magistrala_things_certs | +| MG_CERTS_DB_HOST | Database host | localhost | +| MG_CERTS_DB_PORT | Database port | 5432 | +| MG_CERTS_DB_PASS | Database password | magistrala | +| MG_CERTS_DB_USER | Database user | magistrala | +| MG_CERTS_DB_NAME | Database name | certs | +| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | +| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | +| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | +| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | +| MG_THINGS_URL | Things service URL | [localhost:9000](localhost:9000) | +| MG_JAEGER_URL | Jaeger server URL | [http://localhost:14268/api/traces](http://localhost:14268/api/traces) | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_CERTS_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -95,10 +98,12 @@ MG_AUTH_GRPC_CLIENT_KEY="" \ MG_AUTH_GRPC_SERVER_CERTS="" \ MG_CERTS_SIGN_CA_PATH=ca.crt \ MG_CERTS_SIGN_CA_KEY_PATH=ca.key \ -MG_CERTS_VAULT_HOST="" \ -MG_VAULT_PKI_INT_PATH=pki_int \ -MG_VAULT_CA_ROLE_NAME=magistrala \ -MG_VAULT_TOKEN="" \ +MG_CERTS_VAULT_HOST=http://vault:8200 \ +MG_CERTS_VAULT_NAMESPACE=magistrala \ +MG_CERTS_VAULT_APPROLE_ROLEID=magistrala \ +MG_CERTS_VAULT_APPROLE_SECRET=magistrala \ +MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=pki_int \ +MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=magistrala_things_certs \ MG_CERTS_DB_HOST=localhost \ MG_CERTS_DB_PORT=5432 \ MG_CERTS_DB_PASS=magistrala \ diff --git a/certs/mocks/pki.go b/certs/mocks/pki.go index 3e183b4aad..502917ff99 100644 --- a/certs/mocks/pki.go +++ b/certs/mocks/pki.go @@ -6,6 +6,7 @@ package mocks import ( "bufio" "bytes" + "context" "crypto/ecdsa" "crypto/rand" "crypto/rsa" @@ -160,6 +161,10 @@ func (a *agent) Revoke(serial string) (time.Time, error) { return time.Now(), nil } +func (a *agent) LoginAndRenew(ctx context.Context) error { + return nil +} + func publicKey(priv interface{}) (interface{}, error) { if priv == nil { return nil, errPrivateKeyEmpty diff --git a/certs/pki/vault.go b/certs/pki/vault.go index e1918704a6..91f4617c0a 100644 --- a/certs/pki/vault.go +++ b/certs/pki/vault.go @@ -5,11 +5,14 @@ package pki import ( + "context" "encoding/json" + "log/slog" "time" "github.com/absmach/magistrala/pkg/errors" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/approle" "github.com/mitchellh/mapstructure" ) @@ -30,6 +33,13 @@ var ( ErrFailedCertRevocation = errors.New("failed to revoke certificate") errFailedCertDecoding = errors.New("failed to decode response from vault service") + errFailedToLogin = errors.New("failed to login to Vault") + errFailedAppRole = errors.New("failed to create vault new app role") + errNoAuthInfo = errors.New("no auth information from Vault") + errNonRenewal = errors.New("token is not configured to be renewable") + errRenewWatcher = errors.New("unable to initialize new lifetime watcher for renewing auth token") + errFailedRenew = errors.New("failed to renew token") + errCouldNotRenew = errors.New("token can no longer be renewed") ) type Cert struct { @@ -52,10 +62,15 @@ type Agent interface { // Revoke revokes certificate from PKI Revoke(serial string) (time.Time, error) + + // Login to PKI and renews token + LoginAndRenew(ctx context.Context) error } type pkiAgent struct { - token string + appRole string + appSecret string + namespace string path string role string host string @@ -63,6 +78,8 @@ type pkiAgent struct { readURL string revokeURL string client *api.Client + secret *api.Secret + logger *slog.Logger } type certReq struct { @@ -75,7 +92,7 @@ type certRevokeReq struct { } // NewVaultClient instantiates a Vault client. -func NewVaultClient(token, host, path, role string) (Agent, error) { +func NewVaultClient(appRole, appSecret, host, namespace, path, role string, logger *slog.Logger) (Agent, error) { conf := api.DefaultConfig() conf.Address = host @@ -83,13 +100,19 @@ func NewVaultClient(token, host, path, role string) (Agent, error) { if err != nil { return nil, err } - client.SetToken(token) + if namespace != "" { + client.SetNamespace(namespace) + } + p := pkiAgent{ - token: token, + appRole: appRole, + appSecret: appSecret, host: host, + namespace: namespace, role: role, path: path, client: client, + logger: logger, issueURL: "/" + path + "/" + issue + "/" + role, readURL: "/" + path + "/" + cert + "/", revokeURL: "/" + path + "/" + revoke, @@ -162,3 +185,81 @@ func (p *pkiAgent) Revoke(serial string) (time.Time, error) { return time.Unix(0, int64(rev)*int64(time.Second)), nil } + +func (p *pkiAgent) LoginAndRenew(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + p.logger.Info("pki login and renew function stopping") + return nil + default: + err := p.login(ctx) + if err != nil { + p.logger.Info("unable to authenticate to Vault", slog.Any("error", err)) + time.Sleep(5 * time.Second) + break + } + tokenErr := p.manageTokenLifecycle() + if tokenErr != nil { + p.logger.Info("unable to start managing token lifecycle", slog.Any("error", tokenErr)) + time.Sleep(5 * time.Second) + } + } + } +} + +func (p *pkiAgent) login(ctx context.Context) error { + secretID := &approle.SecretID{FromString: p.appSecret} + + authMethod, err := approle.NewAppRoleAuth( + p.appRole, + secretID, + ) + if err != nil { + return errors.Wrap(errFailedAppRole, err) + } + if p.namespace != "" { + p.client.SetNamespace(p.namespace) + } + secret, err := p.client.Auth().Login(ctx, authMethod) + if err != nil { + return errors.Wrap(errFailedToLogin, err) + } + if secret == nil { + return errNoAuthInfo + } + p.secret = secret + return nil +} + +func (p *pkiAgent) manageTokenLifecycle() error { + renew := p.secret.Auth.Renewable + if !renew { + return errNonRenewal + } + + watcher, err := p.client.NewLifetimeWatcher(&api.LifetimeWatcherInput{ + Secret: p.secret, + Increment: 3600, // Requesting token for 3600s = 1h, If this is more than token_max_ttl, then response token will have token_max_ttl + }) + if err != nil { + return errors.Wrap(errRenewWatcher, err) + } + + go watcher.Start() + defer watcher.Stop() + + for { + select { + case err := <-watcher.DoneCh(): + if err != nil { + return errors.Wrap(errFailedRenew, err) + } + // This occurs once the token has reached max TTL or if token is disabled for renewal. + return errCouldNotRenew + + case renewal := <-watcher.RenewCh(): + p.logger.Info("Successfully renewed token", slog.Any("renewed_at", renewal.RenewedAt)) + } + } +} diff --git a/cmd/certs/main.go b/cmd/certs/main.go index 598d91a585..0d0237acfe 100644 --- a/cmd/certs/main.go +++ b/cmd/certs/main.go @@ -57,10 +57,12 @@ type config struct { SignCAKeyPath string `env:"MG_CERTS_SIGN_CA_KEY_PATH" envDefault:"ca.key"` // 3rd party PKI API access settings - PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""` - PkiPath string `env:"MG_VAULT_PKI_INT_PATH" envDefault:"pki_int"` - PkiRole string `env:"MG_VAULT_CA_ROLE_NAME" envDefault:"magistrala"` - PkiToken string `env:"MG_VAULT_TOKEN" envDefault:""` + PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""` + PkiAppRoleID string `env:"MG_CERTS_VAULT_APPROLE_ROLEID" envDefault:""` + PkiAppSecret string `env:"MG_CERTS_VAULT_APPROLE_SECRET" envDefault:""` + PkiNamespace string `env:"MG_CERTS_VAULT_NAMESPACE" envDefault:""` + PkiPath string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH" envDefault:"pki_int"` + PkiRole string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME" envDefault:"magistrala"` } func main() { @@ -94,13 +96,17 @@ func main() { return } - pkiclient, err := vault.NewVaultClient(cfg.PkiToken, cfg.PkiHost, cfg.PkiPath, cfg.PkiRole) + pkiclient, err := vault.NewVaultClient(cfg.PkiAppRoleID, cfg.PkiAppSecret, cfg.PkiHost, cfg.PkiNamespace, cfg.PkiPath, cfg.PkiRole, logger) if err != nil { logger.Error("failed to configure client for PKI engine") exitCode = 1 return } + g.Go(func() error { + return pkiclient.LoginAndRenew(ctx) + }) + dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { logger.Error(err.Error()) diff --git a/docker/.env b/docker/.env index c433e7f103..4a5e0bcb84 100644 --- a/docker/.env +++ b/docker/.env @@ -311,13 +311,58 @@ MG_PROVISION_CERTS_HOURS_VALID=2400h MG_PROVISION_CERTS_RSA_BITS=2048 MG_PROVISION_INSTANCE_ID= +### Vault +MG_VAULT_HOST=vault +MG_VAULT_PORT=8200 +MG_VAULT_ADDR=http://vault:8200 +MG_VAULT_NAMESPACE=magistrala +MG_VAULT_UNSEAL_KEY_1= +MG_VAULT_UNSEAL_KEY_2= +MG_VAULT_UNSEAL_KEY_3= +MG_VAULT_TOKEN= + +MG_VAULT_PKI_PATH=pki +MG_VAULT_PKI_ROLE_NAME=magistrala_int_ca +MG_VAULT_PKI_FILE_NAME=mg_root +MG_VAULT_PKI_CA_CN='Magistrala Root Certificate Authority' +MG_VAULT_PKI_CA_OU='Magistrala' +MG_VAULT_PKI_CA_O='Magistrala' +MG_VAULT_PKI_CA_C='FRANCE' +MG_VAULT_PKI_CA_L='PARIS' +MG_VAULT_PKI_CA_ST='PARIS' +MG_VAULT_PKI_CA_ADDR='5 Av. Anatole' +MG_VAULT_PKI_CA_PO='75007' +MG_VAULT_PKI_CLUSTER_PATH=http://localhost +MG_VAULT_PKI_CLUSTER_AIA_PATH=http://localhost + +MG_VAULT_PKI_INT_PATH=pki_int +MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME=magistrala_server_certs +MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME=magistrala_things_certs +MG_VAULT_PKI_INT_FILE_NAME=mg_int +MG_VAULT_PKI_INT_CA_CN='Magistrala Intermediate Certificate Authority' +MG_VAULT_PKI_INT_CA_OU='Magistrala' +MG_VAULT_PKI_INT_CA_O='Magistrala' +MG_VAULT_PKI_INT_CA_C='FRANCE' +MG_VAULT_PKI_INT_CA_L='PARIS' +MG_VAULT_PKI_INT_CA_ST='PARIS' +MG_VAULT_PKI_INT_CA_ADDR='5 Av. Anatole' +MG_VAULT_PKI_INT_CA_PO='75007' +MG_VAULT_PKI_INT_CLUSTER_PATH=http://localhost +MG_VAULT_PKI_INT_CLUSTER_AIA_PATH=http://localhost + +MG_VAULT_THINGS_CERTS_ISSUER_ROLEID=magistrala +MG_VAULT_THINGS_CERTS_ISSUER_SECRET=magistrala + # Certs MG_CERTS_LOG_LEVEL=debug MG_CERTS_SIGN_CA_PATH=/etc/ssl/certs/ca.crt MG_CERTS_SIGN_CA_KEY_PATH=/etc/ssl/certs/ca.key -MG_CERTS_VAULT_HOST=http://vault:8200 -MG_VAULT_PKI_INT_PATH=pki_int -MG_VAULT_CA_ROLE_NAME=magistrala +MG_CERTS_VAULT_HOST=${MG_VAULT_ADDR} +MG_CERTS_VAULT_NAMESPACE=${MG_VAULT_NAMESPACE} +MG_CERTS_VAULT_APPROLE_ROLEID=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} +MG_CERTS_VAULT_APPROLE_SECRET=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} +MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=${MG_VAULT_PKI_INT_PATH} +MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} MG_CERTS_HTTP_HOST=certs MG_CERTS_HTTP_PORT=9019 MG_CERTS_HTTP_SERVER_CERT= @@ -333,22 +378,6 @@ MG_CERTS_DB_SSL_KEY= MG_CERTS_DB_SSL_ROOT_CERT= MG_CERTS_INSTANCE_ID= -### Vault -MG_VAULT_HOST=vault -MG_VAULT_PORT=8200 -MG_VAULT_UNSEAL_KEY_1= -MG_VAULT_UNSEAL_KEY_2= -MG_VAULT_UNSEAL_KEY_3= -MG_VAULT_TOKEN= -MG_VAULT_CA_NAME=magistrala -MG_VAULT_CA_ROLE_NAME=magistrala -MG_VAULT_PKI_PATH=pki -MG_VAULT_PKI_INT_PATH=pki_int -MG_VAULT_CA_CN=magistrala.com -MG_VAULT_CA_OU='Magistrala Cloud' -MG_VAULT_CA_O='Magistrala Labs' -MG_VAULT_CA_C=Serbia -MG_VAULT_CA_L=Belgrade ### LoRa MG_LORA_ADAPTER_LOG_LEVEL=debug diff --git a/docker/README.md b/docker/README.md index 9361bcc0c8..4f36f96e77 100644 --- a/docker/README.md +++ b/docker/README.md @@ -117,3 +117,18 @@ services: volumes: - magistrala-broker-volume:/data ``` + +## Nginx Configuration + +Nginx is the entry point for all traffic to Magistrala. +By using environment variables file at `docker/.env` you can modify the below given Nginx directive. + +`MG_NGINX_SERVER_NAME` environmental variable is used to configure nginx directive `server_name`. If environmental variable `MG_NGINX_SERVER_NAME` is empty then default value `localhost` will set to `server_name`. + +`MG_NGINX_SERVER_CERT` environmental variable is used to configure nginx directive `ssl_certificate`. If environmental variable `MG_NGINX_SERVER_CERT` is empty then by default server certificate in the path `docker/ssl/certs/magistrala-server.crt` will be assigned. + +`MG_NGINX_SERVER_KEY` environmental variable is used to configure nginx directive `ssl_certificate_key`. If environmental variable `MG_NGINX_SERVER_KEY` is empty then by default server certificate key in the path `docker/ssl/certs/magistrala-server.key` will be assigned. + +`MG_NGINX_SERVER_CLIENT_CA` environmental variable is used to configure nginx directive `ssl_client_certificate`. If environmental variable `MG_NGINX_SERVER_CLIENT_CA` is empty then by default certificate in the path `docker/ssl/certs/ca.crt` will be assigned. + +`MG_NGINX_SERVER_DHPARAM` environmental variable is used to configure nginx directive `ssl_dhparam`. If environmental variable `MG_NGINX_SERVER_DHPARAM` is empty then by default file in the path `docker/ssl/dhparam.pem` will be assigned. diff --git a/docker/addons/certs/docker-compose.yml b/docker/addons/certs/docker-compose.yml index f5b1591a8e..41b9661161 100644 --- a/docker/addons/certs/docker-compose.yml +++ b/docker/addons/certs/docker-compose.yml @@ -44,10 +44,11 @@ services: MG_CERTS_SIGN_CA_PATH: ${MG_CERTS_SIGN_CA_PATH} MG_CERTS_SIGN_CA_KEY_PATH: ${MG_CERTS_SIGN_CA_KEY_PATH} MG_CERTS_VAULT_HOST: ${MG_CERTS_VAULT_HOST} - MG_VAULT_PKI_INT_PATH: ${MG_VAULT_PKI_INT_PATH} - MG_VAULT_CA_ROLE_NAME: ${MG_VAULT_CA_ROLE_NAME} - MG_VAULT_PKI_PATH: ${MG_VAULT_PKI_PATH} - MG_VAULT_TOKEN: ${MG_VAULT_TOKEN} + MG_CERTS_VAULT_NAMESPACE: ${MG_CERTS_VAULT_NAMESPACE} + MG_CERTS_VAULT_APPROLE_ROLEID: ${MG_CERTS_VAULT_APPROLE_ROLEID} + MG_CERTS_VAULT_APPROLE_SECRET: ${MG_CERTS_VAULT_APPROLE_SECRET} + MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH} + MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME} MG_CERTS_HTTP_HOST: ${MG_CERTS_HTTP_HOST} MG_CERTS_HTTP_PORT: ${MG_CERTS_HTTP_PORT} MG_CERTS_HTTP_SERVER_CERT: ${MG_CERTS_HTTP_SERVER_CERT} diff --git a/docker/addons/vault/.gitignore b/docker/addons/vault/.gitignore index 167a4fd8d7..4f14d396c2 100644 --- a/docker/addons/vault/.gitignore +++ b/docker/addons/vault/.gitignore @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 data +magistrala_things_certs_issue.hcl diff --git a/docker/addons/vault/README.md b/docker/addons/vault/README.md index d21b3c0221..193fa4dafa 100644 --- a/docker/addons/vault/README.md +++ b/docker/addons/vault/README.md @@ -6,35 +6,53 @@ When the Vault service is started, some initialization steps need to be done to ## Configuration -| Variable | Description | Default | -| --------------------- | ------------------------------------------------------- | ---------------- | -| MG_VAULT_HOST | Vault service address | vault | -| MG_VAULT_PORT | Vault service port | 8200 | -| MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_3 | Vault unseal key | "" | -| MG_VAULT_TOKEN | Vault cli access token | "" | -| MG_VAULT_PKI_PATH | Vault secrets engine path for CA | pki | -| MG_VAULT_PKI_INT_PATH | Vault secrets engine path for intermediate CA | pki_int | -| MG_VAULT_CA_ROLE_NAME | Vault secrets engine role | magistrala | -| MG_VAULT_CA_NAME | Certificates name used by `vault-set-pki.sh` | magistrala | -| MG_VAULT_CA_CN | Common name used for CA creation by `vault-set-pki.sh` | magistrala.com | -| MG_VAULT_CA_OU | Org unit used for CA creation by `vault-set-pki.sh` | Magistrala Cloud | -| MG_VAULT_CA_O | Organization used for CA creation by `vault-set-pki.sh` | Magistrala Labs | -| MG_VAULT_CA_C | Country used for CA creation by `vault-set-pki.sh` | Serbia | -| MG_VAULT_CA_L | Location used for CA creation by `vault-set-pki.sh` | Belgrade | + +| Variable | Description | Default | +| :---------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------- | +| MG_VAULT_HOST | Vault service address | vault | +| MG_VAULT_PORT | Vault service port | 8200 | +| MG_VAULT_ADDR | Vault Address | http://vault:8200 | +| MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | +| MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | +| MG_VAULT_UNSEAL_KEY_3 | Vault unseal key | "" | +| MG_VAULT_TOKEN | Vault cli access token | "" | +| MG_VAULT_PKI_PATH | Vault secrets engine path for Root CA | pki | +| MG_VAULT_PKI_ROLE_NAME | Vault Root CA role name to issue intermediate CA | magistrala_int_ca | +| MG_VAULT_PKI_FILE_NAME | Root CA Certificates name used by`vault_set_pki.sh` | mg_root | +| MG_VAULT_PKI_CA_CN | Common name used for Root CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | +| MG_VAULT_PKI_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_CA_O | Organization used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_CA_C | Country used for Root CA creation by`vault_set_pki.sh` | FRANCE | +| MG_VAULT_PKI_CA_L | Location used for Root CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_CA_ST | State or Provisions used for Root CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_CA_ADDR | Address used for Root CA creation by`vault_set_pki.sh` | 5 Av. Anatole | +| MG_VAULT_PKI_CA_PO | Postal code used for Root CA creation by`vault_set_pki.sh` | 75007 | +| MG_VAULT_PKI_CLUSTER_PATH | Vault Root CA Cluster Path | http://localhost | +| MG_VAULT_PKI_CLUSTER_AIA_PATH | Vault Root CA Cluster AIA Path | http://localhost | +| MG_VAULT_PKI_INT_PATH | Vault secrets engine path for Intermediate CA | pki_int | +| MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue server certificate | magistrala_server_certs | +| MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue Things certificates | magistrala_things_certs | +| MG_VAULT_PKI_INT_FILE_NAME | Intermediate CA Certificates name used by`vault_set_pki.sh` | mg_root | +| MG_VAULT_PKI_INT_CA_CN | Common name used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | +| MG_VAULT_PKI_INT_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_INT_CA_O | Organization used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_INT_CA_C | Country used for Intermediate CA creation by`vault_set_pki.sh` | FRANCE | +| MG_VAULT_PKI_INT_CA_L | Location used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_INT_CA_ST | State or Provisions used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_INT_CA_ADDR | Address used for Intermediate CA creation by`vault_set_pki.sh` | 5 Av. Anatole | +| MG_VAULT_PKI_INT_CA_PO | Postal code used for Intermediate CA creation by`vault_set_pki.sh` | 75007 | +| MG_VAULT_PKI_INT_CLUSTER_PATH | Vault Intermediate CA Cluster Path | http://localhost | +| MG_VAULT_PKI_INT_CLUSTER_AIA_PATH | Vault Intermediate CA Cluster AIA Path | http://localhost | +| MG_VAULT_THINGS_CERTS_ISSUER_ROLEID | Vault Intermediate CA Things Certificate issuer AppRole authentication RoleID | magistrala | +| MG_VAULT_THINGS_CERTS_ISSUER_SECRET | Vault Intermediate CA Things Certificate issuer AppRole authentication Secret | magistrala | ## Setup The following scripts are provided, which work on the running Vault service in Docker. -1. `vault-init.sh` - -Calls `vault operator init` to perform the initial vault initialization and generates -a `data/secrets` file which contains the Vault unseal keys and root tokens. +### 1. `vault_init.sh` -After this step, the corresponding Vault environment variables (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, -`MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`) should be updated in `.env` file. +Calls `vault operator init` to perform the initial vault initialization and generates a `docker/addons/vault/data/secrets` file which contains the Vault unseal keys and root tokens. Example contents for `data/secrets`: @@ -62,23 +80,43 @@ bash-4.4 Use 3 out of five keys presented and put it into .env file and than start the composition again Vault should be in unsealed state ( take a note that this is not recommended in terms of security, this is deployment for development) A real production deployment can use Vault auto unseal mode where vault gets unseal keys from some 3rd party KMS ( on AWS for example) ``` -2. `vault-unseal.sh` +### 2. `vault_copy_env.sh` + +After first step, the corresponding Vault environment variables (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`) should be updated in `.env` file. + +`vault_copy_env.sh` scripts copies values from `docker/addons/vault/data/secrets` file and update environmental variables `MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3` present in `.env` file. + +### 3. `vault_unseal.sh` This can be run after the initialization to unseal Vault, which is necessary for it to be used to store and/or get secrets. + This can be used if you don't want to restart the service. -The unseal environment variables need to be set in `.env` for the script to work (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, -`MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`). +The unseal environment variables need to be set in `.env` for the script to work (`MG_VAULT_TOKEN`,`MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`). + +This script should not be necessary to run after the initial setup, since the Vault service unseals itself when starting the container. + +### 4. `vault_set_pki.sh` + +This script is used to generate the root certificate, intermediate certificate and HTTPS server certificate. +All generate certificates, keys and CSR by `vault_set_pki.sh` will be present at `docker/addons/vault/data`. + +The parameters required for generating certificate are obtained from the environment variables which are loaded from `docker/.env`. + +Environmental variables starting with `MG_VAULT_PKI` in `docker/.env` file are used by `vault_set_pki.sh` to generate root CA. +Environmental variables starting with`MG_VAULT_PKI_INT` in `docker/.env` file are used by `vault_set_pki.sh` to generate intermediate CA. + +### 5. `vault_create_approle.sh` -This script should not be necessary to run after the initial setup, since the Vault service unseals itself when -starting the container. +This script is used to enable app role authorization in Vault. Certs service used the approle credentials to issue, revoke things certificate from vault intermedate CA. -3. `vault-set-pki.sh` +`vault_create_approle.sh` script by default tries to enable auth approle. +If approle is already enabled in vault, then use args `skip_enable_app_role` to skip enable auth approle step. +To skip enable auth approle step use the following `vault_create_approle.sh skip_enable_app_role` -This script is used to generate the root certificate, intermediate certificate and HTTPS server certificate. -After it runs, it copies the necessary certificates and keys to the `docker/ssl/certs` folder. +### 6. `vault_copy_certs.sh` -The CA parameters are obtained from the environment variables starting with `MG_VAULT_CA` in `.env` file. +This scripts copies the necessary certificates and keys from `docker/addons/vault/data` to the `docker/ssl/certs` folder. ## Vault CLI diff --git a/docker/addons/vault/magistrala_things_certs_issue.template.hcl b/docker/addons/vault/magistrala_things_certs_issue.template.hcl new file mode 100644 index 0000000000..1b13f6db1b --- /dev/null +++ b/docker/addons/vault/magistrala_things_certs_issue.template.hcl @@ -0,0 +1,32 @@ + +# Allow issue certificate with role with default issuer from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" { + capabilities = ["create", "update"] +} + +## Revole certificate from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/revoke" { + capabilities = ["create", "update"] +} + +## List Revoked Certificates from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/certs/revoked" { + capabilities = ["list"] +} + + +## List Certificates from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/certs" { + capabilities = ["list"] +} + +## Read Certificate from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/cert/+" { + capabilities = ["read"] +} +path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw" { + capabilities = ["read"] +} +path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw/pem" { + capabilities = ["read"] +} diff --git a/docker/addons/vault/vault-set-pki.sh b/docker/addons/vault/vault-set-pki.sh deleted file mode 100755 index c2a7003d4e..0000000000 --- a/docker/addons/vault/vault-set-pki.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -export MAGISTRALA_DIR=$scriptdir/../../../ - -cd $scriptdir - -readDotEnv() { - set -o allexport - source $MAGISTRALA_DIR/docker/.env - set +o allexport -} - -vault() { - docker exec -it magistrala-vault vault "$@" -} - -vaultEnablePKI() { - vault secrets enable -path ${MG_VAULT_PKI_PATH} pki - vault secrets tune -max-lease-ttl=87600h ${MG_VAULT_PKI_PATH} -} - -vaultAddRoleToSecret() { - vault write ${MG_VAULT_PKI_PATH}/roles/${MG_VAULT_CA_NAME} \ - allow_any_name=true \ - max_ttl="4300h" \ - default_ttl="4300h" \ - generate_lease=true -} - -vaultGenerateRootCACertificate() { - echo "Generate root CA certificate" - vault write -format=json ${MG_VAULT_PKI_PATH}/root/generate/exported \ - common_name="\"$MG_VAULT_CA_CN CA Root\"" \ - ou="\"$MG_VAULT_CA_OU\""\ - organization="\"$MG_VAULT_CA_O\"" \ - country="\"$MG_VAULT_CA_C\"" \ - locality="\"$MG_VAULT_CA_L\"" \ - ttl=87600h | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_NAME}_ca.crt) \ - >(jq -r .data.issuing_ca >data/${MG_VAULT_CA_NAME}_issuing_ca.crt) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_NAME}_ca.key) -} - -vaultGenerateIntermediateCAPKI() { - echo "Generate Intermediate CA PKI" - vault secrets enable -path=${MG_VAULT_PKI_INT_PATH} pki - vault secrets tune -max-lease-ttl=43800h ${MG_VAULT_PKI_INT_PATH} -} - -vaultGenerateIntermediateCSR() { - echo "Generate intermediate CSR" - vault write -format=json ${MG_VAULT_PKI_INT_PATH}/intermediate/generate/exported \ - common_name="$MG_VAULT_CA_CN Intermediate Authority" \ - | tee >(jq -r .data.csr >data/${MG_VAULT_CA_NAME}_int.csr) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_NAME}_int.key) -} - -vaultSignIntermediateCSR() { - echo "Sign intermediate CSR" - docker cp data/${MG_VAULT_CA_NAME}_int.csr magistrala-vault:/vault/${MG_VAULT_CA_NAME}_int.csr - vault write -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ - csr=@/vault/${MG_VAULT_CA_NAME}_int.csr \ - | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_NAME}_int.crt) \ - >(jq -r .data.issuing_ca >data/${MG_VAULT_CA_NAME}_int_issuing_ca.crt) -} - -vaultInjectIntermediateCertificate() { - echo "Inject Intermediate Certificate" - docker cp data/${MG_VAULT_CA_NAME}_int.crt magistrala-vault:/vault/${MG_VAULT_CA_NAME}_int.crt - vault write ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_CA_NAME}_int.crt -} - -vaultGenerateIntermediateCertificateBundle() { - echo "Generate intermediate certificate bundle" - cat data/${MG_VAULT_CA_NAME}_int.crt data/${MG_VAULT_CA_NAME}_ca.crt \ - > data/${MG_VAULT_CA_NAME}_int_bundle.crt -} - -vaultSetupIssuingURLs() { - echo "Setup URLs for CRL and issuing" - VAULT_ADDR=http://$MG_VAULT_HOST:$MG_VAULT_PORT - vault write ${MG_VAULT_PKI_INT_PATH}/config/urls \ - issuing_certificates="$VAULT_ADDR/v1/${MG_VAULT_PKI_INT_PATH}/ca" \ - crl_distribution_points="$VAULT_ADDR/v1/${MG_VAULT_PKI_INT_PATH}/crl" -} - -vaultSetupCARole() { - echo "Setup CA role" - vault write ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_CA_ROLE_NAME} \ - allow_subdomains=true \ - allow_any_name=true \ - max_ttl="720h" -} - -vaultGenerateServerCertificate() { - echo "Generate server certificate" - vault write -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_CA_ROLE_NAME} \ - common_name="$MG_VAULT_CA_CN" ttl="8670h" \ - | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_CN}.crt) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_CN}.key) -} - -vaultCleanupFiles() { - docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' -} - -if ! command -v jq &> /dev/null -then - echo "jq command could not be found, please install it and try again." - exit -fi - -readDotEnv - -mkdir -p data - -vault login ${MG_VAULT_TOKEN} - -vaultEnablePKI -vaultAddRoleToSecret -vaultGenerateRootCACertificate -vaultGenerateIntermediateCAPKI -vaultGenerateIntermediateCSR -vaultSignIntermediateCSR -vaultInjectIntermediateCertificate -vaultGenerateIntermediateCertificateBundle -vaultSetupIssuingURLs -vaultSetupCARole -vaultGenerateServerCertificate -vaultCleanupFiles - -echo "Copying certificate files" - -cp -v data/${MG_VAULT_CA_CN}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt -cp -v data/${MG_VAULT_CA_CN}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key -cp -v data/${MG_VAULT_CA_NAME}_int.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key -cp -v data/${MG_VAULT_CA_NAME}_int.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt -cp -v data/${MG_VAULT_CA_NAME}_int_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/bundle.pem - -exit 0 diff --git a/docker/addons/vault/vault_copy_certs.sh b/docker/addons/vault/vault_copy_certs.sh new file mode 100755 index 0000000000..1f00a8f516 --- /dev/null +++ b/docker/addons/vault/vault_copy_certs.sh @@ -0,0 +1,33 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +readDotEnv + +server_name="localhost" + +# Check if MG_NGINX_SERVER_NAME is set or not empty +if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then + server_name="$MG_NGINX_SERVER_NAME" +fi + +echo "Copying certificate files" +cp -v data/${server_name}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt +cp -v data/${server_name}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key +cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key +cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt + +exit 0 diff --git a/docker/addons/vault/vault-init.sh b/docker/addons/vault/vault_copy_env.sh similarity index 84% rename from docker/addons/vault/vault-init.sh rename to docker/addons/vault/vault_copy_env.sh index d8ab5cbbbe..92c773e513 100755 --- a/docker/addons/vault/vault-init.sh +++ b/docker/addons/vault/vault_copy_env.sh @@ -7,18 +7,13 @@ set -euo pipefail scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" export MAGISTRALA_DIR=$scriptdir/../../../ +cd $scriptdir + write_env() { sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env } -vault() { - docker exec -it magistrala-vault vault "$@" -} - -mkdir -p data - -vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) write_env diff --git a/docker/addons/vault/vault_create_approle.sh b/docker/addons/vault/vault_create_approle.sh new file mode 100755 index 0000000000..59b8b44c7a --- /dev/null +++ b/docker/addons/vault/vault_create_approle.sh @@ -0,0 +1,95 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +SKIP_ENABLE_APP_ROLE=${1:-} + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +vaultCreatePolicyFile() { + envsubst ' + ${MG_VAULT_PKI_INT_PATH} + ${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} + ' < magistrala_things_certs_issue.template.hcl > magistrala_things_certs_issue.hcl +} +vaultCreatePolicy() { + echo "Creating new policy for AppRole" + docker cp magistrala_things_certs_issue.hcl magistrala-vault:/vault/magistrala_things_certs_issue.hcl + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl +} + +vaultEnableAppRole() { + if [ "$SKIP_ENABLE_APP_ROLE" == "skip_enable_app_role" ]; then + echo "Skipping Enable AppRole" + else + echo "Enabling AppRole" + vault auth enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} approle + fi +} + +vaultDeleteRole() { + echo "Deleteing old AppRole" + vault delete -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer +} + +vaultCreateRole() { + echo "Creating new AppRole" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer \ + token_policies=magistrala_things_certs_issue secret_id_num_uses=0 \ + secret_id_ttl=0 token_ttl=1h token_max_ttl=3h token_num_uses=0 +} + +vaultWriteCustomRoleID(){ + echo "Writing custom role id" + vault read -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} +} + +vaultWriteCustomSecret() { + echo "Writing custom secret" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -f auth/approle/role/magistrala_things_certs_issuer/secret-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/custom-secret-id secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} num_uses=0 ttl=0 +} + +vaultTestRoleLogin() { + echo "Testing custom roleid secret by logging in" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/login \ + role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} \ + secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} + +} +if ! command -v jq &> /dev/null +then + echo "jq command could not be found, please install it and try again." + exit +fi + +readDotEnv + +vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} + +vaultCreatePolicyFile +vaultCreatePolicy +vaultEnableAppRole +vaultDeleteRole +vaultCreateRole +vaultWriteCustomRoleID +vaultWriteCustomSecret +vaultTestRoleLogin + +exit 0 diff --git a/docker/addons/vault/vault_init.sh b/docker/addons/vault/vault_init.sh new file mode 100755 index 0000000000..e375cbc23a --- /dev/null +++ b/docker/addons/vault/vault_init.sh @@ -0,0 +1,18 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +mkdir -p data + +vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) diff --git a/docker/addons/vault/vault_set_pki.sh b/docker/addons/vault/vault_set_pki.sh new file mode 100755 index 0000000000..51bfee1cff --- /dev/null +++ b/docker/addons/vault/vault_set_pki.sh @@ -0,0 +1,199 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +server_name="localhost" + +# Check if MG_NGINX_SERVER_NAME is set or not empty +if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then + server_name="$MG_NGINX_SERVER_NAME" +fi + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +vaultEnablePKI() { + vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path ${MG_VAULT_PKI_PATH} pki + vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=87600h ${MG_VAULT_PKI_PATH} +} + +vaultConfigPKIClusterPath() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/cluster aia_path=${MG_VAULT_PKI_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_CLUSTER_PATH} +} + +vaultConfigPKICrl() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" +} + +vaultAddRoleToSecret() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/roles/${MG_VAULT_PKI_ROLE_NAME} \ + allow_any_name=true \ + max_ttl="8760h" \ + default_ttl="8760h" \ + generate_lease=true +} + +vaultGenerateRootCACertificate() { + echo "Generate root CA certificate" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/generate/exported \ + common_name="\"$MG_VAULT_PKI_CA_CN\"" \ + ou="\"$MG_VAULT_PKI_CA_OU\"" \ + organization="\"$MG_VAULT_PKI_CA_O\"" \ + country="\"$MG_VAULT_PKI_CA_C\"" \ + locality="\"$MG_VAULT_PKI_CA_L\"" \ + province="\"$MG_VAULT_PKI_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_CA_PO\"" \ + ttl=87600h | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_FILE_NAME}_ca.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_FILE_NAME}_issuing_ca.crt) \ + >(jq -r .data.private_key >data/${MG_VAULT_PKI_FILE_NAME}_ca.key) +} + +vaultSetupRootCAIssuingURLs() { + echo "Setup URLs for CRL and issuing" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/urls \ + issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ca" \ + crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/crl" \ + ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ocsp" \ + enable_templating=true +} + +vaultGenerateIntermediateCAPKI() { + echo "Generate Intermediate CA PKI" + vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path=${MG_VAULT_PKI_INT_PATH} pki + vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=43800h ${MG_VAULT_PKI_INT_PATH} +} + +vaultConfigIntermediatePKIClusterPath() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/cluster aia_path=${MG_VAULT_PKI_INT_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_INT_CLUSTER_PATH} +} + +vaultConfigIntermediatePKICrl() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" +} + +vaultGenerateIntermediateCSR() { + echo "Generate intermediate CSR" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/intermediate/generate/exported \ + common_name="\"$MG_VAULT_PKI_INT_CA_CN\"" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.csr >data/${MG_VAULT_PKI_INT_FILE_NAME}.csr) \ + >(jq -r .data.private_key >data/${MG_VAULT_PKI_INT_FILE_NAME}.key) +} + +vaultSignIntermediateCSR() { + echo "Sign intermediate CSR" + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.csr magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ + csr=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_INT_FILE_NAME}.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt) +} + +vaultInjectIntermediateCertificate() { + echo "Inject Intermediate Certificate" + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.crt magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt +} + +vaultGenerateIntermediateCertificateBundle() { + echo "Generate intermediate certificate bundle" + cat data/${MG_VAULT_PKI_INT_FILE_NAME}.crt data/${MG_VAULT_PKI_FILE_NAME}_ca.crt \ + > data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt +} + +vaultSetupIntermediateIssuingURLs() { + echo "Setup URLs for CRL and issuing" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/urls \ + issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ca" \ + crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/crl" \ + ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ocsp" \ + enable_templating=true +} + +vaultSetupServerCertsRole() { + echo "Setup Server Certs role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + allow_subdomains=true \ + max_ttl="4320h" +} + +vaultGenerateServerCertificate() { + echo "Generate server certificate" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + common_name="$server_name" ttl="4320h" \ + | tee >(jq -r .data.certificate >data/${server_name}.crt) \ + >(jq -r .data.private_key >data/${server_name}.key) +} + +vaultSetupThingCertsRole() { + echo "Setup Thing Certs role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} \ + allow_subdomains=true \ + allow_any_name=true \ + max_ttl="2160h" +} + +vaultCleanupFiles() { + docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' +} + +if ! command -v jq &> /dev/null +then + echo "jq command could not be found, please install it and try again." + exit +fi + +readDotEnv + +mkdir -p data + +vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} + +vaultEnablePKI +vaultConfigPKIClusterPath +vaultConfigPKICrl +vaultAddRoleToSecret +vaultGenerateRootCACertificate +vaultSetupRootCAIssuingURLs +vaultGenerateIntermediateCAPKI +vaultConfigIntermediatePKIClusterPath +vaultConfigIntermediatePKICrl +vaultGenerateIntermediateCSR +vaultSignIntermediateCSR +vaultInjectIntermediateCertificate +vaultGenerateIntermediateCertificateBundle +vaultSetupIntermediateIssuingURLs +vaultSetupServerCertsRole +vaultGenerateServerCertificate +vaultSetupThingCertsRole +vaultCleanupFiles + +exit 0 diff --git a/docker/addons/vault/vault-unseal.sh b/docker/addons/vault/vault_unseal.sh similarity index 100% rename from docker/addons/vault/vault-unseal.sh rename to docker/addons/vault/vault_unseal.sh diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2e501cd887..93781c863e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -234,10 +234,18 @@ services: - ./nginx/entrypoint.sh:/docker-entrypoint.d/entrypoint.sh - ./nginx/snippets:/etc/nginx/snippets - ./ssl/authorization.js:/etc/nginx/authorization.js - - ./ssl/certs/magistrala-server.crt:/etc/ssl/certs/magistrala-server.crt - - ./ssl/certs/ca.crt:/etc/ssl/certs/ca.crt - - ./ssl/certs/magistrala-server.key:/etc/ssl/private/magistrala-server.key - - ./ssl/dhparam.pem:/etc/ssl/certs/dhparam.pem + - type: bind + source: ${MG_NGINX_SERVER_CERT:-./ssl/certs/magistrala-server.crt} + target: /etc/ssl/certs/magistrala-server.crt + - type: bind + source: ${MG_NGINX_SERVER_KEY:-./ssl/certs/magistrala-server.key} + target: /etc/ssl/private/magistrala-server.key + - type: bind + source: ${MG_NGINX_SERVER_CLIENT_CA:-./ssl/certs/ca.crt} + target: /etc/ssl/certs/ca.crt + - type: bind + source: ${MG_NGINX_SERVER_DHPARAM:-./ssl/dhparam.pem} + target: /etc/ssl/certs/dhparam.pem ports: - ${MG_NGINX_HTTP_PORT}:${MG_NGINX_HTTP_PORT} - ${MG_NGINX_SSL_PORT}:${MG_NGINX_SSL_PORT} @@ -716,7 +724,7 @@ services: MG_UI_DB_SSL_MODE: ${MG_UI_DB_SSL_MODE} MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} - MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} + MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: diff --git a/docker/nginx/entrypoint.sh b/docker/nginx/entrypoint.sh index 1076b600fc..6b90377035 100755 --- a/docker/nginx/entrypoint.sh +++ b/docker/nginx/entrypoint.sh @@ -12,6 +12,7 @@ else fi envsubst ' + ${MG_NGINX_SERVER_NAME} ${MG_AUTH_HTTP_PORT} ${MG_USERS_HTTP_PORT} ${MG_THINGS_HTTP_PORT} diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index aa0fa05edb..b33a0667b0 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -39,6 +39,14 @@ http { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; + set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; + + if ($dynamic_server_name = '') { + set $dynamic_server_name "localhost"; + } + + server_name $dynamic_server_name; + include snippets/ssl.conf; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; @@ -48,8 +56,6 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - server_name localhost; - location ~ ^/(channels)/(.+)/(things)/(.+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index efed25da56..1c35f83e63 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -45,6 +45,15 @@ http { listen [::]:80 default_server; listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; + + set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; + + if ($dynamic_server_name = '') { + set $dynamic_server_name "localhost"; + } + + server_name $dynamic_server_name; + ssl_verify_client optional; include snippets/ssl.conf; include snippets/ssl-client.conf; @@ -56,8 +65,6 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - server_name localhost; - # Proxy pass to users service location ~ ^/(users|groups|password|policies|authorize) { include snippets/proxy-headers.conf; @@ -69,7 +76,7 @@ http { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; - } + } # Proxy pass to things service location ~ ^/(things|channels|connect|disconnect|identify) { @@ -77,7 +84,7 @@ http { add_header Access-Control-Expose-Headers Location; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; } - + location ^~ /things/policies { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; @@ -90,7 +97,7 @@ http { add_header Access-Control-Expose-Headers Location; proxy_pass http://invitations:${MG_INVITATIONS_HTTP_PORT}; } - + location /health { include snippets/proxy-headers.conf; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; @@ -100,7 +107,7 @@ http { include snippets/proxy-headers.conf; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; } - + # Proxy pass to magistrala-http-adapter location /http/ { include snippets/verify-ssl-client.conf; diff --git a/go.mod b/go.mod index 69b3397290..ce6d068a69 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/gookit/color v1.5.4 github.com/gopcua/opcua v0.1.6 github.com/gorilla/websocket v1.5.1 - github.com/hashicorp/vault/api v1.10.0 + github.com/hashicorp/vault/api v1.12.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/ivanpirog/coloredcobra v1.0.1 @@ -54,8 +54,8 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 - golang.org/x/crypto v0.18.0 - golang.org/x/net v0.20.0 + golang.org/x/crypto v0.19.0 + golang.org/x/net v0.21.0 golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac @@ -110,6 +110,7 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/vault/api/auth/approle v0.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect @@ -174,7 +175,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 7ad59cad7d..5853168b52 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,7 @@ github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/authzed/authzed-go v0.10.1 h1:0aX2Ox9PPPknID92kLs/FnmhCmfl6Ni16v3ZTLsds5M= github.com/authzed/authzed-go v0.10.1/go.mod h1:ZsaFPCiMjwT0jLW0gCyYzh3elHqhKDDGGRySyykXwqc= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 h1:bQeIwWWRI9bl93poTqpix4sYHi+gnXUPK7N6bMtXzBE= @@ -33,6 +34,7 @@ github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403/go.mod h1:s3qC7V7 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= @@ -41,7 +43,9 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -95,6 +99,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -179,6 +184,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -203,27 +209,38 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMW github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= +github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= +github.com/hashicorp/vault/api/auth/approle v0.6.0/go.mod h1:CCoIl1xBC3lAWpd1HV+0ovk76Z8b8Mdepyk21h3pGk0= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -339,13 +356,18 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -355,8 +377,11 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -424,6 +449,7 @@ github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90/go.mod h1:Z7oKFLSG github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= @@ -447,6 +473,7 @@ github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTG github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -589,8 +616,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -630,8 +661,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= @@ -646,6 +680,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -657,6 +692,7 @@ golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -678,8 +714,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -687,6 +727,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -697,8 +739,10 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= From ee8d24dd80c3ed903772d29e235f18b3ba724382 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:31:11 +0300 Subject: [PATCH 44/71] NOISSUE - Remove duplicate errors (#2086) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- auth/api/grpc/client.go | 9 +- auth/api/grpc/endpoint_test.go | 6 +- auth/api/grpc/server.go | 12 +- auth/api/http/keys/endpoint_test.go | 8 +- auth/jwt/token_test.go | 3 +- auth/jwt/tokenizer.go | 9 +- auth/mocks/auth_client.go | 3 +- auth/postgres/domains.go | 6 +- auth/postgres/domains_test.go | 14 +- auth/postgres/key.go | 3 +- auth/postgres/key_test.go | 9 +- auth/service.go | 12 +- auth/service_test.go | 44 ++-- auth/spicedb/policies.go | 11 +- bootstrap/api/endpoint_test.go | 3 +- bootstrap/api/transport.go | 5 +- bootstrap/mocks/configs.go | 23 +- bootstrap/postgres/configs.go | 71 +++--- bootstrap/postgres/configs_test.go | 27 +- bootstrap/service.go | 5 +- certs/mocks/certs.go | 10 +- certs/mocks/pki.go | 3 +- certs/postgres/certs.go | 13 +- cmd/users/main.go | 4 +- coap/adapter.go | 13 +- coap/api/transport.go | 11 +- consumers/notifiers/mocks/subscriptions.go | 12 +- consumers/notifiers/postgres/subscriptions.go | 19 +- .../notifiers/postgres/subscriptions_test.go | 5 +- consumers/notifiers/service_test.go | 3 +- http/handler.go | 3 +- internal/api/common.go | 9 +- internal/api/common_test.go | 8 +- internal/apiutil/transport_test.go | 5 +- internal/groups/api/endpoint_test.go | 45 ++-- internal/groups/service.go | 16 +- internal/groups/service_test.go | 230 +++++++++--------- invitations/api/endpoint_test.go | 12 +- invitations/mocks/invitations.go | 14 +- invitations/mocks/service.go | 15 +- mqtt/handler.go | 3 +- mqtt/handler_test.go | 3 +- pkg/clients/postgres/clients_test.go | 46 ++-- pkg/errors/service/types.go | 11 +- pkg/errors/types.go | 36 --- pkg/sdk/go/certs_test.go | 3 +- pkg/sdk/go/channels_test.go | 7 +- pkg/sdk/go/groups_test.go | 5 +- pkg/sdk/go/message_test.go | 6 +- pkg/sdk/go/things_test.go | 44 ++-- pkg/sdk/go/users_test.go | 30 +-- provision/api/endpoint_test.go | 6 +- provision/service_test.go | 14 +- readers/api/endpoint.go | 3 +- things/api/grpc/client.go | 9 +- things/api/grpc/endpoint_test.go | 13 +- things/api/grpc/server.go | 5 +- things/api/http/endpoints_test.go | 2 +- things/api/http/requests.go | 9 +- things/api/http/requests_test.go | 17 +- things/cache/things.go | 15 +- things/cache/things_test.go | 17 +- things/postgres/clients_test.go | 16 +- things/service.go | 12 +- things/service_test.go | 32 +-- things/standalone/standalone.go | 2 +- twins/api/http/transport.go | 1 - twins/mocks/twins.go | 8 +- twins/mongodb/twins.go | 19 +- twins/mongodb/twins_test.go | 3 +- twins/service.go | 2 +- users/api/endpoint_test.go | 4 +- users/mocks/hasher.go | 3 +- users/postgres/clients.go | 15 +- users/postgres/clients_test.go | 14 +- users/service.go | 12 +- users/service_test.go | 106 ++++---- ws/adapter.go | 5 +- ws/handler.go | 2 +- 79 files changed, 648 insertions(+), 645 deletions(-) diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index ff0b3418c6..566a47bc34 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" @@ -725,13 +726,13 @@ func decodeError(err error) error { if st, ok := status.FromError(err); ok { switch st.Code() { case codes.NotFound: - return errors.Wrap(errors.ErrNotFound, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) case codes.InvalidArgument: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.AlreadyExists: - return errors.Wrap(errors.ErrConflict, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) case codes.Unauthenticated: - return errors.Wrap(errors.ErrAuthentication, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) case codes.OK: if msg := st.Message(); msg != "" { return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) @@ -740,7 +741,7 @@ func decodeError(err error) error { case codes.FailedPrecondition: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.PermissionDenied: - return errors.Wrap(errors.ErrAuthorization, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) default: return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) } diff --git a/auth/api/grpc/endpoint_test.go b/auth/api/grpc/endpoint_test.go index 699777a817..f70a411b5e 100644 --- a/auth/api/grpc/endpoint_test.go +++ b/auth/api/grpc/endpoint_test.go @@ -103,7 +103,7 @@ func TestIssue(t *testing.T) { domainID: domainID, kind: auth.APIKey, issueResponse: auth.Token{}, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "issue for invalid key type", @@ -119,7 +119,7 @@ func TestIssue(t *testing.T) { domainID: "", kind: auth.APIKey, issueResponse: auth.Token{}, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } @@ -158,7 +158,7 @@ func TestRefresh(t *testing.T) { token: inValidToken, domainID: domainID, issueResponse: auth.Token{}, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "refresh token with empty token", diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index 89c2a2add9..7af9e075a5 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -538,19 +538,17 @@ func encodeError(err error) error { err == apiutil.ErrMissingPolicyObj, err == apiutil.ErrMalformedPolicyAct: return status.Error(codes.InvalidArgument, err.Error()) - case errors.Contains(err, errors.ErrAuthentication), - errors.Contains(err, svcerr.ErrAuthentication), + case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, auth.ErrKeyExpired), err == apiutil.ErrMissingEmail, err == apiutil.ErrBearerToken: return status.Error(codes.Unauthenticated, err.Error()) - case errors.Contains(err, errors.ErrAuthorization), - errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, errors.ErrDomainAuthorization): + case errors.Contains(err, svcerr.ErrAuthorization), + errors.Contains(err, svcerr.ErrDomainAuthorization): return status.Error(codes.PermissionDenied, err.Error()) - case errors.Contains(err, errors.ErrNotFound): + case errors.Contains(err, svcerr.ErrNotFound): return status.Error(codes.NotFound, err.Error()) - case errors.Contains(err, errors.ErrConflict): + case errors.Contains(err, svcerr.ErrConflict): return status.Error(codes.AlreadyExists, err.Error()) default: return status.Error(codes.Internal, err.Error()) diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index ae46d02b2a..0f2ec5eba8 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -20,7 +20,7 @@ import ( "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/apiutil" mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -240,21 +240,21 @@ func TestRetrieve(t *testing.T) { id: "non-existing", token: token.AccessToken, status: http.StatusNotFound, - err: errors.ErrNotFound, + err: svcerr.ErrNotFound, }, { desc: "retrieve a key with an invalid token", id: k.AccessToken, token: "wrong", status: http.StatusUnauthorized, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "retrieve a key with an empty token", token: "", id: k.AccessToken, status: http.StatusUnauthorized, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } diff --git a/auth/jwt/token_test.go b/auth/jwt/token_test.go index d441727f69..4996e1d24f 100644 --- a/auth/jwt/token_test.go +++ b/auth/jwt/token_test.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/auth" authjwt "github.com/absmach/magistrala/auth/jwt" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" @@ -119,7 +120,7 @@ func TestParse(t *testing.T) { desc: "parse invalid key", key: auth.Key{}, token: "invalid", - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "parse expired key", diff --git a/auth/jwt/tokenizer.go b/auth/jwt/tokenizer.go index 8c34f25222..b3fc92e63e 100644 --- a/auth/jwt/tokenizer.go +++ b/auth/jwt/tokenizer.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" ) @@ -68,7 +69,7 @@ func (repo *tokenizer) Issue(key auth.Key) (string, error) { } tkn, err := builder.Build() if err != nil { - return "", errors.Wrap(errors.ErrAuthentication, err) + return "", errors.Wrap(svcerr.ErrAuthentication, err) } signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, repo.secret)) if err != nil { @@ -88,7 +89,7 @@ func (repo *tokenizer) Parse(token string) (auth.Key, error) { return auth.Key{}, ErrExpiry } - return auth.Key{}, errors.Wrap(errors.ErrAuthentication, err) + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) } validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError { if t.Issuer() != issuerName { @@ -111,11 +112,11 @@ func (repo *tokenizer) Parse(token string) (auth.Key, error) { tType, ok := tkn.Get(tokenType) if !ok { - return auth.Key{}, errors.Wrap(errors.ErrAuthentication, err) + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) } ktype, err := strconv.ParseInt(fmt.Sprintf("%v", tType), 10, 64) if err != nil { - return auth.Key{}, errors.Wrap(errors.ErrAuthentication, err) + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) } key.ID = tkn.JwtID() diff --git a/auth/mocks/auth_client.go b/auth/mocks/auth_client.go index 0d166cb3b5..1fe4076b3b 100644 --- a/auth/mocks/auth_client.go +++ b/auth/mocks/auth_client.go @@ -7,7 +7,6 @@ import ( context "context" "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/mock" "google.golang.org/grpc" @@ -54,7 +53,7 @@ func (m *AuthClient) Authorize(ctx context.Context, in *magistrala.AuthorizeReq, return &magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization } if in.GetObject() == InvalidValue || in.GetObject() == "" { - return &magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization + return &magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization } return ret.Get(0).(*magistrala.AuthorizeRes), ret.Error(1) diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index a7d77e3b37..e622e7ca16 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -269,7 +269,7 @@ func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.Do dbd, err := toDBDomain(d) if err != nil { - return auth.Domain{}, errors.Wrap(errors.ErrUpdateEntity, err) + return auth.Domain{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } row, err := repo.db.NamedQueryContext(ctx, q, dbd) if err != nil { @@ -516,7 +516,7 @@ type dbDomainsPage struct { func toDBClientsPage(pm auth.Page) (dbDomainsPage, error) { _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) if err != nil { - return dbDomainsPage{}, errors.Wrap(errors.ErrViewEntity, err) + return dbDomainsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } return dbDomainsPage{ Total: pm.Total, @@ -571,7 +571,7 @@ func buildPageQuery(pm auth.Page) (string, error) { mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) if err != nil { - return "", errors.Wrap(errors.ErrViewEntity, err) + return "", errors.Wrap(repoerr.ErrViewEntity, err) } if mq != "" { query = append(query, mq) diff --git a/auth/postgres/domains_test.go b/auth/postgres/domains_test.go index 4ecf721854..0755a631b7 100644 --- a/auth/postgres/domains_test.go +++ b/auth/postgres/domains_test.go @@ -55,7 +55,7 @@ func TestAddPolicyCopy(t *testing.T) { ObjectType: "unknown", ObjectID: "unknown", }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, } @@ -174,7 +174,7 @@ func TestSave(t *testing.T) { UpdatedBy: userID, Status: auth.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, } @@ -226,13 +226,13 @@ func TestRetrieveByID(t *testing.T) { desc: "retrieve non-existing client", domainID: inValid, response: auth.Domain{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve with empty client id", domainID: "", response: auth.Domain{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -530,7 +530,7 @@ func TestRetrieveAllByIDs(t *testing.T) { response: auth.DomainsPage{ Page: auth.Page{}, }, - err: errors.ErrViewEntity, + err: repoerr.ErrViewEntity, }, { desc: "retrieve all by ids and id", @@ -851,7 +851,7 @@ func TestListDomains(t *testing.T) { response: auth.DomainsPage{ Page: auth.Page{}, }, - err: errors.ErrViewEntity, + err: repoerr.ErrViewEntity, }, } @@ -976,7 +976,7 @@ func TestUpdate(t *testing.T) { Metadata: &clients.Metadata{"key": make(chan int)}, }, response: auth.Domain{}, - err: errors.ErrUpdateEntity, + err: repoerr.ErrUpdateEntity, }, } diff --git a/auth/postgres/key.go b/auth/postgres/key.go index e17f9c49a3..0f45df5cda 100644 --- a/auth/postgres/key.go +++ b/auth/postgres/key.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/postgres" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" ) var ( @@ -48,7 +49,7 @@ func (kr *repo) Retrieve(ctx context.Context, issuerID, id string) (auth.Key, er key := dbKey{} if err := kr.db.QueryRowxContext(ctx, q, issuerID, id).StructScan(&key); err != nil { if err == sql.ErrNoRows { - return auth.Key{}, errors.ErrNotFound + return auth.Key{}, repoerr.ErrNotFound } return auth.Key{}, postgres.HandleError(errRetrieve, err) diff --git a/auth/postgres/key_test.go b/auth/postgres/key_test.go index a89cdc1c98..e415524b0a 100644 --- a/auth/postgres/key_test.go +++ b/auth/postgres/key_test.go @@ -13,6 +13,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/auth/postgres" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -63,7 +64,7 @@ func TestKeySave(t *testing.T) { IssuedAt: time.Now(), ExpiresAt: expTime, }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "save with empty id", @@ -184,19 +185,19 @@ func TestKeyRetrieve(t *testing.T) { desc: "retrieve key with empty issuer id", id: key.ID, issuer: "", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve non-existent key", id: "", issuer: key.Issuer, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve non-existent key with empty issuer id", id: "", issuer: "", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/auth/service.go b/auth/service.go index 9f9f130fce..0626036c6e 100644 --- a/auth/service.go +++ b/auth/service.go @@ -189,7 +189,7 @@ func (svc service) Authorize(ctx context.Context, pr PolicyReq) error { } if key.Subject == "" { if pr.ObjectType == GroupType || pr.ObjectType == ThingType || pr.ObjectType == DomainType { - return errors.ErrDomainAuthorization + return svcerr.ErrDomainAuthorization } return svcerr.ErrAuthentication } @@ -208,7 +208,7 @@ func (svc service) checkPolicy(ctx context.Context, pr PolicyReq) error { domainID := pr.Domain if domainID == "" { if pr.ObjectType != DomainType { - return errors.ErrDomainAuthorization + return svcerr.ErrDomainAuthorization } domainID = pr.Object } @@ -238,7 +238,7 @@ func (svc service) checkDomain(ctx context.Context, subjectType, subject, domain Object: domainID, ObjectType: DomainType, }); err != nil { - return errors.ErrDomainAuthorization + return svcerr.ErrDomainAuthorization } case FreezeStatus: if err := svc.agent.CheckPolicy(ctx, PolicyReq{ @@ -248,10 +248,10 @@ func (svc service) checkDomain(ctx context.Context, subjectType, subject, domain Object: MagistralaObject, ObjectType: PlatformType, }); err != nil { - return errors.ErrDomainAuthorization + return svcerr.ErrDomainAuthorization } default: - return errors.ErrDomainAuthorization + return svcerr.ErrDomainAuthorization } return nil @@ -543,7 +543,7 @@ func (svc service) CreateDomain(ctx context.Context, token string, d Domain) (do domainID, err := svc.idProvider.ID() if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrUniqueID, err) + return Domain{}, err } d.ID = domainID diff --git a/auth/service_test.go b/auth/service_test.go index c8a56f52eb..6e16c59589 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -199,10 +199,10 @@ func TestIssue(t *testing.T) { ObjectType: auth.PlatformType, Permission: auth.AdminPermission, }, - checkPolicyErr: errors.ErrNotFound, + checkPolicyErr: repoerr.ErrNotFound, retrieveByIDResponse: auth.Domain{}, retreiveByIDErr: repoerr.ErrNotFound, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "issue login key with failed check on platform admin with enabled status", @@ -351,8 +351,8 @@ func TestIssue(t *testing.T) { IssuedAt: time.Now(), }, token: accessToken, - saveErr: errors.ErrNotFound, - err: errors.ErrNotFound, + saveErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, tc := range cases3 { @@ -406,7 +406,7 @@ func TestIssue(t *testing.T) { }, token: refreshToken, checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: errors.ErrNotFound, + retrieveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrAuthorization, }, { @@ -463,7 +463,7 @@ func TestIssue(t *testing.T) { }, token: refreshToken, checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: errors.ErrNotFound, + retrieveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, } @@ -678,7 +678,7 @@ func TestIdentify(t *testing.T) { desc: "identify invalid key", key: "invalid", idt: "", - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "identify invalid key type", @@ -782,7 +782,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, Permission: auth.AdminPermission, }, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "authorize token with disabled domain", @@ -848,8 +848,8 @@ func TestAuthorize(t *testing.T) { Name: groupName, Status: auth.DisabledStatus, }, - checkPolicyErr1: errors.ErrDomainAuthorization, - err: errors.ErrDomainAuthorization, + checkPolicyErr1: svcerr.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "authorize token with frozen domain", @@ -915,8 +915,8 @@ func TestAuthorize(t *testing.T) { Name: groupName, Status: auth.FreezeStatus, }, - checkPolicyErr1: errors.ErrDomainAuthorization, - err: errors.ErrDomainAuthorization, + checkPolicyErr1: svcerr.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "authorize token with domain with invalid status", @@ -949,7 +949,7 @@ func TestAuthorize(t *testing.T) { Name: groupName, Status: auth.AllStatus, }, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { @@ -1006,7 +1006,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.PlatformKind, Permission: auth.AdminPermission, }, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "authorize a user key successfully", @@ -1043,7 +1043,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.PlatformType, Permission: auth.AdminPermission, }, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, } for _, tc := range cases { @@ -1770,14 +1770,14 @@ func TestRetrieveDomain(t *testing.T) { desc: "retrieve non-existing domain", token: accessToken, domainID: inValid, - domainRepoErr: errors.ErrNotFound, + domainRepoErr: repoerr.ErrNotFound, err: svcerr.ErrAuthorization, }, { desc: "retrieve domain with failed to retrieve by id", token: accessToken, domainID: validID, - domainRepoErr1: errors.ErrNotFound, + domainRepoErr1: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, } @@ -1829,14 +1829,14 @@ func TestRetrieveDomainPermissions(t *testing.T) { desc: "retrieve domain permissions with failed to retrieve permissions", token: accessToken, domainID: validID, - retreivePermissionsErr: errors.ErrNotFound, + retreivePermissionsErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, { desc: "retrieve domain permissions with failed to retrieve by id", token: accessToken, domainID: validID, - retreiveByIDErr: errors.ErrNotFound, + retreiveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, } @@ -1905,7 +1905,7 @@ func TestUpdateDomain(t *testing.T) { Name: &valid, Alias: &valid, }, - retrieveByIDErr: errors.ErrNotFound, + retrieveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, { @@ -1973,7 +1973,7 @@ func TestChangeDomainStatus(t *testing.T) { domainReq: auth.DomainReq{ Status: &disabledStatus, }, - retreieveByIDErr: errors.ErrNotFound, + retreieveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrAuthorization, }, { @@ -2607,7 +2607,7 @@ func TestListUsersDomains(t *testing.T) { Limit: 10, Permission: auth.AdminPermission, }, - listDomainErr: errors.ErrNotFound, + listDomainErr: repoerr.ErrNotFound, err: svcerr.ErrViewEntity, }, } diff --git a/auth/spicedb/policies.go b/auth/spicedb/policies.go index 4012569253..927f0bfe84 100644 --- a/auth/spicedb/policies.go +++ b/auth/spicedb/policies.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/authzed-go/v1" @@ -76,7 +77,7 @@ func (pa *policyAgent) CheckPolicy(ctx context.Context, pr auth.PolicyReq) error if reason, ok := v1.CheckPermissionResponse_Permissionship_name[int32(resp.Permissionship)]; ok { return errors.Wrap(svcerr.ErrAuthorization, errors.New(reason)) } - return errors.ErrAuthorization + return svcerr.ErrAuthorization } func (pa *policyAgent) AddPolicies(ctx context.Context, prs []auth.PolicyReq) error { @@ -776,13 +777,13 @@ func convertToGrpcStatus(gst *gstatus.Status) *status.Status { func convertGRPCStatusToError(st *status.Status) error { switch st.Code() { case codes.NotFound: - return errors.Wrap(errors.ErrNotFound, errors.New(st.Message())) + return errors.Wrap(repoerr.ErrNotFound, errors.New(st.Message())) case codes.InvalidArgument: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.AlreadyExists: - return errors.Wrap(errors.ErrConflict, errors.New(st.Message())) + return errors.Wrap(repoerr.ErrConflict, errors.New(st.Message())) case codes.Unauthenticated: - return errors.Wrap(errors.ErrAuthentication, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) case codes.Internal: return errors.Wrap(errInternal, errors.New(st.Message())) case codes.OK: @@ -793,7 +794,7 @@ func convertGRPCStatusToError(st *status.Status) error { case codes.FailedPrecondition: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.PermissionDenied: - return errors.Wrap(errors.ErrAuthorization, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) default: return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) } diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 8d6f568c0d..5f4c87b361 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -27,6 +27,7 @@ import ( "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" "github.com/absmach/magistrala/pkg/uuid" @@ -92,7 +93,7 @@ var ( missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()}) missingKeyRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerKey.Error(), Msg: apiutil.ErrValidation.Error()}) - bsErrorRes = toJSON(apiutil.ErrorRes{Err: errors.ErrNotFound.Error(), Msg: bootstrap.ErrBootstrap.Error()}) + bsErrorRes = toJSON(apiutil.ErrorRes{Err: svcerr.ErrNotFound.Error(), Msg: bootstrap.ErrBootstrap.Error()}) extKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKey.Error()}) extSecKeyRes = toJSON(apiutil.ErrorRes{Err: "encoding/hex: invalid byte: U+002D '-'", Msg: bootstrap.ErrExternalKeySecure.Error()}) ) diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index 740275c8f3..572a45126a 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -295,11 +295,10 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, bootstrap.ErrExternalKeySecure), errors.Contains(err, svcerr.ErrAuthorization): w.WriteHeader(http.StatusForbidden) - case errors.Contains(err, svcerr.ErrConflict): - w.WriteHeader(http.StatusConflict) case errors.Contains(err, bootstrap.ErrThings): w.WriteHeader(http.StatusServiceUnavailable) - + case errors.Contains(err, svcerr.ErrConflict): + w.WriteHeader(http.StatusConflict) case errors.Contains(err, svcerr.ErrCreateEntity), errors.Contains(err, svcerr.ErrUpdateEntity), errors.Contains(err, svcerr.ErrViewEntity), diff --git a/bootstrap/mocks/configs.go b/bootstrap/mocks/configs.go index 56d6b8d04e..1f9bad9c23 100644 --- a/bootstrap/mocks/configs.go +++ b/bootstrap/mocks/configs.go @@ -11,7 +11,8 @@ import ( "sync" "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" ) const emptyState = -1 @@ -39,7 +40,7 @@ func (crm *configRepositoryMock) Save(_ context.Context, config bootstrap.Config for _, v := range crm.configs { if v.ThingID == config.ThingID || v.ExternalID == config.ExternalID { - return "", errors.ErrConflict + return "", repoerr.ErrConflict } } @@ -68,10 +69,10 @@ func (crm *configRepositoryMock) RetrieveByID(_ context.Context, token, id strin c, ok := crm.configs[id] if !ok { - return bootstrap.Config{}, errors.ErrNotFound + return bootstrap.Config{}, repoerr.ErrNotFound } if c.Owner != token { - return bootstrap.Config{}, errors.ErrAuthentication + return bootstrap.Config{}, svcerr.ErrAuthentication } return c, nil @@ -131,7 +132,7 @@ func (crm *configRepositoryMock) RetrieveByExternalID(_ context.Context, externa } } - return bootstrap.Config{}, errors.ErrNotFound + return bootstrap.Config{}, repoerr.ErrNotFound } func (crm *configRepositoryMock) Update(_ context.Context, config bootstrap.Config) error { @@ -140,7 +141,7 @@ func (crm *configRepositoryMock) Update(_ context.Context, config bootstrap.Conf cfg, ok := crm.configs[config.ThingID] if !ok || cfg.Owner != config.Owner { - return errors.ErrNotFound + return repoerr.ErrNotFound } cfg.Name = config.Name @@ -161,7 +162,7 @@ func (crm *configRepositoryMock) UpdateCert(_ context.Context, owner, thingID, c } } if _, ok := crm.configs[forUpdate.ThingID]; !ok { - return bootstrap.Config{}, errors.ErrNotFound + return bootstrap.Config{}, repoerr.ErrNotFound } forUpdate.ClientCert = clientCert forUpdate.ClientKey = clientKey @@ -177,7 +178,7 @@ func (crm *configRepositoryMock) UpdateConnections(_ context.Context, token, id config, ok := crm.configs[id] if !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } for _, ch := range channels { @@ -188,7 +189,7 @@ func (crm *configRepositoryMock) UpdateConnections(_ context.Context, token, id for _, conn := range connections { ch, ok := crm.channels[conn] if !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } config.Channels = append(config.Channels, ch) } @@ -217,10 +218,10 @@ func (crm *configRepositoryMock) ChangeState(_ context.Context, token, id string config, ok := crm.configs[id] if !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } if config.Owner != token { - return errors.ErrAuthentication + return svcerr.ErrAuthentication } config.State = state diff --git a/bootstrap/postgres/configs.go b/bootstrap/postgres/configs.go index 74fe79b275..998f2a28aa 100644 --- a/bootstrap/postgres/configs.go +++ b/bootstrap/postgres/configs.go @@ -16,6 +16,7 @@ import ( "github.com/absmach/magistrala/internal/postgres" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/jackc/pgerrcode" "github.com/jackc/pgtype" "github.com/jackc/pgx/v5/pgconn" @@ -52,7 +53,7 @@ func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsCo tx, err := cr.db.BeginTxx(ctx, nil) if err != nil { - return "", errors.Wrap(errors.ErrCreateEntity, err) + return "", errors.Wrap(repoerr.ErrCreateEntity, err) } dbcfg := toDBConfig(cfg) @@ -60,11 +61,11 @@ func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsCo if _, err := tx.NamedExec(q, dbcfg); err != nil { e := err if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == pgerrcode.UniqueViolation { - e = errors.ErrConflict + e = repoerr.ErrConflict } cr.rollback("Failed to insert a Config", tx) - return "", errors.Wrap(errors.ErrCreateEntity, e) + return "", errors.Wrap(repoerr.ErrCreateEntity, e) } if err := insertChannels(ctx, cfg.Owner, cfg.Channels, tx); err != nil { @@ -97,14 +98,14 @@ func (cr configRepository) RetrieveByID(ctx context.Context, owner, id string) ( row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { if err == sql.ErrNoRows { - return bootstrap.Config{}, errors.Wrap(errors.ErrNotFound, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, err) } - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(errors.ErrNotFound, row.Err()) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) } if err := row.StructScan(&dbcfg); err != nil { @@ -119,7 +120,7 @@ func (cr configRepository) RetrieveByID(ctx context.Context, owner, id string) ( rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err)) - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } defer rows.Close() @@ -128,13 +129,13 @@ func (cr configRepository) RetrieveByID(ctx context.Context, owner, id string) ( dbch := dbChannel{} if err := rows.StructScan(&dbch); err != nil { cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err)) - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } dbch.Owner = nullString(dbcfg.Owner) ch, err := toChannel(dbch) if err != nil { - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } chans = append(chans, ch) } @@ -202,17 +203,17 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { if err == sql.ErrNoRows { - return bootstrap.Config{}, errors.Wrap(errors.ErrNotFound, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, err) } - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(errors.ErrNotFound, row.Err()) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) } if err := row.StructScan(&dbcfg); err != nil { - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } q = `SELECT magistrala_channel, name, metadata FROM channels ch @@ -223,7 +224,7 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err)) - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } defer rows.Close() @@ -232,13 +233,13 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID dbch := dbChannel{} if err := rows.StructScan(&dbch); err != nil { cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err)) - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } ch, err := toChannel(dbch) if err != nil { cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err)) - return bootstrap.Config{}, errors.Wrap(errors.ErrViewEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } channels = append(channels, ch) @@ -262,16 +263,16 @@ func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) err res, err := cr.db.NamedExecContext(ctx, q, dbcfg) if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } cnt, err := res.RowsAffected() if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } if cnt == 0 { - return errors.ErrNotFound + return repoerr.ErrNotFound } return nil @@ -291,12 +292,12 @@ func (cr configRepository) UpdateCert(ctx context.Context, owner, thingID, clien row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { - return bootstrap.Config{}, errors.Wrap(errors.ErrUpdateEntity, err) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } defer row.Close() if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(errors.ErrNotFound, row.Err()) + return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) } if err := row.StructScan(&dbcfg); err != nil { @@ -309,27 +310,27 @@ func (cr configRepository) UpdateCert(ctx context.Context, owner, thingID, clien func (cr configRepository) UpdateConnections(ctx context.Context, owner, id string, channels []bootstrap.Channel, connections []string) error { tx, err := cr.db.BeginTxx(ctx, nil) if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } if err := insertChannels(ctx, owner, channels, tx); err != nil { cr.rollback("Failed to insert Channels during the update", tx) - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } if err := updateConnections(ctx, owner, id, connections, tx); err != nil { if e, ok := err.(*pgconn.PgError); ok { if e.Code == pgerrcode.ForeignKeyViolation { - return errors.ErrNotFound + return repoerr.ErrNotFound } } cr.rollback("Failed to update connections during the update", tx) - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } if err := tx.Commit(); err != nil { cr.rollback("Failed to commit Config update", tx) - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } return nil @@ -343,7 +344,7 @@ func (cr configRepository) Remove(ctx context.Context, owner, id string) error { } if _, err := cr.db.NamedExecContext(ctx, q, dbcfg); err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil { @@ -364,16 +365,16 @@ func (cr configRepository) ChangeState(ctx context.Context, owner, id string, st res, err := cr.db.NamedExecContext(ctx, q, dbcfg) if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } cnt, err := res.RowsAffected() if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } if cnt == 0 { - return errors.ErrNotFound + return repoerr.ErrNotFound } return nil @@ -393,14 +394,14 @@ func (cr configRepository) ListExisting(ctx context.Context, owner string, ids [ q := "SELECT magistrala_channel, name, metadata FROM channels WHERE owner = $1 AND magistrala_channel = ANY ($2)" rows, err := cr.db.QueryxContext(ctx, q, owner, chans) if err != nil { - return []bootstrap.Channel{}, errors.Wrap(errors.ErrViewEntity, err) + return []bootstrap.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) } for rows.Next() { var dbch dbChannel if err := rows.StructScan(&dbch); err != nil { cr.log.Error(fmt.Sprintf("Failed to read retrieved channels due to %s", err)) - return []bootstrap.Channel{}, errors.Wrap(errors.ErrViewEntity, err) + return []bootstrap.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) } ch, err := toChannel(dbch) @@ -423,7 +424,7 @@ func (cr configRepository) RemoveThing(ctx context.Context, id string) error { cr.log.Warn("Failed to clean dangling channels after removal") } if err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } return nil } @@ -431,7 +432,7 @@ func (cr configRepository) RemoveThing(ctx context.Context, id string) error { func (cr configRepository) UpdateChannel(ctx context.Context, c bootstrap.Channel) error { dbch, err := toDBChannel("", c) if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) + return errors.Wrap(repoerr.ErrUpdateEntity, err) } q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by @@ -506,7 +507,7 @@ func insertChannels(_ context.Context, owner string, channels []bootstrap.Channe if _, err := tx.NamedExec(q, chans); err != nil { e := err if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation { - e = errors.ErrConflict + e = repoerr.ErrConflict } return e } diff --git a/bootstrap/postgres/configs_test.go b/bootstrap/postgres/configs_test.go index edea8df8ce..81e3d85256 100644 --- a/bootstrap/postgres/configs_test.go +++ b/bootstrap/postgres/configs_test.go @@ -12,6 +12,7 @@ import ( "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/bootstrap/postgres" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -75,19 +76,19 @@ func TestSave(t *testing.T) { desc: "save config with same Thing ID", config: duplicateThing, connections: nil, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "save config with same external ID", config: duplicateExternal, connections: nil, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "save config with same Channels", config: duplicateChannels, connections: channels, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, } for _, tc := range cases { @@ -134,19 +135,19 @@ func TestRetrieveByID(t *testing.T) { desc: "retrieve config with wrong owner", owner: "2", id: id, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve a non-existing config", owner: c.Owner, id: nonexistentConfID.String(), - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve a config with invalid ID", owner: c.Owner, id: "invalid", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, tc := range cases { @@ -260,7 +261,7 @@ func TestRetrieveByExternalID(t *testing.T) { { desc: "retrieve with invalid external ID", externalID: strconv.Itoa(numConfigs + 1), - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve with external key", @@ -305,7 +306,7 @@ func TestUpdate(t *testing.T) { { desc: "update with wrong owner", config: wrongOwner, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update a config", @@ -359,7 +360,7 @@ func TestUpdateCert(t *testing.T) { ca: "", owner: "wrong", expectedConfig: bootstrap.Config{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update a config", @@ -425,7 +426,7 @@ func TestUpdateConnections(t *testing.T) { id: "unknown", channels: nil, connections: []string{channels[1]}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update connections", @@ -481,7 +482,7 @@ func TestRemove(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("%d: failed to remove config due to: %s", i, err)) _, err = repo.RetrieveByID(context.Background(), c.Owner, id) - assert.True(t, errors.Contains(err, errors.ErrNotFound), fmt.Sprintf("%d: expected %s got %s", i, errors.ErrNotFound, err)) + assert.True(t, errors.Contains(err, repoerr.ErrNotFound), fmt.Sprintf("%d: expected %s got %s", i, repoerr.ErrNotFound, err)) } } @@ -512,13 +513,13 @@ func TestChangeState(t *testing.T) { desc: "change state with wrong owner", id: saved, owner: "2", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "change state with wrong id", id: "wrong", owner: c.Owner, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "change state to Active", diff --git a/bootstrap/service.go b/bootstrap/service.go index 5730054ccf..2f87d1ae57 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -12,6 +12,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" ) @@ -241,7 +242,7 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri for _, c := range disconnect { if err := bs.sdk.DisconnectThing(id, c, token); err != nil { - if errors.Contains(err, errors.ErrNotFound) { + if errors.Contains(err, repoerr.ErrNotFound) { continue } return ErrThings @@ -330,7 +331,7 @@ func (bs bootstrapService) ChangeState(ctx context.Context, token, id string, st case Inactive: for _, c := range cfg.Channels { if err := bs.sdk.DisconnectThing(cfg.ThingID, c.ID, token); err != nil { - if errors.Contains(err, errors.ErrNotFound) { + if errors.Contains(err, repoerr.ErrNotFound) { continue } return ErrThings diff --git a/certs/mocks/certs.go b/certs/mocks/certs.go index c613c7d11e..232cd8ddf4 100644 --- a/certs/mocks/certs.go +++ b/certs/mocks/certs.go @@ -8,7 +8,7 @@ import ( "sync" "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" ) var _ certs.Repository = (*certsRepoMock)(nil) @@ -63,7 +63,7 @@ func (c *certsRepoMock) RetrieveAll(ctx context.Context, ownerID string, offset, oc, ok := c.certsByThingID[ownerID] if !ok { - return certs.Page{}, errors.ErrNotFound + return certs.Page{}, repoerr.ErrNotFound } var crts []certs.Cert @@ -89,7 +89,7 @@ func (c *certsRepoMock) Remove(ctx context.Context, ownerID, serial string) erro defer c.mu.Unlock() crt, ok := c.certsBySerial[serial] if !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } delete(c.certsBySerial, crt.Serial) delete(c.certsByThingID, crt.ThingID) @@ -105,7 +105,7 @@ func (c *certsRepoMock) RetrieveByThing(ctx context.Context, ownerID, thingID st cs, ok := c.certsByThingID[ownerID][thingID] if !ok { - return certs.Page{}, errors.ErrNotFound + return certs.Page{}, repoerr.ErrNotFound } var crts []certs.Cert @@ -130,7 +130,7 @@ func (c *certsRepoMock) RetrieveBySerial(ctx context.Context, ownerID, serialID crt, ok := c.certsBySerial[serialID] if !ok { - return certs.Cert{}, errors.ErrNotFound + return certs.Cert{}, repoerr.ErrNotFound } return crt, nil diff --git a/certs/mocks/pki.go b/certs/mocks/pki.go index 502917ff99..0245242d37 100644 --- a/certs/mocks/pki.go +++ b/certs/mocks/pki.go @@ -20,6 +20,7 @@ import ( "github.com/absmach/magistrala/certs/pki" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" ) const keyBits = 2048 @@ -151,7 +152,7 @@ func (a *agent) Read(serial string) (pki.Cert, error) { crt, ok := a.certs[serial] if !ok { - return pki.Cert{}, errors.ErrNotFound + return pki.Cert{}, repoerr.ErrNotFound } return crt, nil diff --git a/certs/postgres/certs.go b/certs/postgres/certs.go index ea6667253c..429f1d81c1 100644 --- a/certs/postgres/certs.go +++ b/certs/postgres/certs.go @@ -13,6 +13,7 @@ import ( "github.com/absmach/magistrala/certs" "github.com/absmach/magistrala/internal/postgres" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" @@ -77,7 +78,7 @@ func (cr certsRepository) Save(ctx context.Context, cert certs.Cert) (string, er tx, err := cr.db.BeginTxx(ctx, nil) if err != nil { - return "", errors.Wrap(errors.ErrCreateEntity, err) + return "", errors.Wrap(repoerr.ErrCreateEntity, err) } dbcrt := toDBCert(cert) @@ -90,7 +91,7 @@ func (cr certsRepository) Save(ctx context.Context, cert certs.Cert) (string, er cr.rollback("Failed to insert a Cert", tx, err) - return "", errors.Wrap(errors.ErrCreateEntity, e) + return "", errors.Wrap(repoerr.ErrCreateEntity, e) } if err := tx.Commit(); err != nil { @@ -102,14 +103,14 @@ func (cr certsRepository) Save(ctx context.Context, cert certs.Cert) (string, er func (cr certsRepository) Remove(ctx context.Context, ownerID, serial string) error { if _, err := cr.RetrieveBySerial(ctx, ownerID, serial); err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } q := `DELETE FROM certs WHERE serial = :serial` var c certs.Cert c.Serial = serial dbcrt := toDBCert(c) if _, err := cr.db.NamedExecContext(ctx, q, dbcrt); err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } return nil } @@ -156,10 +157,10 @@ func (cr certsRepository) RetrieveBySerial(ctx context.Context, ownerID, serialI if err := cr.db.QueryRowxContext(ctx, q, ownerID, serialID).StructScan(&dbcrt); err != nil { pqErr, ok := err.(*pgconn.PgError) if err == sql.ErrNoRows || ok && pgerrcode.InvalidTextRepresentation == pqErr.Code { - return c, errors.Wrap(errors.ErrNotFound, err) + return c, errors.Wrap(repoerr.ErrNotFound, err) } - return c, errors.Wrap(errors.ErrViewEntity, err) + return c, errors.Wrap(repoerr.ErrViewEntity, err) } c = toCert(dbcrt) diff --git a/cmd/users/main.go b/cmd/users/main.go index 938833ba24..98a9437541 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -32,7 +32,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/auth" mgclients "github.com/absmach/magistrala/pkg/clients" - "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" @@ -298,7 +298,7 @@ func createAdminPolicy(ctx context.Context, clientID string, authClient magistra return err } if !addPolicyRes.Added { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } } return nil diff --git a/coap/adapter.go b/coap/adapter.go index 380213f85f..8a22398c1a 100644 --- a/coap/adapter.go +++ b/coap/adapter.go @@ -13,6 +13,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" ) @@ -63,10 +64,10 @@ func (svc *adapterService) Publish(ctx context.Context, key string, msg *messagi } res, err := svc.auth.Authorize(ctx, ar) if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + return errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } msg.Publisher = res.GetId() @@ -83,10 +84,10 @@ func (svc *adapterService) Subscribe(ctx context.Context, key, chanID, subtopic } res, err := svc.auth.Authorize(ctx, ar) if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + return errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) if subtopic != "" { @@ -111,10 +112,10 @@ func (svc *adapterService) Unsubscribe(ctx context.Context, key, chanID, subtopi } res, err := svc.auth.Authorize(ctx, ar) if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + return errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) if subtopic != "" { diff --git a/coap/api/transport.go b/coap/api/transport.go index 784e341386..64d8fabb44 100644 --- a/coap/api/transport.go +++ b/coap/api/transport.go @@ -17,6 +17,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/coap" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" "github.com/go-chi/chi/v5" "github.com/plgd-dev/go-coap/v2/message" @@ -98,16 +99,16 @@ func handler(w mux.ResponseWriter, m *mux.Message) { resp.Code = codes.Created err = service.Publish(m.Context, key, msg) default: - err = errors.ErrNotFound + err = svcerr.ErrNotFound } if err != nil { switch { case err == errBadOptions: resp.Code = codes.BadOption - case err == errors.ErrNotFound: + case err == svcerr.ErrNotFound: resp.Code = codes.NotFound - case errors.Contains(err, errors.ErrAuthorization), - errors.Contains(err, errors.ErrAuthentication): + case errors.Contains(err, svcerr.ErrAuthorization), + errors.Contains(err, svcerr.ErrAuthentication): resp.Code = codes.Unauthorized default: resp.Code = codes.InternalServerError @@ -174,7 +175,7 @@ func parseKey(msg *mux.Message) (string, error) { } vars := strings.Split(authKey, "=") if len(vars) != 2 || vars[0] != authQuery { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return vars[1], nil } diff --git a/consumers/notifiers/mocks/subscriptions.go b/consumers/notifiers/mocks/subscriptions.go index d146b084a6..594c18f991 100644 --- a/consumers/notifiers/mocks/subscriptions.go +++ b/consumers/notifiers/mocks/subscriptions.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" ) var _ notifiers.SubscriptionsRepository = (*subRepoMock)(nil) @@ -30,11 +30,11 @@ func (srm *subRepoMock) Save(_ context.Context, sub notifiers.Subscription) (str srm.mu.Lock() defer srm.mu.Unlock() if _, ok := srm.subs[sub.ID]; ok { - return "", errors.ErrConflict + return "", repoerr.ErrConflict } for _, s := range srm.subs { if s.Contact == sub.Contact && s.Topic == sub.Topic { - return "", errors.ErrConflict + return "", repoerr.ErrConflict } } @@ -47,7 +47,7 @@ func (srm *subRepoMock) Retrieve(_ context.Context, id string) (notifiers.Subscr defer srm.mu.Unlock() ret, ok := srm.subs[id] if !ok { - return notifiers.Subscription{}, errors.ErrNotFound + return notifiers.Subscription{}, repoerr.ErrNotFound } return ret, nil } @@ -101,7 +101,7 @@ func (srm *subRepoMock) RetrieveAll(_ context.Context, pm notifiers.PageMetadata } if len(subs) == 0 { - return notifiers.Page{}, errors.ErrNotFound + return notifiers.Page{}, repoerr.ErrNotFound } ret := notifiers.Page{ @@ -124,7 +124,7 @@ func (srm *subRepoMock) Remove(_ context.Context, id string) error { srm.mu.Lock() defer srm.mu.Unlock() if _, ok := srm.subs[id]; !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } delete(srm.subs, id) return nil diff --git a/consumers/notifiers/postgres/subscriptions.go b/consumers/notifiers/postgres/subscriptions.go index e6117c5756..1d445d93b5 100644 --- a/consumers/notifiers/postgres/subscriptions.go +++ b/consumers/notifiers/postgres/subscriptions.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/consumers/notifiers" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" ) @@ -41,9 +42,9 @@ func (repo subscriptionsRepo) Save(ctx context.Context, sub notifiers.Subscripti row, err := repo.db.NamedQueryContext(ctx, q, dbSub) if err != nil { if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation { - return "", errors.Wrap(errors.ErrConflict, err) + return "", errors.Wrap(repoerr.ErrConflict, err) } - return "", errors.Wrap(errors.ErrCreateEntity, err) + return "", errors.Wrap(repoerr.ErrCreateEntity, err) } defer row.Close() @@ -55,9 +56,9 @@ func (repo subscriptionsRepo) Retrieve(ctx context.Context, id string) (notifier sub := dbSubscription{} if err := repo.db.QueryRowxContext(ctx, q, id).StructScan(&sub); err != nil { if err == sql.ErrNoRows { - return notifiers.Subscription{}, errors.Wrap(errors.ErrNotFound, err) + return notifiers.Subscription{}, errors.Wrap(repoerr.ErrNotFound, err) } - return notifiers.Subscription{}, errors.Wrap(errors.ErrViewEntity, err) + return notifiers.Subscription{}, errors.Wrap(repoerr.ErrViewEntity, err) } return fromDBSub(sub), nil @@ -90,7 +91,7 @@ func (repo subscriptionsRepo) RetrieveAll(ctx context.Context, pm notifiers.Page rows, err := repo.db.NamedQueryContext(ctx, q, args) if err != nil { - return notifiers.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } defer rows.Close() @@ -98,19 +99,19 @@ func (repo subscriptionsRepo) RetrieveAll(ctx context.Context, pm notifiers.Page for rows.Next() { sub := dbSubscription{} if err := rows.StructScan(&sub); err != nil { - return notifiers.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } subs = append(subs, fromDBSub(sub)) } if len(subs) == 0 { - return notifiers.Page{}, errors.ErrNotFound + return notifiers.Page{}, repoerr.ErrNotFound } cq := fmt.Sprintf(`SELECT COUNT(*) FROM subscriptions %s`, condition) total, err := total(ctx, repo.db, cq, args) if err != nil { - return notifiers.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } ret := notifiers.Page{ @@ -126,7 +127,7 @@ func (repo subscriptionsRepo) Remove(ctx context.Context, id string) error { q := `DELETE from subscriptions WHERE id = $1` if r := repo.db.QueryRowxContext(ctx, q, id); r.Err() != nil { - return errors.Wrap(errors.ErrRemoveEntity, r.Err()) + return errors.Wrap(repoerr.ErrRemoveEntity, r.Err()) } return nil } diff --git a/consumers/notifiers/postgres/subscriptions_test.go b/consumers/notifiers/postgres/subscriptions_test.go index 25a06f62ba..507de04052 100644 --- a/consumers/notifiers/postgres/subscriptions_test.go +++ b/consumers/notifiers/postgres/subscriptions_test.go @@ -11,6 +11,7 @@ import ( "github.com/absmach/magistrala/consumers/notifiers" "github.com/absmach/magistrala/consumers/notifiers/postgres" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" @@ -59,7 +60,7 @@ func TestSave(t *testing.T) { desc: "save duplicate", sub: sub2, id: "", - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, } @@ -104,7 +105,7 @@ func TestView(t *testing.T) { desc: "retrieve not existing", sub: notifiers.Subscription{}, id: "non-existing", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index 1cd8546e75..c4fb5212e4 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -14,6 +14,7 @@ import ( "github.com/absmach/magistrala/consumers/notifiers/mocks" "github.com/absmach/magistrala/internal/testsutil" "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/messaging" "github.com/absmach/magistrala/pkg/uuid" @@ -60,7 +61,7 @@ func TestCreateSubscription(t *testing.T) { token: exampleUser1, sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, id: "", - err: svcerr.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "test with empty token", diff --git a/http/handler.go b/http/handler.go index ec5321bfb1..fb17586aa2 100644 --- a/http/handler.go +++ b/http/handler.go @@ -16,6 +16,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/mproxy/pkg/session" ) @@ -156,7 +157,7 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e return err } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } msg.Publisher = res.GetId() diff --git a/internal/api/common.go b/internal/api/common.go index 0f5193f164..886b934657 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -124,18 +124,15 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrValidation): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), - errors.Contains(err, errors.ErrAuthentication), errors.Contains(err, apiutil.ErrBearerToken): w.WriteHeader(http.StatusUnauthorized) case errors.Contains(err, svcerr.ErrNotFound): w.WriteHeader(http.StatusNotFound) - case errors.Contains(err, svcerr.ErrConflict), - errors.Contains(err, postgres.ErrMemberAlreadyAssigned), - errors.Contains(err, errors.ErrConflict): + case errors.Contains(err, postgres.ErrMemberAlreadyAssigned), + errors.Contains(err, svcerr.ErrConflict): w.WriteHeader(http.StatusConflict) case errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, errors.ErrAuthorization), - errors.Contains(err, errors.ErrDomainAuthorization): + errors.Contains(err, svcerr.ErrDomainAuthorization): w.WriteHeader(http.StatusForbidden) case errors.Contains(err, apiutil.ErrUnsupportedContentType): w.WriteHeader(http.StatusUnsupportedMediaType) diff --git a/internal/api/common_test.go b/internal/api/common_test.go index a29bba99ca..c243aac1b5 100644 --- a/internal/api/common_test.go +++ b/internal/api/common_test.go @@ -256,7 +256,7 @@ func TestEncodeError(t *testing.T) { desc: "Unauthorized", errs: []error{ svcerr.ErrAuthentication, - errors.ErrAuthentication, + svcerr.ErrAuthentication, apiutil.ErrBearerToken, }, code: http.StatusUnauthorized, @@ -273,7 +273,7 @@ func TestEncodeError(t *testing.T) { desc: "Conflict", errs: []error{ svcerr.ErrConflict, - errors.ErrConflict, + svcerr.ErrConflict, }, code: http.StatusConflict, }, @@ -281,8 +281,8 @@ func TestEncodeError(t *testing.T) { desc: "Forbidden", errs: []error{ svcerr.ErrAuthorization, - errors.ErrAuthorization, - errors.ErrDomainAuthorization, + svcerr.ErrAuthorization, + svcerr.ErrDomainAuthorization, }, code: http.StatusForbidden, }, diff --git a/internal/apiutil/transport_test.go b/internal/apiutil/transport_test.go index 8c31009a06..28c206bf8b 100644 --- a/internal/apiutil/transport_test.go +++ b/internal/apiutil/transport_test.go @@ -14,6 +14,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" ) @@ -339,11 +340,11 @@ func TestLoggingErrorEncoder(t *testing.T) { }{ { desc: "error contains ErrValidation", - err: errors.Wrap(apiutil.ErrValidation, errors.ErrAuthentication), + err: errors.Wrap(apiutil.ErrValidation, svcerr.ErrAuthentication), }, { desc: "error does not contain ErrValidation", - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } diff --git a/internal/groups/api/endpoint_test.go b/internal/groups/api/endpoint_test.go index 04d4612e9a..a55eb198fb 100644 --- a/internal/groups/api/endpoint_test.go +++ b/internal/groups/api/endpoint_test.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" "github.com/stretchr/testify/assert" @@ -96,9 +97,9 @@ func TestCreateGroupEndpoint(t *testing.T) { }, }, svcResp: groups.Group{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: createGroupRes{created: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -157,9 +158,9 @@ func TestViewGroupEndpoint(t *testing.T) { id: testsutil.GenerateUUID(t), }, svcResp: groups.Group{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: viewGroupRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -214,9 +215,9 @@ func TestViewGroupPermsEndpoint(t *testing.T) { id: testsutil.GenerateUUID(t), }, svcResp: []string{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: viewGroupPermsRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -269,9 +270,9 @@ func TestEnableGroupEndpoint(t *testing.T) { id: testsutil.GenerateUUID(t), }, svcResp: groups.Group{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: changeStatusRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -324,9 +325,9 @@ func TestDisableGroupEndpoint(t *testing.T) { id: testsutil.GenerateUUID(t), }, svcResp: groups.Group{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: changeStatusRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -376,9 +377,9 @@ func TestDeleteGroupEndpoint(t *testing.T) { token: valid, id: testsutil.GenerateUUID(t), }, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: deleteGroupRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -439,9 +440,9 @@ func TestUpdateGroupEndpoint(t *testing.T) { Name: valid, }, svcResp: groups.Group{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: updateGroupRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -574,9 +575,9 @@ func TestListGroupsEndpoint(t *testing.T) { memberID: testsutil.GenerateUUID(t), }, svcResp: groups.Page{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: groupPageRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -676,9 +677,9 @@ func TestListMembersEndpoint(t *testing.T) { groupID: testsutil.GenerateUUID(t), }, svcResp: groups.MembersPage{}, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: listMembersRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -779,9 +780,9 @@ func TestAssignMembersEndpoint(t *testing.T) { testsutil.GenerateUUID(t), }, }, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: assignRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -890,9 +891,9 @@ func TestUnassignMembersEndpoint(t *testing.T) { testsutil.GenerateUUID(t), }, }, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, resp: unassignRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } diff --git a/internal/groups/service.go b/internal/groups/service.go index 8dd6d08f04..819889f90c 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -13,6 +13,8 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "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/groups" "golang.org/x/sync/errgroup" ) @@ -270,7 +272,7 @@ func (svc service) listUserGroupPermission(ctx context.Context, userID, groupID return []string{}, err } if len(lp.GetPermissions()) == 0 { - return []string{}, errors.ErrAuthorization + return []string{}, svcerr.ErrAuthorization } return lp.GetPermissions(), nil } @@ -287,7 +289,7 @@ func (svc service) checkSuperAdmin(ctx context.Context, userID string) error { return err } if !res.Authorized { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } return nil } @@ -463,7 +465,7 @@ func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID var deletePolicies magistrala.DeletePoliciesReq for _, group := range groupsPage.Groups { if group.Parent != "" { - return errors.Wrap(errors.ErrConflict, fmt.Errorf("%s group already have parent", group.ID)) + return errors.Wrap(repoerr.ErrConflict, fmt.Errorf("%s group already have parent", group.ID)) } addPolicies.AddPoliciesReq = append(addPolicies.AddPoliciesReq, &magistrala.AddPolicyReq{ Domain: domain, @@ -509,7 +511,7 @@ func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupI var deletePolicies magistrala.DeletePoliciesReq for _, group := range groupsPage.Groups { if group.Parent != "" && group.Parent != parentGroupID { - return errors.Wrap(errors.ErrConflict, fmt.Errorf("%s group doesn't have same parent", group.ID)) + return errors.Wrap(repoerr.ErrConflict, fmt.Errorf("%s group doesn't have same parent", group.ID)) } addPolicies.AddPoliciesReq = append(addPolicies.AddPoliciesReq, &magistrala.AddPolicyReq{ Domain: domain, @@ -707,7 +709,7 @@ func (svc service) identify(ctx context.Context, token string) (*magistrala.Iden return nil, err } if res.GetId() == "" || res.GetDomainId() == "" { - return nil, errors.ErrDomainAuthorization + return nil, svcerr.ErrDomainAuthorization } return res, nil } @@ -726,7 +728,7 @@ func (svc service) authorizeToken(ctx context.Context, subjectType, subject, per return "", err } if !res.GetAuthorized() { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return res.GetId(), nil } @@ -746,7 +748,7 @@ func (svc service) authorizeKind(ctx context.Context, domainID, subjectType, sub return "", err } if !res.GetAuthorized() { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return res.GetId(), nil } diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index d28b92ef11..bfc4a17822 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -19,6 +19,8 @@ import ( "github.com/absmach/magistrala/pkg/clients" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" "github.com/absmach/magistrala/pkg/uuid" @@ -97,8 +99,8 @@ func TestCreateGroup(t *testing.T) { kind: auth.NewGroupKind, group: validGroup, idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "with empty id or domain id but with no grpc error", @@ -107,7 +109,7 @@ func TestCreateGroup(t *testing.T) { group: validGroup, idResp: &magistrala.IdentityRes{}, idErr: nil, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "with failed to authorize domain membership", @@ -121,7 +123,7 @@ func TestCreateGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with failed to authorize domain membership with grpc error", @@ -135,8 +137,8 @@ func TestCreateGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with invalid status", @@ -204,7 +206,7 @@ func TestCreateGroup(t *testing.T) { Authorized: true, }, authzTknResp: &magistrala.AuthorizeRes{}, - authzTknErr: errors.ErrAuthorization, + authzTknErr: svcerr.ErrAuthorization, repoResp: mggroups.Group{ ID: testsutil.GenerateUUID(t), Parent: testsutil.GenerateUUID(t), @@ -212,7 +214,7 @@ func TestCreateGroup(t *testing.T) { addPolResp: &magistrala.AddPoliciesRes{ Added: true, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with repo error", @@ -252,8 +254,8 @@ func TestCreateGroup(t *testing.T) { ID: testsutil.GenerateUUID(t), }, addPolResp: &magistrala.AddPoliciesRes{}, - addPolErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + addPolErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -328,8 +330,8 @@ func TestViewGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with failed to authorize", @@ -339,7 +341,7 @@ func TestViewGroup(t *testing.T) { Authorized: false, }, authzErr: nil, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -400,8 +402,8 @@ func TestViewGroupPerms(t *testing.T) { token: token, id: testsutil.GenerateUUID(t), idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "with failed to list permissions", @@ -411,8 +413,8 @@ func TestViewGroupPerms(t *testing.T) { Id: testsutil.GenerateUUID(t), DomainId: testsutil.GenerateUUID(t), }, - listErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with empty permissions", @@ -425,7 +427,7 @@ func TestViewGroupPerms(t *testing.T) { listResp: &magistrala.ListPermissionsRes{ Permissions: []string{}, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -486,8 +488,8 @@ func TestUpdateGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with failed to authorize", @@ -500,7 +502,7 @@ func TestUpdateGroup(t *testing.T) { Authorized: false, }, authzErr: nil, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -562,8 +564,8 @@ func TestEnableGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with failed to authorize", @@ -573,7 +575,7 @@ func TestEnableGroup(t *testing.T) { Authorized: false, }, authzErr: nil, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with enabled group", @@ -595,8 +597,8 @@ func TestEnableGroup(t *testing.T) { Authorized: true, }, retrieveResp: mggroups.Group{}, - retrieveErr: errors.ErrNotFound, - err: errors.ErrNotFound, + retrieveErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -662,8 +664,8 @@ func TestDisableGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with failed to authorize", @@ -673,7 +675,7 @@ func TestDisableGroup(t *testing.T) { Authorized: false, }, authzErr: nil, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "with enabled group", @@ -695,8 +697,8 @@ func TestDisableGroup(t *testing.T) { Authorized: true, }, retrieveResp: mggroups.Group{}, - retrieveErr: errors.ErrNotFound, - err: errors.ErrNotFound, + retrieveErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -795,7 +797,7 @@ func TestListMembers(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "failed to list objects with things kind", @@ -808,8 +810,8 @@ func TestListMembers(t *testing.T) { listObjectResp: &magistrala.ListObjectsRes{ Policies: []string{}, }, - listObjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "failed to list subjects with users kind", @@ -823,8 +825,8 @@ func TestListMembers(t *testing.T) { listSubjectResp: &magistrala.ListSubjectsRes{ Policies: []string{}, }, - listSubjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listSubjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -1087,8 +1089,8 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with users kind admin with nil error", @@ -1106,7 +1108,7 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with things kind due to failed to authorize", @@ -1124,8 +1126,8 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with things kind due to failed to list subjects", @@ -1144,8 +1146,8 @@ func TestListGroups(t *testing.T) { Authorized: true, }, listSubjectResp: &magistrala.ListSubjectsRes{}, - listSubjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listSubjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with things kind due to failed to list filtered objects", @@ -1167,8 +1169,8 @@ func TestListGroups(t *testing.T) { Policies: allowedIDs, }, listObjectFilterResp: &magistrala.ListObjectsRes{}, - listObjectFilterErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectFilterErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with groups kind due to failed to authorize", @@ -1186,8 +1188,8 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with groups kind due to failed to list subjects", @@ -1206,8 +1208,8 @@ func TestListGroups(t *testing.T) { Authorized: true, }, listObjectResp: &magistrala.ListObjectsRes{}, - listObjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with groups kind due to failed to list filtered objects", @@ -1229,8 +1231,8 @@ func TestListGroups(t *testing.T) { Policies: allowedIDs, }, listObjectFilterResp: &magistrala.ListObjectsRes{}, - listObjectFilterErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectFilterErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with channels kind due to failed to authorize", @@ -1248,8 +1250,8 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with channels kind due to failed to list subjects", @@ -1268,8 +1270,8 @@ func TestListGroups(t *testing.T) { Authorized: true, }, listSubjectResp: &magistrala.ListSubjectsRes{}, - listSubjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listSubjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with channels kind due to failed to list filtered objects", @@ -1291,8 +1293,8 @@ func TestListGroups(t *testing.T) { Policies: allowedIDs, }, listObjectFilterResp: &magistrala.ListObjectsRes{}, - listObjectFilterErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectFilterErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with users kind due to failed to authorize", @@ -1310,8 +1312,8 @@ func TestListGroups(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with users kind due to failed to list subjects", @@ -1330,8 +1332,8 @@ func TestListGroups(t *testing.T) { Authorized: true, }, listObjectResp: &magistrala.ListObjectsRes{}, - listObjectErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with users kind due to failed to list filtered objects", @@ -1353,8 +1355,8 @@ func TestListGroups(t *testing.T) { Policies: allowedIDs, }, listObjectFilterResp: &magistrala.ListObjectsRes{}, - listObjectFilterErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listObjectFilterErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "successfully with users kind admin", @@ -1430,8 +1432,8 @@ func TestListGroups(t *testing.T) { Policies: allowedIDs, }, repoResp: mggroups.Page{}, - repoErr: errors.ErrViewEntity, - err: errors.ErrViewEntity, + repoErr: repoerr.ErrViewEntity, + err: repoerr.ErrViewEntity, }, { desc: "unsuccessfully with things kind due to failed to list permissions", @@ -1463,8 +1465,8 @@ func TestListGroups(t *testing.T) { }, }, listPermResp: &magistrala.ListPermissionsRes{}, - listPermErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + listPermErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with invalid token", @@ -1476,8 +1478,8 @@ func TestListGroups(t *testing.T) { ListPerms: true, }, idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } @@ -1736,8 +1738,8 @@ func TestAssign(t *testing.T) { Authorized: true, }, repoResp: mggroups.Page{}, - repoErr: errors.ErrViewEntity, - err: errors.ErrViewEntity, + repoErr: repoerr.ErrViewEntity, + err: repoerr.ErrViewEntity, }, { desc: "unsuccessfully with groups kind due to empty page", @@ -1780,7 +1782,7 @@ func TestAssign(t *testing.T) { }, }, }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "unsuccessfully with groups kind due to failed to add policies", @@ -1806,8 +1808,8 @@ func TestAssign(t *testing.T) { addPoliciesRes: &magistrala.AddPoliciesRes{ Added: false, }, - addPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + addPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with groups kind due to failed to assign parent", @@ -1833,8 +1835,8 @@ func TestAssign(t *testing.T) { addPoliciesRes: &magistrala.AddPoliciesRes{ Added: true, }, - repoParentGroupErr: errors.ErrConflict, - err: errors.ErrConflict, + repoParentGroupErr: repoerr.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "unsuccessfully with groups kind due to failed to assign parent and delete policy", @@ -1863,8 +1865,8 @@ func TestAssign(t *testing.T) { deleteParentPoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: false, }, - deleteParentPoliciesErr: errors.ErrAuthorization, - repoParentGroupErr: errors.ErrConflict, + deleteParentPoliciesErr: svcerr.ErrAuthorization, + repoParentGroupErr: repoerr.ErrConflict, err: apiutil.ErrRollbackTx, }, { @@ -1891,8 +1893,8 @@ func TestAssign(t *testing.T) { memberKind: auth.UsersKind, memberIDs: allowedIDs, idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "unsuccessfully with failed to authorize", @@ -1908,8 +1910,8 @@ func TestAssign(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to add policies", @@ -1928,8 +1930,8 @@ func TestAssign(t *testing.T) { addPoliciesRes: &magistrala.AddPoliciesRes{ Added: false, }, - addPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + addPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -2145,8 +2147,8 @@ func TestUnassign(t *testing.T) { Authorized: true, }, repoResp: mggroups.Page{}, - repoErr: errors.ErrViewEntity, - err: errors.ErrViewEntity, + repoErr: repoerr.ErrViewEntity, + err: repoerr.ErrViewEntity, }, { desc: "unsuccessfully with groups kind due to empty page", @@ -2189,7 +2191,7 @@ func TestUnassign(t *testing.T) { }, }, }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "unsuccessfully with groups kind due to failed to add policies", @@ -2215,8 +2217,8 @@ func TestUnassign(t *testing.T) { deletePoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: false, }, - deletePoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deletePoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with groups kind due to failed to unassign parent", @@ -2242,8 +2244,8 @@ func TestUnassign(t *testing.T) { deletePoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: true, }, - repoParentGroupErr: errors.ErrConflict, - err: errors.ErrConflict, + repoParentGroupErr: repoerr.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "unsuccessfully with groups kind due to failed to unassign parent and add policy", @@ -2269,12 +2271,12 @@ func TestUnassign(t *testing.T) { deletePoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: true, }, - repoParentGroupErr: errors.ErrConflict, + repoParentGroupErr: repoerr.ErrConflict, addParentPoliciesRes: &magistrala.AddPoliciesRes{ Added: false, }, - addParentPoliciesErr: errors.ErrAuthorization, - err: errors.ErrConflict, + addParentPoliciesErr: svcerr.ErrAuthorization, + err: repoerr.ErrConflict, }, { desc: "unsuccessfully with invalid kind", @@ -2300,8 +2302,8 @@ func TestUnassign(t *testing.T) { memberKind: auth.UsersKind, memberIDs: allowedIDs, idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "unsuccessfully with failed to authorize", @@ -2317,8 +2319,8 @@ func TestUnassign(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to add policies", @@ -2337,8 +2339,8 @@ func TestUnassign(t *testing.T) { deletePoliciesRes: &magistrala.DeletePoliciesRes{ Deleted: false, }, - deletePoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deletePoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -2485,8 +2487,8 @@ func TestDeleteGroup(t *testing.T) { token: token, groupID: testsutil.GenerateUUID(t), idResp: &magistrala.IdentityRes{}, - idErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + idErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "unsuccessfully with authorization error", @@ -2499,8 +2501,8 @@ func TestDeleteGroup(t *testing.T) { authzResp: &magistrala.AuthorizeRes{ Authorized: false, }, - authzErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to remove child group policy", @@ -2516,8 +2518,8 @@ func TestDeleteGroup(t *testing.T) { deleteChildPoliciesRes: &magistrala.DeletePolicyRes{ Deleted: false, }, - deleteChildPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deleteChildPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to remove things policy", @@ -2536,8 +2538,8 @@ func TestDeleteGroup(t *testing.T) { deleteThingsPoliciesRes: &magistrala.DeletePolicyRes{ Deleted: false, }, - deleteThingsPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deleteThingsPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to remove domain policy", @@ -2559,8 +2561,8 @@ func TestDeleteGroup(t *testing.T) { deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ Deleted: false, }, - deleteDomainsPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deleteDomainsPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with failed to remove user policy", @@ -2585,8 +2587,8 @@ func TestDeleteGroup(t *testing.T) { deleteUsersPoliciesRes: &magistrala.DeletePolicyRes{ Deleted: false, }, - deleteUsersPoliciesErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + deleteUsersPoliciesErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "unsuccessfully with repo err", @@ -2608,8 +2610,8 @@ func TestDeleteGroup(t *testing.T) { deleteDomainsPoliciesRes: &magistrala.DeletePolicyRes{ Deleted: true, }, - repoErr: errors.ErrNotFound, - err: errors.ErrNotFound, + repoErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go index fbf0cd4d07..dc39fb9fac 100644 --- a/invitations/api/endpoint_test.go +++ b/invitations/api/endpoint_test.go @@ -17,7 +17,7 @@ import ( "github.com/absmach/magistrala/invitations/api" "github.com/absmach/magistrala/invitations/mocks" mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -111,7 +111,7 @@ func TestSendInvitation(t *testing.T) { data: fmt.Sprintf(`{"user_id": "%s", "domain_id": "%s", "relation": "%s"}`, validID, validID, "domain"), status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, } @@ -283,7 +283,7 @@ func TestListInvitation(t *testing.T) { token: validToken, status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, } @@ -341,7 +341,7 @@ func TestViewInvitation(t *testing.T) { domainID: validID, status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, { desc: "with empty user_id", @@ -426,7 +426,7 @@ func TestDeleteInvitation(t *testing.T) { domainID: validID, status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, { desc: "with empty user_id", @@ -507,7 +507,7 @@ func TestAcceptInvitation(t *testing.T) { data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, { desc: "invalid content type", diff --git a/invitations/mocks/invitations.go b/invitations/mocks/invitations.go index 2c7f1fa22f..057e1b2910 100644 --- a/invitations/mocks/invitations.go +++ b/invitations/mocks/invitations.go @@ -7,7 +7,7 @@ import ( "context" "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/stretchr/testify/mock" ) @@ -23,7 +23,7 @@ func (m *Repository) Create(ctx context.Context, invitation invitations.Invitati ret := m.Called(ctx, invitation) if invitation.UserID == Invalid || invitation.DomainID == Invalid || invitation.InvitedBy == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) @@ -33,7 +33,7 @@ func (m *Repository) Retrieve(ctx context.Context, userID, domainID string) (inv ret := m.Called(ctx, userID, domainID) if userID == Invalid || domainID == Invalid { - return invitations.Invitation{}, errors.ErrNotFound + return invitations.Invitation{}, repoerr.ErrNotFound } return ret.Get(0).(invitations.Invitation), ret.Error(1) @@ -43,7 +43,7 @@ func (m *Repository) RetrieveAll(ctx context.Context, page invitations.Page) (in ret := m.Called(ctx, page) if page.UserID == Invalid || page.DomainID == Invalid { - return invitations.InvitationPage{}, errors.ErrNotFound + return invitations.InvitationPage{}, repoerr.ErrNotFound } return ret.Get(0).(invitations.InvitationPage), ret.Error(1) @@ -53,7 +53,7 @@ func (m *Repository) UpdateToken(ctx context.Context, invitation invitations.Inv ret := m.Called(ctx, invitation) if invitation.UserID == Invalid || invitation.DomainID == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) @@ -63,7 +63,7 @@ func (m *Repository) UpdateConfirmation(ctx context.Context, invitation invitati ret := m.Called(ctx, invitation) if invitation.UserID == Invalid || invitation.DomainID == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) @@ -73,7 +73,7 @@ func (m *Repository) Delete(ctx context.Context, userID, domainID string) error ret := m.Called(ctx, userID, domainID) if userID == Invalid || domainID == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) diff --git a/invitations/mocks/service.go b/invitations/mocks/service.go index e16813d834..6e65ace3c1 100644 --- a/invitations/mocks/service.go +++ b/invitations/mocks/service.go @@ -7,7 +7,8 @@ import ( "context" "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/mock" ) @@ -21,7 +22,7 @@ func (svc *Service) SendInvitation(ctx context.Context, token string, invitation ret := svc.Called(ctx, token, invitation) if token == Invalid || invitation.UserID == Invalid || invitation.DomainID == Invalid || invitation.InvitedBy == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) @@ -31,7 +32,7 @@ func (svc *Service) ViewInvitation(ctx context.Context, token, userID, domainID ret := svc.Called(ctx, token, userID, domainID) if token == Invalid || userID == Invalid || domainID == Invalid { - return invitations.Invitation{}, errors.ErrNotFound + return invitations.Invitation{}, repoerr.ErrNotFound } return ret.Get(0).(invitations.Invitation), ret.Error(1) @@ -41,7 +42,7 @@ func (svc *Service) ListInvitations(ctx context.Context, token string, page invi ret := svc.Called(ctx, token, page) if token == Invalid { - return invitations.InvitationPage{}, errors.ErrAuthentication + return invitations.InvitationPage{}, svcerr.ErrAuthentication } return ret.Get(0).(invitations.InvitationPage), ret.Error(1) @@ -51,7 +52,7 @@ func (svc *Service) AcceptInvitation(ctx context.Context, token, domainID string ret := svc.Called(ctx, token, domainID) if token == Invalid { - return errors.ErrAuthentication + return svcerr.ErrAuthentication } return ret.Error(0) @@ -61,11 +62,11 @@ func (svc *Service) DeleteInvitation(ctx context.Context, token, userID, domainI ret := svc.Called(ctx, token, userID, domainID) if token == Invalid { - return errors.ErrAuthentication + return svcerr.ErrAuthentication } if userID == Invalid || domainID == Invalid { - return errors.ErrNotFound + return repoerr.ErrNotFound } return ret.Error(0) diff --git a/mqtt/handler.go b/mqtt/handler.go index ad09d7e7fc..b9b89a784c 100644 --- a/mqtt/handler.go +++ b/mqtt/handler.go @@ -16,6 +16,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/mqtt/events" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/mproxy/pkg/session" ) @@ -235,7 +236,7 @@ func (h *handler) authAccess(ctx context.Context, password, topic, action string return err } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } return nil diff --git a/mqtt/handler_test.go b/mqtt/handler_test.go index c46219cb01..0dd52808e2 100644 --- a/mqtt/handler_test.go +++ b/mqtt/handler_test.go @@ -17,6 +17,7 @@ import ( "github.com/absmach/magistrala/mqtt" "github.com/absmach/magistrala/mqtt/mocks" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/mproxy/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -197,7 +198,7 @@ func TestAuthSubscribe(t *testing.T) { { desc: "subscribe with invalid channel ID", session: &sessionClient, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, topic: &invalidChanIDTopics, }, { diff --git a/pkg/clients/postgres/clients_test.go b/pkg/clients/postgres/clients_test.go index 178e628ac8..c87ee944a8 100644 --- a/pkg/clients/postgres/clients_test.go +++ b/pkg/clients/postgres/clients_test.go @@ -1353,7 +1353,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name for enabled client", @@ -1371,7 +1371,7 @@ func TestUpdate(t *testing.T) { ID: client2.ID, Name: namegen.Generate(), }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name and metadata for enabled client", @@ -1395,7 +1395,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update metadata for invalid client", @@ -1406,7 +1406,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name for invalid client", @@ -1415,7 +1415,7 @@ func TestUpdate(t *testing.T) { ID: testsutil.GenerateUUID(t), Name: namegen.Generate(), }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name and metadata for invalid client", @@ -1427,7 +1427,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update metadata for empty client", @@ -1437,7 +1437,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name for empty client", @@ -1445,7 +1445,7 @@ func TestUpdate(t *testing.T) { client: mgclients.Client{ Name: namegen.Generate(), }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "update name and metadata for empty client", @@ -1456,7 +1456,7 @@ func TestUpdate(t *testing.T) { "update": namegen.Generate(), }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, c := range cases { @@ -1511,7 +1511,7 @@ func TestUpdateTags(t *testing.T) { ID: client2.ID, Tags: namegen.GenerateNames(5), }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for invalid client", @@ -1519,12 +1519,12 @@ func TestUpdateTags(t *testing.T) { ID: testsutil.GenerateUUID(t), Tags: namegen.GenerateNames(5), }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for empty client", client: mgclients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, c := range cases { @@ -1575,7 +1575,7 @@ func TestUpdateSecret(t *testing.T) { Secret: "newpassword", }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for invalid client", @@ -1585,12 +1585,12 @@ func TestUpdateSecret(t *testing.T) { Secret: "newpassword", }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for empty client", client: mgclients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, c := range cases { @@ -1643,7 +1643,7 @@ func TestUpdateIdentity(t *testing.T) { Identity: namegen.Generate() + emailSuffix, }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for invalid client", @@ -1653,12 +1653,12 @@ func TestUpdateIdentity(t *testing.T) { Identity: namegen.Generate() + emailSuffix, }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for empty client", client: mgclients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } for _, c := range cases { @@ -1713,12 +1713,12 @@ func TestChangeStatus(t *testing.T) { ID: testsutil.GenerateUUID(t), Status: mgclients.DisabledStatus, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for empty client", client: mgclients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -1766,7 +1766,7 @@ func TestUpdateRole(t *testing.T) { ID: client2.ID, Role: mgclients.AdminRole, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for invalid client", @@ -1774,12 +1774,12 @@ func TestUpdateRole(t *testing.T) { ID: testsutil.GenerateUUID(t), Role: mgclients.AdminRole, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "for empty client", client: mgclients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index a0a3049f2c..771fe2159a 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -8,11 +8,17 @@ import "github.com/absmach/magistrala/pkg/errors" // Wrapper for Service errors. var ( // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = errors.New("authentication error") + ErrAuthentication = errors.New("failed to perform authentication over the entity") // ErrAuthorization indicates failure occurred while authorizing the entity. ErrAuthorization = errors.New("failed to perform authorization over the entity") + // ErrDomainAuthorization indicates failure occurred while authorizing the domain. + ErrDomainAuthorization = errors.New("failed to perform authorization over the domain") + + // ErrLogin indicates wrong login credentials. + ErrLogin = errors.New("invalid user id or secret") + // ErrMalformedEntity indicates a malformed entity specification. ErrMalformedEntity = errors.New("malformed entity specification") @@ -34,9 +40,6 @@ var ( // ErrUpdateEntity indicates error in updating entity or entities. ErrUpdateEntity = errors.New("update entity failed") - // ErrUniqueID indicates an error in generating a unique ID. - ErrUniqueID = errors.New("failed to generate unique identifier") - // ErrInvalidStatus indicates an invalid status. ErrInvalidStatus = errors.New("invalid status") diff --git a/pkg/errors/types.go b/pkg/errors/types.go index 684c4dec6c..b75a43e313 100644 --- a/pkg/errors/types.go +++ b/pkg/errors/types.go @@ -6,45 +6,9 @@ package errors import "errors" var ( - // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = New("failed to perform authentication over the entity") - - // ErrAuthorization indicates failure occurred while authorizing the entity. - ErrAuthorization = New("failed to perform authorization over the entity") - - // ErrDomainAuthorization indicates failure occurred while authorizing the domain. - ErrDomainAuthorization = New("failed to perform authorization over the domain") - // ErrMalformedEntity indicates a malformed entity specification. ErrMalformedEntity = New("malformed entity specification") - // ErrNotFound indicates a non-existent entity request. - ErrNotFound = New("entity not found") - - // ErrConflict indicates that entity already exists. - ErrConflict = New("entity already exists") - - // ErrCreateEntity indicates error in creating entity or entities. - ErrCreateEntity = New("failed to create entity in the db") - - // ErrViewEntity indicates error in viewing entity or entities. - ErrViewEntity = New("view entity failed") - - // ErrUpdateEntity indicates error in updating entity or entities. - ErrUpdateEntity = New("update entity failed") - - // ErrRemoveEntity indicates error in removing entity. - ErrRemoveEntity = New("failed to remove entity") - - // ErrScanMetadata indicates problem with metadata in db. - ErrScanMetadata = New("failed to scan metadata in db") - - // ErrWrongSecret indicates a wrong secret was provided. - ErrWrongSecret = New("wrong secret") - - // ErrLogin indicates wrong login credentials. - ErrLogin = New("invalid user id or secret") - // ErrUnsupportedContentType indicates invalid content type. ErrUnsupportedContentType = errors.New("invalid content type") diff --git a/pkg/sdk/go/certs_test.go b/pkg/sdk/go/certs_test.go index 742f8a0ba0..35ca5bcf41 100644 --- a/pkg/sdk/go/certs_test.go +++ b/pkg/sdk/go/certs_test.go @@ -19,6 +19,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" sdk "github.com/absmach/magistrala/pkg/sdk/go" thmocks "github.com/absmach/magistrala/things/mocks" @@ -261,7 +262,7 @@ func TestViewCertByThing(t *testing.T) { desc: "get non-existent cert", thingID: "43", token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, errors.ErrNotFound), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, repoerr.ErrNotFound), http.StatusInternalServerError), response: sdk.Subscription{}, }, { diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index a64749028a..8df439d6b8 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -20,6 +20,7 @@ import ( mglog "github.com/absmach/magistrala/logger" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" @@ -109,7 +110,7 @@ func TestCreateChannel(t *testing.T) { Status: mgclients.EnabledStatus.String(), }, token: token, - err: errors.NewSDKErrorWithStatus(errors.ErrCreateEntity, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(repoerr.ErrCreateEntity, http.StatusInternalServerError), }, { desc: "create channel with missing name", @@ -655,7 +656,7 @@ func TestEnableChannel(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) + repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableChannel("wrongID", validToken) assert.Equal(t, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), err, fmt.Sprintf("Enable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) @@ -708,7 +709,7 @@ func TestDisableChannel(t *testing.T) { repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) - repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) + repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) _, err := mgsdk.DisableChannel("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index ca6f49a255..6bd6f59e8e 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -18,6 +18,7 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" @@ -789,7 +790,7 @@ func TestEnableGroup(t *testing.T) { } repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) - repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) + repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableGroup("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Enable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) @@ -843,7 +844,7 @@ func TestDisableGroup(t *testing.T) { repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) - repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, errors.ErrNotFound) + repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) _, err := mgsdk.DisableGroup("wrongID", validToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go index 474c04e5c6..e670c76869 100644 --- a/pkg/sdk/go/message_test.go +++ b/pkg/sdk/go/message_test.go @@ -57,7 +57,7 @@ func TestSendMessage(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) auth.On("Authorize", mock.Anything, &magistrala.AuthorizeReq{Subject: atoken, Object: chanID, Domain: "", SubjectType: "thing", Permission: "publish", ObjectType: "group"}).Return(&magistrala.AuthorizeRes{Authorized: true, Id: ""}, nil) - auth.On("Authorize", mock.Anything, &magistrala.AuthorizeReq{Subject: invalidToken, Object: chanID, Domain: "", SubjectType: "thing", Permission: "publish", ObjectType: "group"}).Return(&magistrala.AuthorizeRes{Authorized: true, Id: ""}, errors.ErrAuthentication) + auth.On("Authorize", mock.Anything, &magistrala.AuthorizeReq{Subject: invalidToken, Object: chanID, Domain: "", SubjectType: "thing", Permission: "publish", ObjectType: "group"}).Return(&magistrala.AuthorizeRes{Authorized: true, Id: ""}, svcerr.ErrAuthentication) auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false, Id: ""}, nil) cases := map[string]struct { @@ -76,13 +76,13 @@ func TestSendMessage(t *testing.T) { chanID: chanID, msg: msg, auth: "", - err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), }, "publish message with invalid authorization token": { chanID: chanID, msg: msg, auth: invalidToken, - err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusBadRequest), }, "publish message with wrong content type": { chanID: chanID, diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 98defd07ea..40fd38d0ee 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -361,7 +361,7 @@ func TestListThings(t *testing.T) { token: authmocks.InvalidValue, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, errors.ErrAuthentication), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -369,7 +369,7 @@ func TestListThings(t *testing.T) { token: "", offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, errors.ErrAuthentication), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -464,9 +464,9 @@ func TestListThings(t *testing.T) { repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response)}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) - repoCall2 = auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{}, errors.ErrAuthorization) + repoCall2 = auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{}, svcerr.ErrAuthorization) } repoCall3 := cRepo.On("RetrieveAllByIDs", mock.Anything, mock.Anything).Return(mgclients.ClientsPage{Page: convertClientPage(pm), Clients: convertThings(tc.response...)}, tc.err) page, err := mgsdk.Things(pm, validToken) @@ -573,7 +573,7 @@ func TestListThingsByChannel(t *testing.T) { channelID: testsutil.GenerateUUID(t), page: sdk.PageMetadata{}, response: []sdk.Thing(nil), - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), }, { desc: "list things with an invalid id", @@ -646,7 +646,7 @@ func TestThing(t *testing.T) { response: sdk.Thing{}, token: validToken, thingID: wrongID, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrNotFound, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), }, { desc: "view thing with an invalid token and invalid thing id", @@ -660,7 +660,7 @@ func TestThing(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.thingID).Return(convertThing(tc.response), tc.err) rClient, err := mgsdk.Thing(tc.thingID, tc.token) @@ -745,7 +745,7 @@ func TestUpdateThing(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := cRepo.On("Update", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) uClient, err := mgsdk.UpdateThing(tc.thing, tc.token) @@ -830,7 +830,7 @@ func TestUpdateThingTags(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := cRepo.On("UpdateTags", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) uClient, err := mgsdk.UpdateThingTags(tc.thing, tc.token) @@ -883,8 +883,8 @@ func TestUpdateThingSecret(t *testing.T) { newSecret: "newPassword", token: "non-existent", response: sdk.Thing{}, - repoErr: errors.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, errors.ErrAuthorization), http.StatusForbidden), + repoErr: svcerr.ErrAuthorization, + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update thing secret with wrong old secret", @@ -900,7 +900,7 @@ func TestUpdateThingSecret(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := cRepo.On("UpdateSecret", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.repoErr) uClient, err := mgsdk.UpdateThingSecret(tc.oldSecret, tc.newSecret, tc.token) @@ -965,7 +965,7 @@ func TestEnableThing(t *testing.T) { thing: sdk.Thing{}, response: sdk.Thing{}, repoErr: sdk.ErrFailedEnable, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedEnable, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedEnable, repoerr.ErrNotFound), http.StatusNotFound), }, } @@ -973,7 +973,7 @@ func TestEnableThing(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertThing(tc.thing), tc.repoErr) repoCall3 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.repoErr) @@ -1036,7 +1036,7 @@ func TestEnableThing(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response.Things)}, nil) repoCall3 := cRepo.On("RetrieveAllByIDs", mock.Anything, mock.Anything).Return(convertThingsPage(tc.response), nil) @@ -1100,7 +1100,7 @@ func TestDisableThing(t *testing.T) { token: validToken, response: sdk.Thing{}, repoErr: sdk.ErrFailedDisable, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedDisable, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedDisable, repoerr.ErrNotFound), http.StatusNotFound), }, } @@ -1108,7 +1108,7 @@ func TestDisableThing(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertThing(tc.thing), tc.repoErr) repoCall3 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.repoErr) @@ -1173,7 +1173,7 @@ func TestDisableThing(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall2 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&magistrala.ListObjectsRes{Policies: toIDs(tc.response.Things)}, nil) repoCall3 := cRepo.On("RetrieveAllByIDs", mock.Anything, mock.Anything).Return(convertThingsPage(tc.response), nil) @@ -1218,15 +1218,15 @@ func TestShareThing(t *testing.T) { channelID: generateUUID(t), thingID: "thingID", token: invalidToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), }, { desc: "share thing with valid token for unauthorized user", channelID: generateUUID(t), thingID: "thingID", token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthorization), http.StatusForbidden), - repoErr: errors.ErrAuthorization, + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), + repoErr: svcerr.ErrAuthorization, }, } @@ -1235,7 +1235,7 @@ func TestShareThing(t *testing.T) { repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.repoErr) repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) if tc.token != validToken { - repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, errors.ErrAuthorization) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, svcerr.ErrAuthorization) } repoCall3 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Added: true}, nil) req := sdk.UsersRelationRequest{ diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 39d6aeacfd..2483fb3fad 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -182,7 +182,7 @@ func TestCreateClient(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Added: true}, nil) repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(&magistrala.DeletePoliciesRes{Deleted: true}, nil) @@ -261,7 +261,7 @@ func TestListClients(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrNotFound, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), response: nil, }, { @@ -269,7 +269,7 @@ func TestListClients(t *testing.T) { token: "", offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrNotFound, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), response: nil, }, { @@ -277,7 +277,7 @@ func TestListClients(t *testing.T) { token: token, offset: offset, limit: 0, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrNotFound, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), response: nil, }, { @@ -430,7 +430,7 @@ func TestClient(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := crepo.On("RetrieveByID", mock.Anything, tc.clientID).Return(convertClient(tc.response), tc.err) @@ -489,7 +489,7 @@ func TestProfile(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCal1 := crepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) rClient, err := mgsdk.UserProfile(tc.token) @@ -578,7 +578,7 @@ func TestUpdateClient(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := crepo.On("Update", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) @@ -670,7 +670,7 @@ func TestUpdateClientTags(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := crepo.On("UpdateTags", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) @@ -760,7 +760,7 @@ func TestUpdateClientIdentity(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := crepo.On("UpdateIdentity", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) @@ -816,8 +816,8 @@ func TestUpdateClientSecret(t *testing.T) { newSecret: "newPassword", token: "non-existent", response: sdk.User{}, - repoErr: errors.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, errors.ErrAuthentication), http.StatusUnauthorized), + repoErr: svcerr.ErrAuthentication, + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), }, { desc: "update client secret with wrong old secret", @@ -833,7 +833,7 @@ func TestUpdateClientSecret(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: user.ID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(&magistrala.Token{AccessToken: validToken}, nil) repoCall2 := crepo.On("RetrieveByID", mock.Anything, user.ID).Return(convertClient(tc.response), tc.repoErr) @@ -927,7 +927,7 @@ func TestUpdateClientRole(t *testing.T) { for _, tc := range cases { repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil) if tc.token != validToken { - repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, errors.ErrAuthentication) + repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication) } repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) @@ -999,7 +999,7 @@ func TestEnableClient(t *testing.T) { client: sdk.User{}, response: sdk.User{}, repoErr: sdk.ErrFailedEnable, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedEnable, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedEnable, repoerr.ErrNotFound), http.StatusNotFound), }, } @@ -1128,7 +1128,7 @@ func TestDisableClient(t *testing.T) { token: validToken, response: sdk.User{}, repoErr: sdk.ErrFailedDisable, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedDisable, errors.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedDisable, repoerr.ErrNotFound), http.StatusNotFound), }, } diff --git a/provision/api/endpoint_test.go b/provision/api/endpoint_test.go index be9cbd1b76..5fd21a5f51 100644 --- a/provision/api/endpoint_test.go +++ b/provision/api/endpoint_test.go @@ -14,7 +14,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/provision" "github.com/absmach/magistrala/provision/api" "github.com/absmach/magistrala/provision/mocks" @@ -126,7 +126,7 @@ func TestProvision(t *testing.T) { data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID), status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, } @@ -186,7 +186,7 @@ func TestMapping(t *testing.T) { token: validToken, status: http.StatusForbidden, contentType: validContenType, - svcErr: errors.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, }, } diff --git a/provision/service_test.go b/provision/service_test.go index 1d2913d446..bc6b5c6db8 100644 --- a/provision/service_test.go +++ b/provision/service_test.go @@ -10,6 +10,8 @@ import ( "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" sdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" "github.com/absmach/magistrala/provision" @@ -41,7 +43,7 @@ func TestMapping(t *testing.T) { desc: "invalid token", token: "invalid", content: map[string]interface{}{}, - sdkerr: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, 401), + sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), err: provision.ErrUnauthorized, }, } @@ -138,7 +140,7 @@ func TestCert(t *testing.T) { key: "", sdkThingErr: nil, sdkCertErr: nil, - sdkTokenErr: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, 401), + sdkTokenErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), err: provision.ErrFailedToCreateToken, }, { @@ -165,7 +167,7 @@ func TestCert(t *testing.T) { ttl: "1h", cert: "", key: "", - sdkThingErr: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, 401), + sdkThingErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), sdkCertErr: nil, sdkTokenErr: nil, err: provision.ErrUnauthorized, @@ -178,7 +180,7 @@ func TestCert(t *testing.T) { ttl: "1h", cert: "", key: "", - sdkThingErr: errors.NewSDKErrorWithStatus(errors.ErrNotFound, 404), + sdkThingErr: errors.NewSDKErrorWithStatus(repoerr.ErrNotFound, 404), sdkCertErr: nil, sdkTokenErr: nil, err: provision.ErrUnauthorized, @@ -193,8 +195,8 @@ func TestCert(t *testing.T) { key: "", sdkThingErr: nil, sdkTokenErr: nil, - sdkCertErr: errors.NewSDKError(errors.ErrCreateEntity), - err: errors.ErrCreateEntity, + sdkCertErr: errors.NewSDKError(repoerr.ErrCreateEntity), + err: repoerr.ErrCreateEntity, }, } diff --git a/readers/api/endpoint.go b/readers/api/endpoint.go index beeb3604a0..bf225e8a71 100644 --- a/readers/api/endpoint.go +++ b/readers/api/endpoint.go @@ -9,6 +9,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/readers" "github.com/go-kit/kit/endpoint" ) @@ -21,7 +22,7 @@ func listMessagesEndpoint(svc readers.MessageRepository, uauth magistrala.AuthSe } if err := authorize(ctx, req, uauth, taauth); err != nil { - return nil, errors.Wrap(errors.ErrAuthorization, err) + return nil, errors.Wrap(svcerr.ErrAuthorization, err) } page, err := svc.ReadAll(req.chanID, req.pageMeta) diff --git a/things/api/grpc/client.go b/things/api/grpc/client.go index d0f2b6f309..db1c31abb7 100644 --- a/things/api/grpc/client.go +++ b/things/api/grpc/client.go @@ -10,6 +10,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" @@ -78,17 +79,17 @@ func decodeError(err error) error { if st, ok := status.FromError(err); ok { switch st.Code() { case codes.Unauthenticated: - return errors.Wrap(errors.ErrAuthentication, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) case codes.PermissionDenied: - return errors.Wrap(errors.ErrAuthorization, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) case codes.InvalidArgument: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.FailedPrecondition: return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) case codes.NotFound: - return errors.Wrap(errors.ErrNotFound, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) case codes.AlreadyExists: - return errors.Wrap(errors.ErrConflict, errors.New(st.Message())) + return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) case codes.OK: if msg := st.Message(); msg != "" { return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) diff --git a/things/api/grpc/endpoint_test.go b/things/api/grpc/endpoint_test.go index fb979b19c9..bd123e5d2f 100644 --- a/things/api/grpc/endpoint_test.go +++ b/things/api/grpc/endpoint_test.go @@ -14,6 +14,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" grpcapi "github.com/absmach/magistrala/things/api/grpc" "github.com/absmach/magistrala/things/mocks" "github.com/stretchr/testify/assert" @@ -87,7 +88,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "authorize with missing ID", @@ -113,7 +114,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "authorize with invalid permission", @@ -126,7 +127,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "authorize with invalid channel ID", @@ -139,7 +140,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "authorize with empty channel ID", @@ -152,7 +153,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "authorize with empty permission", @@ -165,7 +166,7 @@ func TestAuthorize(t *testing.T) { ObjectType: auth.GroupType, }, res: &magistrala.AuthorizeRes{}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } diff --git a/things/api/grpc/server.go b/things/api/grpc/server.go index 48376d816b..4c79a900a4 100644 --- a/things/api/grpc/server.go +++ b/things/api/grpc/server.go @@ -65,13 +65,12 @@ func encodeError(err error) error { err == apiutil.ErrMissingPolicyObj, err == apiutil.ErrMalformedPolicyAct: return status.Error(codes.InvalidArgument, err.Error()) - case errors.Contains(err, errors.ErrAuthentication), + case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, auth.ErrKeyExpired), err == apiutil.ErrMissingEmail, err == apiutil.ErrBearerToken: return status.Error(codes.Unauthenticated, err.Error()) - case errors.Contains(err, errors.ErrAuthorization), - errors.Contains(err, svcerr.ErrAuthorization): + case errors.Contains(err, svcerr.ErrAuthorization): return status.Error(codes.PermissionDenied, err.Error()) default: return status.Error(codes.Internal, err.Error()) diff --git a/things/api/http/endpoints_test.go b/things/api/http/endpoints_test.go index cbe12647cc..677725bd0a 100644 --- a/things/api/http/endpoints_test.go +++ b/things/api/http/endpoints_test.go @@ -129,7 +129,7 @@ func TestCreateThing(t *testing.T) { token: validToken, contentType: contentType, status: http.StatusConflict, - err: errors.ErrConflict, + err: svcerr.ErrConflict, }, { desc: "register a new thing with an empty token", diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 9f481e9497..06717122d6 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -8,6 +8,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" ) type createClientReq struct { @@ -310,7 +311,7 @@ type connectChannelThingRequest struct { func (req *connectChannelThingRequest) validate() error { if req.ThingID == "" || req.ChannelID == "" { - return errors.ErrCreateEntity + return svcerr.ErrCreateEntity } return nil } @@ -323,7 +324,7 @@ type disconnectChannelThingRequest struct { func (req *disconnectChannelThingRequest) validate() error { if req.ThingID == "" || req.ChannelID == "" { - return errors.ErrCreateEntity + return svcerr.ErrCreateEntity } return nil } @@ -340,7 +341,7 @@ func (req *thingShareRequest) validate() error { return errors.ErrMalformedEntity } if req.Relation == "" || len(req.UserIDs) == 0 { - return errors.ErrCreateEntity + return svcerr.ErrCreateEntity } return nil } @@ -357,7 +358,7 @@ func (req *thingUnshareRequest) validate() error { return errors.ErrMalformedEntity } if req.Relation == "" || len(req.UserIDs) == 0 { - return errors.ErrCreateEntity + return svcerr.ErrCreateEntity } return nil } diff --git a/things/api/http/requests_test.go b/things/api/http/requests_test.go index 1ff0c2ed54..783e9070ee 100644 --- a/things/api/http/requests_test.go +++ b/things/api/http/requests_test.go @@ -12,6 +12,7 @@ import ( "github.com/absmach/magistrala/internal/testsutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" ) @@ -731,7 +732,7 @@ func TestConnectChannelThingRequestValidate(t *testing.T) { ChannelID: "", ThingID: validID, }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, { desc: "empty thing id", @@ -740,7 +741,7 @@ func TestConnectChannelThingRequestValidate(t *testing.T) { ChannelID: validID, ThingID: "", }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, } for _, c := range cases { @@ -771,7 +772,7 @@ func TestDisconnectChannelThingRequestValidate(t *testing.T) { ChannelID: "", ThingID: validID, }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, { desc: "empty thing id", @@ -780,7 +781,7 @@ func TestDisconnectChannelThingRequestValidate(t *testing.T) { ChannelID: validID, ThingID: "", }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, } for _, c := range cases { @@ -823,7 +824,7 @@ func TestThingShareRequestValidate(t *testing.T) { UserIDs: []string{}, Relation: valid, }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, { desc: "empty relation", @@ -833,7 +834,7 @@ func TestThingShareRequestValidate(t *testing.T) { UserIDs: []string{validID}, Relation: "", }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, } for _, c := range cases { @@ -876,7 +877,7 @@ func TestThingUnshareRequestValidate(t *testing.T) { UserIDs: []string{}, Relation: valid, }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, { desc: "empty relation", @@ -886,7 +887,7 @@ func TestThingUnshareRequestValidate(t *testing.T) { UserIDs: []string{validID}, Relation: "", }, - err: errors.ErrCreateEntity, + err: svcerr.ErrCreateEntity, }, } for _, c := range cases { diff --git a/things/cache/things.go b/things/cache/things.go index f4d1a2d667..9c2619ce48 100644 --- a/things/cache/things.go +++ b/things/cache/things.go @@ -9,6 +9,7 @@ import ( "time" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/things" "github.com/go-redis/redis/v8" ) @@ -35,16 +36,16 @@ func NewCache(client *redis.Client, duration time.Duration) things.Cache { func (tc *thingCache) Save(ctx context.Context, thingKey, thingID string) error { if thingKey == "" || thingID == "" { - return errors.Wrap(errors.ErrCreateEntity, errors.New("thing key or thing id is empty")) + return errors.Wrap(repoerr.ErrCreateEntity, errors.New("thing key or thing id is empty")) } tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) if err := tc.client.Set(ctx, tkey, thingID, tc.keyDuration).Err(); err != nil { - return errors.Wrap(errors.ErrCreateEntity, err) + return errors.Wrap(repoerr.ErrCreateEntity, err) } tid := fmt.Sprintf("%s:%s", idPrefix, thingID) if err := tc.client.Set(ctx, tid, thingKey, tc.keyDuration).Err(); err != nil { - return errors.Wrap(errors.ErrCreateEntity, err) + return errors.Wrap(repoerr.ErrCreateEntity, err) } return nil @@ -52,13 +53,13 @@ func (tc *thingCache) Save(ctx context.Context, thingKey, thingID string) error func (tc *thingCache) ID(ctx context.Context, thingKey string) (string, error) { if thingKey == "" { - return "", errors.ErrNotFound + return "", repoerr.ErrNotFound } tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) thingID, err := tc.client.Get(ctx, tkey).Result() if err != nil { - return "", errors.Wrap(errors.ErrNotFound, err) + return "", errors.Wrap(repoerr.ErrNotFound, err) } return thingID, nil @@ -72,12 +73,12 @@ func (tc *thingCache) Remove(ctx context.Context, thingID string) error { return nil } if err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } tkey := fmt.Sprintf("%s:%s", keyPrefix, key) if err := tc.client.Del(ctx, tkey, tid).Err(); err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } return nil diff --git a/things/cache/things_test.go b/things/cache/things_test.go index 61821cb19e..8fa34e2227 100644 --- a/things/cache/things_test.go +++ b/things/cache/things_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/things/cache" "github.com/stretchr/testify/assert" ) @@ -55,31 +56,31 @@ func TestSave(t *testing.T) { desc: "Save thing with long key ", key: strings.Repeat("a", 513*1024*1024), id: testID, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "Save thing with long id ", key: testKey, id: strings.Repeat("a", 513*1024*1024), - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "Save thing with empty key", key: "", id: testID, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "Save thing with empty id", key: testKey, id: "", - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "Save thing with empty key and id", key: "", id: "", - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, } @@ -117,13 +118,13 @@ func TestID(t *testing.T) { desc: "Get thing ID from cache for non existing thing", key: "nonExistingKey", id: "", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "Get thing ID from cache for empty key", key: "", id: "", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -167,7 +168,7 @@ func TestRemove(t *testing.T) { { desc: "Remove thing with long id from cache", key: strings.Repeat("a", 513*1024*1024), - err: errors.ErrRemoveEntity, + err: repoerr.ErrRemoveEntity, }, } diff --git a/things/postgres/clients_test.go b/things/postgres/clients_test.go index fa191d32af..fb9edd4dcf 100644 --- a/things/postgres/clients_test.go +++ b/things/postgres/clients_test.go @@ -74,7 +74,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add new client with duplicate secret", @@ -89,7 +89,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add new client without domain id", @@ -118,7 +118,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add client with invalid client name", @@ -133,7 +133,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add client with invalid client domain id", @@ -147,7 +147,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add client with invalid client identity", @@ -161,7 +161,7 @@ func TestClientsSave(t *testing.T) { Metadata: clients.Metadata{}, Status: clients.EnabledStatus, }, - err: errors.ErrCreateEntity, + err: repoerr.ErrCreateEntity, }, { desc: "add client with a missing client identity", @@ -253,13 +253,13 @@ func TestClientsRetrieveBySecret(t *testing.T) { desc: "retrieve client by invalid secret", secret: "non-existent-secret", response: clients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve client by empty secret", secret: "", response: clients.Client{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/things/service.go b/things/service.go index 8130609c67..32987f8b84 100644 --- a/things/service.go +++ b/things/service.go @@ -80,14 +80,14 @@ func (svc service) CreateThings(ctx context.Context, token string, cls ...mgclie if c.ID == "" { clientID, err := svc.idProvider.ID() if err != nil { - return []mgclients.Client{}, errors.Wrap(svcerr.ErrUniqueID, err) + return []mgclients.Client{}, err } c.ID = clientID } if c.Credentials.Secret == "" { key, err := svc.idProvider.ID() if err != nil { - return []mgclients.Client{}, errors.Wrap(svcerr.ErrUniqueID, err) + return []mgclients.Client{}, err } c.Credentials.Secret = key } @@ -154,7 +154,7 @@ func (svc service) ViewClientPerms(ctx context.Context, token, id string) ([]str return nil, err } if len(permissions) == 0 { - return nil, errors.ErrAuthorization + return nil, svcerr.ErrAuthorization } return permissions, nil } @@ -587,10 +587,10 @@ func (svc service) Identify(ctx context.Context, key string) (string, error) { func (svc service) identify(ctx context.Context, token string) (*magistrala.IdentityRes, error) { res, err := svc.auth.Identify(ctx, &magistrala.IdentityReq{Token: token}) if err != nil { - return nil, errors.Wrap(errors.ErrAuthentication, err) + return nil, errors.Wrap(svcerr.ErrAuthentication, err) } if res.GetId() == "" || res.GetDomainId() == "" { - return nil, errors.ErrDomainAuthorization + return nil, svcerr.ErrDomainAuthorization } return res, nil } @@ -610,7 +610,7 @@ func (svc *service) authorize(ctx context.Context, domainID, subjType, subjKind, return "", err } if !res.GetAuthorized() { - return "", errors.Wrap(errors.ErrAuthorization, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } return res.GetId(), nil diff --git a/things/service_test.go b/things/service_test.go index 644bd6c0b7..23f6881b25 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -84,8 +84,8 @@ func TestCreateThings(t *testing.T) { thing: client, token: validToken, authResponse: &magistrala.AuthorizeRes{Authorized: true}, - saveErr: errors.ErrConflict, - err: svcerr.ErrConflict, + saveErr: repoerr.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "create a new thing without secret", @@ -482,7 +482,7 @@ func TestListClients(t *testing.T) { }, token: validToken, identifyResponse: &magistrala.IdentityRes{Id: nonAdminID, UserId: nonAdminID, DomainId: ""}, - err: errors.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "list all clients as non admin with failed to retrieve all", @@ -1622,8 +1622,8 @@ func TestDeleteClient(t *testing.T) { token: authmocks.InvalidValue, clientID: client.ID, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, - err: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, { desc: "Delete invalid client", @@ -1632,7 +1632,7 @@ func TestDeleteClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, authorizeErr: svcerr.ErrAuthorization, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "Delete client with repo error ", @@ -1642,8 +1642,8 @@ func TestDeleteClient(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: true}, deletePolicyResponse1: &magistrala.DeletePolicyRes{Deleted: true}, - deleteErr: errors.ErrRemoveEntity, - err: errors.ErrRemoveEntity, + deleteErr: repoerr.ErrRemoveEntity, + err: repoerr.ErrRemoveEntity, }, { desc: "Delete client with cache error ", @@ -1652,7 +1652,7 @@ func TestDeleteClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, removeErr: svcerr.ErrRemoveEntity, - err: errors.ErrRemoveEntity, + err: repoerr.ErrRemoveEntity, }, { desc: "Delete client with failed to delete groups policy", @@ -1974,7 +1974,7 @@ func TestIdentify(t *testing.T) { desc: "identify client with valid key from repo", key: valid, cacheIDResponse: "", - cacheIDErr: errors.ErrNotFound, + cacheIDErr: repoerr.ErrNotFound, repoIDResponse: client, err: nil, }, @@ -1982,16 +1982,16 @@ func TestIdentify(t *testing.T) { desc: "identify client with invalid key", key: invalid, cacheIDResponse: "", - cacheIDErr: errors.ErrNotFound, + cacheIDErr: repoerr.ErrNotFound, repoIDResponse: mgclients.Client{}, - retrieveBySecretErr: errors.ErrNotFound, - err: errors.ErrNotFound, + retrieveBySecretErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "identify client with failed to save to cache", key: valid, cacheIDResponse: "", - cacheIDErr: errors.ErrNotFound, + cacheIDErr: repoerr.ErrNotFound, repoIDResponse: client, saveErr: errors.ErrMalformedEntity, err: svcerr.ErrAuthorization, @@ -2037,8 +2037,8 @@ func TestAuthorize(t *testing.T) { key: invalid, request: &magistrala.AuthorizeReq{Subject: invalid, Object: inValidToken, Permission: "admin"}, response: &magistrala.AuthorizeRes{Authorized: false}, - identifyErr: errors.ErrNotFound, - err: errors.ErrNotFound, + identifyErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "authorize with invalid token", diff --git a/things/standalone/standalone.go b/things/standalone/standalone.go index 8d800f6a22..f9e5aabefc 100644 --- a/things/standalone/standalone.go +++ b/things/standalone/standalone.go @@ -7,7 +7,7 @@ import ( "context" "github.com/absmach/magistrala" - svcerr "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "google.golang.org/grpc" ) diff --git a/twins/api/http/transport.go b/twins/api/http/transport.go index ae79b7b12d..e5f2048c2a 100644 --- a/twins/api/http/transport.go +++ b/twins/api/http/transport.go @@ -216,7 +216,6 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(http.StatusNotFound) case errors.Contains(err, svcerr.ErrConflict): w.WriteHeader(http.StatusConflict) - case errors.Contains(err, svcerr.ErrCreateEntity), errors.Contains(err, svcerr.ErrUpdateEntity), errors.Contains(err, svcerr.ErrViewEntity), diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index 77d35e4159..0878223263 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -10,7 +10,7 @@ import ( "strings" "sync" - "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/twins" ) @@ -35,7 +35,7 @@ func (trm *twinRepositoryMock) Save(ctx context.Context, twin twins.Twin) (strin for _, tw := range trm.twins { if tw.ID == twin.ID { - return "", errors.ErrConflict + return "", repoerr.ErrConflict } } @@ -50,7 +50,7 @@ func (trm *twinRepositoryMock) Update(ctx context.Context, twin twins.Twin) erro dbKey := key(twin.Owner, twin.ID) if _, ok := trm.twins[dbKey]; !ok { - return errors.ErrNotFound + return repoerr.ErrNotFound } trm.twins[dbKey] = twin @@ -68,7 +68,7 @@ func (trm *twinRepositoryMock) RetrieveByID(_ context.Context, twinID string) (t } } - return twins.Twin{}, errors.ErrNotFound + return twins.Twin{}, repoerr.ErrNotFound } func (trm *twinRepositoryMock) RetrieveByAttribute(ctx context.Context, channel, subtopic string) ([]string, error) { diff --git a/twins/mongodb/twins.go b/twins/mongodb/twins.go index 413b512491..be50df55a3 100644 --- a/twins/mongodb/twins.go +++ b/twins/mongodb/twins.go @@ -7,6 +7,7 @@ import ( "context" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/twins" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -39,7 +40,7 @@ func (tr *twinRepository) Save(ctx context.Context, tw twins.Twin) (string, erro coll := tr.db.Collection(twinsCollection) if _, err := coll.InsertOne(ctx, tw); err != nil { - return "", errors.Wrap(errors.ErrCreateEntity, err) + return "", errors.Wrap(repoerr.ErrCreateEntity, err) } return tw.ID, nil @@ -60,7 +61,7 @@ func (tr *twinRepository) Update(ctx context.Context, tw twins.Twin) error { } if res.ModifiedCount < 1 { - return errors.ErrNotFound + return repoerr.ErrNotFound } return nil @@ -72,7 +73,7 @@ func (tr *twinRepository) RetrieveByID(ctx context.Context, twinID string) (twin filter := bson.M{"id": twinID} if err := coll.FindOne(ctx, filter).Decode(&tw); err != nil { - return tw, errors.ErrNotFound + return tw, repoerr.ErrNotFound } return tw, nil @@ -108,7 +109,7 @@ func (tr *twinRepository) RetrieveByAttribute(ctx context.Context, channel, subt cur, err := coll.Aggregate(ctx, []bson.M{prj1, match, prj2}, findOptions) if err != nil { - return []string{}, errors.Wrap(errors.ErrViewEntity, err) + return []string{}, errors.Wrap(repoerr.ErrViewEntity, err) } defer cur.Close(ctx) @@ -151,17 +152,17 @@ func (tr *twinRepository) RetrieveAll(ctx context.Context, owner string, offset, } cur, err := coll.Find(ctx, filter, findOptions) if err != nil { - return twins.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return twins.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } results, err := decodeTwins(ctx, cur) if err != nil { - return twins.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return twins.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } total, err := coll.CountDocuments(ctx, filter) if err != nil { - return twins.Page{}, errors.Wrap(errors.ErrViewEntity, err) + return twins.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) } return twins.Page{ @@ -180,11 +181,11 @@ func (tr *twinRepository) Remove(ctx context.Context, twinID string) error { filter := bson.M{"id": twinID} res, err := coll.DeleteOne(ctx, filter) if err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) + return errors.Wrap(repoerr.ErrRemoveEntity, err) } if res.DeletedCount < 1 { - return errors.ErrNotFound + return repoerr.ErrNotFound } return nil diff --git a/twins/mongodb/twins_test.go b/twins/mongodb/twins_test.go index 647e90023f..765c7863bb 100644 --- a/twins/mongodb/twins_test.go +++ b/twins/mongodb/twins_test.go @@ -11,7 +11,6 @@ import ( "testing" mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/twins" @@ -378,7 +377,7 @@ func TestTwinsRemove(t *testing.T) { { desc: "remove a non-existing twin", id: nonexistentTwinID, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/twins/service.go b/twins/service.go index 763c6cd529..f852f4e624 100644 --- a/twins/service.go +++ b/twins/service.go @@ -110,7 +110,7 @@ func (ts *twinsService) AddTwin(ctx context.Context, token string, twin Twin, de twin.ID, err = ts.idProvider.ID() if err != nil { - return Twin{}, errors.Wrap(svcerr.ErrUniqueID, err) + return Twin{}, err } twin.Owner = res.GetId() diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index 0691810aa0..ba9a78e015 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -128,7 +128,7 @@ func TestRegisterClient(t *testing.T) { token: validToken, contentType: contentType, status: http.StatusConflict, - err: errors.ErrConflict, + err: svcerr.ErrConflict, }, { desc: "register a new user with an empty token", @@ -1093,7 +1093,7 @@ func TestPasswordResetRequest(t *testing.T) { data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "invalid", testhost), contentType: contentType, status: http.StatusNotFound, - err: errors.ErrNotFound, + err: svcerr.ErrNotFound, }, { desc: "password reset with malformed data", diff --git a/users/mocks/hasher.go b/users/mocks/hasher.go index 9a90b6983c..ce47dd0a37 100644 --- a/users/mocks/hasher.go +++ b/users/mocks/hasher.go @@ -5,6 +5,7 @@ package mocks import ( "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/users" ) @@ -27,7 +28,7 @@ func (hm *hasherMock) Hash(pwd string) (string, error) { func (hm *hasherMock) Compare(plain, hashed string) error { if plain != hashed { - return errors.ErrAuthentication + return svcerr.ErrAuthentication } return nil diff --git a/users/postgres/clients.go b/users/postgres/clients.go index 5a65de6917..defb5fedcf 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -12,6 +12,7 @@ import ( pgclients "github.com/absmach/magistrala/pkg/clients/postgres" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" ) var _ mgclients.Repository = (*clientRepo)(nil) @@ -78,17 +79,17 @@ func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) erro q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2" rows, err := repo.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole) if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + return errors.Wrap(svcerr.ErrAuthorization, err) } defer rows.Close() if rows.Next() { if err := rows.Err(); err != nil { - return errors.Wrap(errors.ErrAuthorization, err) + return errors.Wrap(svcerr.ErrAuthorization, err) } return nil } - return errors.ErrAuthorization + return svcerr.ErrAuthorization } func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.Client, error) { @@ -125,7 +126,7 @@ func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.C func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) { query, err := pgclients.PageQuery(pm) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) + return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, c.status, c.role, @@ -181,17 +182,17 @@ func (repo clientRepo) UpdateRole(ctx context.Context, client mgclients.Client) dbc, err := pgclients.ToDBClient(client) if err != nil { - return mgclients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) + return mgclients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } row, err := repo.DB.NamedQueryContext(ctx, query, dbc) if err != nil { - return mgclients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) + return mgclients.Client{}, postgres.HandleError(err, repoerr.ErrUpdateEntity) } defer row.Close() if ok := row.Next(); !ok { - return mgclients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) + return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) } dbc = pgclients.DBClient{} if err := row.StructScan(&dbc); err != nil { diff --git a/users/postgres/clients_test.go b/users/postgres/clients_test.go index 29d7239602..418f22476e 100644 --- a/users/postgres/clients_test.go +++ b/users/postgres/clients_test.go @@ -13,6 +13,8 @@ import ( "github.com/absmach/magistrala/internal/testsutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" cpostgres "github.com/absmach/magistrala/users/postgres" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -69,7 +71,7 @@ func TestClientsSave(t *testing.T) { Metadata: mgclients.Metadata{}, Status: mgclients.EnabledStatus, }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "add client with duplicate client name", @@ -83,7 +85,7 @@ func TestClientsSave(t *testing.T) { Metadata: mgclients.Metadata{}, Status: mgclients.EnabledStatus, }, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "add client with invalid client id", @@ -230,7 +232,7 @@ func TestIsPlatformAdmin(t *testing.T) { Status: mgclients.EnabledStatus, Role: mgclients.UserRole, }, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -276,12 +278,12 @@ func TestRetrieveByID(t *testing.T) { { desc: "retrieve non-existing client", clientID: invalidName, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "retrieve with empty client id", clientID: "", - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } @@ -737,7 +739,7 @@ func TestUpdateRole(t *testing.T) { desc: "update role with invalid client id", client: mgclients.Client{ID: invalidName}, newRole: mgclients.AdminRole, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, } diff --git a/users/service.go b/users/service.go index 5f703cbe16..6f6acb13ba 100644 --- a/users/service.go +++ b/users/service.go @@ -77,7 +77,7 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien clientID, err := svc.idProvider.ID() if err != nil { - return mgclients.Client{}, errors.Wrap(svcerr.ErrUniqueID, err) + return mgclients.Client{}, err } if cli.Credentials.Secret == "" { @@ -120,7 +120,7 @@ func (svc service) IssueToken(ctx context.Context, identity, secret, domainID st return &magistrala.Token{}, errors.Wrap(repoerr.ErrNotFound, err) } if err := svc.hasher.Compare(secret, dbUser.Credentials.Secret); err != nil { - return &magistrala.Token{}, errors.Wrap(errors.ErrLogin, err) + return &magistrala.Token{}, errors.Wrap(svcerr.ErrLogin, err) } var d string @@ -284,7 +284,7 @@ func (svc service) UpdateClientIdentity(ctx context.Context, token, clientID, id func (svc service) GenerateResetToken(ctx context.Context, email, host string) error { client, err := svc.clients.RetrieveByIdentity(ctx, email) if err != nil || client.Credentials.Identity == "" { - return errors.ErrNotFound + return repoerr.ErrNotFound } issueReq := &magistrala.IssueReq{ UserId: client.ID, @@ -308,7 +308,7 @@ func (svc service) ResetSecret(ctx context.Context, resetToken, secret string) e return errors.Wrap(repoerr.ErrNotFound, err) } if c.Credentials.Identity == "" { - return errors.ErrNotFound + return repoerr.ErrNotFound } if !svc.passRegex.MatchString(secret) { return ErrPasswordFormat @@ -619,7 +619,7 @@ func (svc service) addClientPolicy(ctx context.Context, userID string, role mgcl return err } if !resp.Added { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } return nil } @@ -649,7 +649,7 @@ func (svc service) addClientPolicyRollback(ctx context.Context, userID string, r return err } if !resp.Deleted { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } return nil } diff --git a/users/service_test.go b/users/service_test.go index 474502942b..ebd63823fb 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -88,7 +88,7 @@ func TestRegisterClient(t *testing.T) { deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, token: validToken, saveErr: repoerr.ErrConflict, - err: errors.ErrConflict, + err: repoerr.ErrConflict, }, { desc: "register a new enabled client with name", @@ -227,7 +227,7 @@ func TestRegisterClient(t *testing.T) { Role: mgclients.AdminRole, }, addPoliciesResponse: &magistrala.AddPoliciesRes{Added: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "register a new client with failed to add policies with err", @@ -337,7 +337,7 @@ func TestRegisterClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "register a new client as admin with failed check on super admin", @@ -345,8 +345,8 @@ func TestRegisterClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: validID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases2 { @@ -418,7 +418,7 @@ func TestViewClient(t *testing.T) { retrieveByIDResponse: mgclients.Client{}, token: validToken, clientID: client.ID, - retrieveByIDErr: errors.ErrNotFound, + retrieveByIDErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, { @@ -435,7 +435,7 @@ func TestViewClient(t *testing.T) { desc: "view client as admin user with invalid token", identifyResponse: &magistrala.IdentityRes{}, token: inValidToken, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -444,7 +444,7 @@ func TestViewClient(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, clientID: client.ID, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "view client as admin user with failed check on super admin", @@ -452,8 +452,8 @@ func TestViewClient(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, clientID: client.ID, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } @@ -548,7 +548,7 @@ func TestListClients(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, retrieveAllResponse: mgclients.ClientsPage{}, token: validToken, - retrieveAllErr: errors.ErrNotFound, + retrieveAllErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, { @@ -559,7 +559,7 @@ func TestListClients(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - superAdminErr: errors.ErrAuthorization, + superAdminErr: svcerr.ErrAuthorization, err: nil, }, { @@ -593,7 +593,7 @@ func TestListClients(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, retrieveAllResponse: mgclients.ClientsPage{}, token: validToken, - retrieveAllErr: errors.ErrNotFound, + retrieveAllErr: repoerr.ErrNotFound, err: svcerr.ErrNotFound, }, } @@ -660,7 +660,7 @@ func TestUpdateClient(t *testing.T) { client: client1, identifyResponse: &magistrala.IdentityRes{}, token: inValidToken, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -694,7 +694,7 @@ func TestUpdateClient(t *testing.T) { desc: "update client name as admin with invalid token", client: client1, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, token: inValidToken, err: svcerr.ErrAuthentication, }, @@ -704,7 +704,7 @@ func TestUpdateClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client with failed check on super admin", @@ -712,8 +712,8 @@ func TestUpdateClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: adminID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client name as admin with repo error on update", @@ -778,7 +778,7 @@ func TestUpdateClientTags(t *testing.T) { desc: "update client tags as normal user with invalid token", client: client, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, token: inValidToken, err: svcerr.ErrAuthentication, }, @@ -803,7 +803,7 @@ func TestUpdateClientTags(t *testing.T) { desc: "update client tags as admin with invalid token", client: client, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, token: inValidToken, err: svcerr.ErrAuthentication, }, @@ -813,16 +813,16 @@ func TestUpdateClientTags(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client tags as admin with failed check on super admin", client: client, identifyResponse: &magistrala.IdentityRes{UserId: adminID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - checkSuperAdminErr: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client tags as admin with repo error on update", @@ -891,7 +891,7 @@ func TestUpdateClientIdentity(t *testing.T) { token: inValidToken, id: client.ID, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -919,7 +919,7 @@ func TestUpdateClientIdentity(t *testing.T) { token: inValidToken, id: client.ID, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -929,7 +929,7 @@ func TestUpdateClientIdentity(t *testing.T) { id: client.ID, identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client identity as admin with failed check on super admin", @@ -938,8 +938,8 @@ func TestUpdateClientIdentity(t *testing.T) { id: client.ID, identifyResponse: &magistrala.IdentityRes{UserId: adminID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client identity as admin with repo error on update", @@ -1011,7 +1011,7 @@ func TestUpdateClientRole(t *testing.T) { desc: "update client role with invalid token", client: client, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, token: inValidToken, err: svcerr.ErrAuthentication, }, @@ -1021,16 +1021,16 @@ func TestUpdateClientRole(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: wrongID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client role with failed check on super admin", client: client, identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - checkSuperAdminErr: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client role with failed authorization on add policy", @@ -1039,7 +1039,7 @@ func TestUpdateClientRole(t *testing.T) { authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, addPolicyResponse: &magistrala.AddPolicyRes{Added: false}, token: validToken, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "update client role with failed to add policy", @@ -1175,7 +1175,7 @@ func TestUpdateClientSecret(t *testing.T) { newSecret: newSecret, token: inValidToken, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -1215,7 +1215,7 @@ func TestUpdateClientSecret(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, retrieveByIDResponse: client, retrieveByIdentityResponse: rClient, - err: errors.ErrLogin, + err: svcerr.ErrLogin, }, { desc: "update client secret with too long new secret", @@ -1309,7 +1309,7 @@ func TestEnableClient(t *testing.T) { token: inValidToken, client: disabledClient1, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -1319,7 +1319,7 @@ func TestEnableClient(t *testing.T) { client: disabledClient1, identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "enable disabled client with normal user token", @@ -1328,8 +1328,8 @@ func TestEnableClient(t *testing.T) { client: disabledClient1, identifyResponse: &magistrala.IdentityRes{UserId: validID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "enable disabled client with failed to retrieve client by ID", @@ -1496,7 +1496,7 @@ func TestDisableClient(t *testing.T) { token: inValidToken, client: enabledClient1, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { @@ -1506,7 +1506,7 @@ func TestDisableClient(t *testing.T) { client: enabledClient1, identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "disable enabled client with normal user token", @@ -1515,8 +1515,8 @@ func TestDisableClient(t *testing.T) { client: enabledClient1, identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - checkSuperAdminErr: errors.ErrAuthorization, - err: errors.ErrAuthorization, + checkSuperAdminErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "disable enabled client with failed to retrieve client by ID", @@ -1847,7 +1847,7 @@ func TestListMembers(t *testing.T) { Object: validID, }, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "list members with of the things kind with failed to list all subjects", @@ -1872,9 +1872,9 @@ func TestListMembers(t *testing.T) { ObjectType: authsvc.ThingType, }, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - listAllSubjectsErr: errors.ErrNotFound, + listAllSubjectsErr: repoerr.ErrNotFound, listAllSubjectsResponse: &magistrala.ListSubjectsRes{}, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "list members with of the things kind with failed to retrieve all", @@ -2001,7 +2001,7 @@ func TestListMembers(t *testing.T) { Object: validID, }, authorizeResponse: &magistrala.AuthorizeRes{Authorized: false}, - err: errors.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { desc: "list members with no policies successfully of the groups kind", @@ -2085,7 +2085,7 @@ func TestListMembers(t *testing.T) { token: inValidToken, page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read"}, identifyResponse: &magistrala.IdentityRes{}, - identifyErr: errors.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, } @@ -2139,14 +2139,14 @@ func TestIssueToken(t *testing.T) { desc: "issue token for a non-existing client", client: client, retrieveByIdentityResponse: mgclients.Client{}, - retrieveByIdentityErr: errors.ErrNotFound, + retrieveByIdentityErr: repoerr.ErrNotFound, err: repoerr.ErrNotFound, }, { desc: "issue token for a client with wrong secret", client: client, retrieveByIdentityResponse: rClient3, - err: errors.ErrLogin, + err: svcerr.ErrLogin, }, { desc: "issue token with non-empty domain id", @@ -2269,8 +2269,8 @@ func TestGenerateResetToken(t *testing.T) { Identity: "", }, }, - retrieveByIdentityErr: errors.ErrNotFound, - err: errors.ErrNotFound, + retrieveByIdentityErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "generate reset token with failed to issue token", @@ -2360,7 +2360,7 @@ func TestResetSecret(t *testing.T) { Identity: "", }, }, - err: errors.ErrNotFound, + err: repoerr.ErrNotFound, }, { desc: "reset secret with invalid secret format", diff --git a/ws/adapter.go b/ws/adapter.go index 76963550ae..dcc97dee0c 100644 --- a/ws/adapter.go +++ b/ws/adapter.go @@ -10,6 +10,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" ) @@ -105,10 +106,10 @@ func (svc *adapterService) authorize(ctx context.Context, thingKey, chanID, acti } res, err := svc.auth.Authorize(ctx, ar) if err != nil { - return "", errors.Wrap(errors.ErrAuthorization, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return "", errors.Wrap(errors.ErrAuthorization, err) + return "", errors.Wrap(svcerr.ErrAuthorization, err) } return res.GetId(), nil diff --git a/ws/handler.go b/ws/handler.go index 855454294b..7ced66cb0b 100644 --- a/ws/handler.go +++ b/ws/handler.go @@ -177,7 +177,7 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e return err } if !res.GetAuthorized() { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } msg := messaging.Message{ From 6745435eb525d0334f57a01ee1d4422f7d50f505 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:39:50 +0530 Subject: [PATCH 45/71] NOIISUE - Update Vault setup scripts to support Vault CLI (#2091) Signed-off-by: Arvindh --- certs/README.md | 14 ++-- docker/.env | 5 +- docker/addons/vault/README.md | 29 ++++--- docker/addons/vault/vault_cmd.sh | 24 ++++++ docker/addons/vault/vault_copy_certs.sh | 28 ++++++- docker/addons/vault/vault_copy_env.sh | 13 +++- docker/addons/vault/vault_create_approle.sh | 14 ++-- docker/addons/vault/vault_init.sh | 12 ++- docker/addons/vault/vault_set_pki.sh | 84 ++++++++++++++------- docker/addons/vault/vault_unseal.sh | 12 +-- 10 files changed, 165 insertions(+), 70 deletions(-) create mode 100644 docker/addons/vault/vault_cmd.sh diff --git a/certs/README.md b/certs/README.md index ad89e581c2..a0e727dba7 100644 --- a/certs/README.md +++ b/certs/README.md @@ -9,17 +9,17 @@ When `MG_CERTS_VAULT_HOST` is set it is presumed that `Vault` is installed and ` First you'll need to set up `Vault`. To setup `Vault` follow steps in [Build Your Own Certificate Authority (CA)](https://learn.hashicorp.com/tutorials/vault/pki-engine). -To setup certs service with `Vault` following environment variables must be set: +For lab purposes you can use docker-compose and script for setting up PKI in [https://github.com/absmach/magistrala/blob/master/docker/addons/vault/README.md](https://github.com/absmach/magistrala/blob/master/docker/addons/vault/README.md) ```bash -MG_CERTS_VAULT_HOST=vault-domain.com -MG_CERTS_VAULT_PKI_PATH= -MG_CERTS_VAULT_ROLE= -MG_CERTS_VAULT_TOKEN= +MG_CERTS_VAULT_HOST= +MG_CERTS_VAULT_NAMESPACE= +MG_CERTS_VAULT_APPROLE_ROLEID= +MG_CERTS_VAULT_APPROLE_SECRET= +MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH= +MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME= ``` -For lab purposes you can use docker-compose and script for setting up PKI in [https://github.com/mteodor/vault](https://github.com/mteodor/vault) - The certificates can also be revoked using `certs` service. To revoke a certificate you need to provide `thing_id` of the thing for which the certificate was issued. ```bash diff --git a/docker/.env b/docker/.env index 4a5e0bcb84..2f6910de42 100644 --- a/docker/.env +++ b/docker/.env @@ -271,7 +271,7 @@ MG_UI_DB_SSL_ROOT_CERT= ## Addons Services ### Bootstrap MG_BOOTSTRAP_LOG_LEVEL=debug -MG_BOOTSTRAP_ENCRYPT_KEY=v7aT0HGxJxt2gULzr3RHwf4WIf6DusPphG5Ftm2bNCWD8mTpyr +MG_BOOTSTRAP_ENCRYPT_KEY=v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp MG_BOOTSTRAP_EVENT_CONSUMER=bootstrap MG_BOOTSTRAP_HTTP_HOST=bootstrap MG_BOOTSTRAP_HTTP_PORT=9013 @@ -302,8 +302,7 @@ MG_PROVISION_PASS= MG_PROVISION_API_KEY= MG_PROVISION_CERTS_SVC_URL=http://certs:9019 MG_PROVISION_X509_PROVISIONING=false -MG_PROVISION_BS_SVC_URL=http://bootstrap:9013/things -MG_PROVISION_BS_SVC_WHITELIST_URL=http://bootstrap:9013/things/state +MG_PROVISION_BS_SVC_URL=http://bootstrap:9013 MG_PROVISION_BS_CONFIG_PROVISIONING=true MG_PROVISION_BS_AUTO_WHITELIST=true MG_PROVISION_BS_CONTENT= diff --git a/docker/addons/vault/README.md b/docker/addons/vault/README.md index 193fa4dafa..1ac1136bca 100644 --- a/docker/addons/vault/README.md +++ b/docker/addons/vault/README.md @@ -6,11 +6,8 @@ When the Vault service is started, some initialization steps need to be done to ## Configuration - | Variable | Description | Default | -| :---------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------- | -| MG_VAULT_HOST | Vault service address | vault | -| MG_VAULT_PORT | Vault service port | 8200 | +| :-------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------- | | MG_VAULT_ADDR | Vault Address | http://vault:8200 | | MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | | MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | @@ -106,24 +103,34 @@ The parameters required for generating certificate are obtained from the environ Environmental variables starting with `MG_VAULT_PKI` in `docker/.env` file are used by `vault_set_pki.sh` to generate root CA. Environmental variables starting with`MG_VAULT_PKI_INT` in `docker/.env` file are used by `vault_set_pki.sh` to generate intermediate CA. +Passing command line args `--skip-server-cert` to `vault_set_pki.sh` will skip server certificate role & process of generation of server certificate & key. + ### 5. `vault_create_approle.sh` This script is used to enable app role authorization in Vault. Certs service used the approle credentials to issue, revoke things certificate from vault intermedate CA. `vault_create_approle.sh` script by default tries to enable auth approle. -If approle is already enabled in vault, then use args `skip_enable_app_role` to skip enable auth approle step. -To skip enable auth approle step use the following `vault_create_approle.sh skip_enable_app_role` +If approle is already enabled in vault, then use args `--skip-enable-approle` to skip enable auth approle step. +To skip enable auth approle step use the following `vault_create_approle.sh --skip-enable-approle` ### 6. `vault_copy_certs.sh` This scripts copies the necessary certificates and keys from `docker/addons/vault/data` to the `docker/ssl/certs` folder. +## Hashicorp Cloud Platform (HCP) Vault + +To have the same PKI setup can done in Hashicorp Cloud Platform (HCP) Vault follow the below steps: +Requirement: [VAULT CLI](https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install) + +- Replace the environmental variable `MG_VAULT_ADDR` in `docker/.env` with HCP Vault address. +- Replace the environmental variable `MG_VAULT_TOKEN` in `docker/.env` with HCP Vault Admin token. +- Run script `vault_set_pki.sh` and `vault_create_approle.sh`. +- Optional step, run script `vault_copy_certs.sh` to copy certificates to magistrala default path. + ## Vault CLI It can also be useful to run the Vault CLI for inspection and administration work. -This can be done directly using the Vault image in Docker: `docker run -it magistrala/vault:latest vault` - ```bash Usage: vault [args] @@ -156,6 +163,8 @@ Other commands: token Interact with tokens ``` -### Vault Web UI +If the Vault is setup through `docker/addons/vault`, then Vault CLI can be run directly using the Vault image in Docker: `docker run -it magistrala/vault:latest vault` + +## Vault Web UI -The Vault Web UI is accessible by default on `http://localhost:8200/ui`. +If the Vault is setup through `docker/addons/vault`, Then Vault Web UI is accessible by default on `http://localhost:8200/ui`. diff --git a/docker/addons/vault/vault_cmd.sh b/docker/addons/vault/vault_cmd.sh new file mode 100644 index 0000000000..97a8cc9247 --- /dev/null +++ b/docker/addons/vault/vault_cmd.sh @@ -0,0 +1,24 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +vault() { + if is_container_running "magistrala-vault"; then + docker exec -it magistrala-vault vault "$@" + else + if which vault &> /dev/null; then + $(which vault) "$@" + else + echo "magistrala-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md" + fi + fi +} + +is_container_running() { + local container_name="$1" + if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then + return 0 + else + return 1 + fi +} diff --git a/docker/addons/vault/vault_copy_certs.sh b/docker/addons/vault/vault_copy_certs.sh index 1f00a8f516..c4656df6c5 100755 --- a/docker/addons/vault/vault_copy_certs.sh +++ b/docker/addons/vault/vault_copy_certs.sh @@ -25,9 +25,29 @@ if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then fi echo "Copying certificate files" -cp -v data/${server_name}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt -cp -v data/${server_name}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key -cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key -cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt + +if [ -e "data/${server_name}.crt" ]; then + cp -v data/${server_name}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt +else + echo "${server_name}.crt file not available" +fi + +if [ -e "data/${server_name}.key" ]; then + cp -v data/${server_name}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key +else + echo "${server_name}.key file not available" +fi + +if [ -e "data/${MG_VAULT_PKI_INT_FILE_NAME}.key" ]; then + cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key +else + echo "data/${MG_VAULT_PKI_INT_FILE_NAME}.key file not available" +fi + +if [ -e "data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt" ]; then + cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt +else + echo "data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt file not available" +fi exit 0 diff --git a/docker/addons/vault/vault_copy_env.sh b/docker/addons/vault/vault_copy_env.sh index 92c773e513..dbb5fe41b4 100755 --- a/docker/addons/vault/vault_copy_env.sh +++ b/docker/addons/vault/vault_copy_env.sh @@ -10,10 +10,15 @@ export MAGISTRALA_DIR=$scriptdir/../../../ cd $scriptdir write_env() { - sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env - sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env - sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env - sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env + if [ -e "data/secrets" ]; then + sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env + sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env + sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env + sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env + echo "Vault environment varaibles are set successfully in docker/.env" + else + echo "Error: Source file 'data/secrets' not found." + fi } write_env diff --git a/docker/addons/vault/vault_create_approle.sh b/docker/addons/vault/vault_create_approle.sh index 59b8b44c7a..614f8dca65 100755 --- a/docker/addons/vault/vault_create_approle.sh +++ b/docker/addons/vault/vault_create_approle.sh @@ -17,9 +17,7 @@ readDotEnv() { set +o allexport } -vault() { - docker exec -it magistrala-vault vault "$@" -} +source vault_cmd.sh vaultCreatePolicyFile() { envsubst ' @@ -29,12 +27,16 @@ vaultCreatePolicyFile() { } vaultCreatePolicy() { echo "Creating new policy for AppRole" - docker cp magistrala_things_certs_issue.hcl magistrala-vault:/vault/magistrala_things_certs_issue.hcl - vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl + if is_container_running "magistrala-vault"; then + docker cp magistrala_things_certs_issue.hcl magistrala-vault:/vault/magistrala_things_certs_issue.hcl + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl + else + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue magistrala_things_certs_issue.hcl + fi } vaultEnableAppRole() { - if [ "$SKIP_ENABLE_APP_ROLE" == "skip_enable_app_role" ]; then + if [ "$SKIP_ENABLE_APP_ROLE" == "--skip-enable-approle" ]; then echo "Skipping Enable AppRole" else echo "Enabling AppRole" diff --git a/docker/addons/vault/vault_init.sh b/docker/addons/vault/vault_init.sh index e375cbc23a..bd1e05ffd4 100755 --- a/docker/addons/vault/vault_init.sh +++ b/docker/addons/vault/vault_init.sh @@ -9,10 +9,16 @@ export MAGISTRALA_DIR=$scriptdir/../../../ cd $scriptdir -vault() { - docker exec -it magistrala-vault vault "$@" +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport } +source vault_cmd.sh + +readDotEnv + mkdir -p data -vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) +vault operator init -address=$MG_VAULT_ADDR 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) diff --git a/docker/addons/vault/vault_set_pki.sh b/docker/addons/vault/vault_set_pki.sh index 51bfee1cff..6f8ebdcf8c 100755 --- a/docker/addons/vault/vault_set_pki.sh +++ b/docker/addons/vault/vault_set_pki.sh @@ -7,6 +7,8 @@ set -euo pipefail scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export MAGISTRALA_DIR=$scriptdir/../../../ +SKIP_SERVER_CERT=${1:-} + cd $scriptdir readDotEnv() { @@ -22,9 +24,7 @@ if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then server_name="$MG_NGINX_SERVER_NAME" fi -vault() { - docker exec -it magistrala-vault vault "$@" -} +source vault_cmd.sh vaultEnablePKI() { vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path ${MG_VAULT_PKI_PATH} pki @@ -103,24 +103,43 @@ vaultGenerateIntermediateCSR() { vaultSignIntermediateCSR() { echo "Sign intermediate CSR" - docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.csr magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ - csr=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ - ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ - organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ - country="\"$MG_VAULT_PKI_INT_CA_C\"" \ - locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ - province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ - street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ - postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ - | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_INT_FILE_NAME}.crt) \ - >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt) + if is_container_running "magistrala-vault"; then + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.csr magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ + csr=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_INT_FILE_NAME}.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt) + else + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ + csr=@data/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_INT_FILE_NAME}.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt) + fi + } vaultInjectIntermediateCertificate() { echo "Inject Intermediate Certificate" - docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.crt magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt + if is_container_running "magistrala-vault"; then + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.crt magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt + else + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@data/${MG_VAULT_PKI_INT_FILE_NAME}.crt + fi } vaultGenerateIntermediateCertificateBundle() { @@ -139,18 +158,27 @@ vaultSetupIntermediateIssuingURLs() { } vaultSetupServerCertsRole() { - echo "Setup Server Certs role" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ - allow_subdomains=true \ - max_ttl="4320h" + if [ "$SKIP_SERVER_CERT" == "--skip-server-cert" ]; then + echo "Skipping server certificate role" + else + echo "Setup Server certificate role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + allow_subdomains=true \ + max_ttl="4320h" + fi } vaultGenerateServerCertificate() { - echo "Generate server certificate" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ - common_name="$server_name" ttl="4320h" \ - | tee >(jq -r .data.certificate >data/${server_name}.crt) \ - >(jq -r .data.private_key >data/${server_name}.key) + if [ "$SKIP_SERVER_CERT" == "--skip-server-cert" ]; then + echo "Skipping generate server certificate" + else + echo "Generate server certificate" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + common_name="$server_name" ttl="4320h" \ + | tee >(jq -r .data.certificate >data/${server_name}.crt) \ + >(jq -r .data.private_key >data/${server_name}.key) + fi + } vaultSetupThingCertsRole() { @@ -162,7 +190,9 @@ vaultSetupThingCertsRole() { } vaultCleanupFiles() { - docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' + if is_container_running "magistrala-vault"; then + docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' + fi } if ! command -v jq &> /dev/null diff --git a/docker/addons/vault/vault_unseal.sh b/docker/addons/vault/vault_unseal.sh index 5132f65484..b80b6eed05 100755 --- a/docker/addons/vault/vault_unseal.sh +++ b/docker/addons/vault/vault_unseal.sh @@ -7,18 +7,18 @@ set -euo pipefail scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export MAGISTRALA_DIR=$scriptdir/../../../ +cd $scriptdir + readDotEnv() { set -o allexport source $MAGISTRALA_DIR/docker/.env set +o allexport } -vault() { - docker exec -it magistrala-vault vault "$@" -} +source vault_cmd.sh readDotEnv -vault operator unseal ${MG_VAULT_UNSEAL_KEY_1} -vault operator unseal ${MG_VAULT_UNSEAL_KEY_2} -vault operator unseal ${MG_VAULT_UNSEAL_KEY_3} +vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_1} +vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_2} +vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_3} From 0e49c12d924c4c240a1154ae5f43880fce08b0c7 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:39:06 +0530 Subject: [PATCH 46/71] NOISSUE : Add Domain routes in Nginx x509 conf (#2100) Signed-off-by: Arvindh --- docker/nginx/nginx-x509.conf | 65 +++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index 1c35f83e63..69cc38c759 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -65,8 +65,71 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; + location ~ ^/(channels)/(.+)/(things)/(.+) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + } + # Proxy pass to users & groups id to things service for listing of channels + # /users/{userID}/channels - Listing of channels belongs to userID + # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID + location ~ ^/(users|groups)/(.+)/(channels|things) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + break; + } + proxy_pass http://users:${MG_USERS_HTTP_PORT}; + } + + # Proxy pass to channel id to users service for listing of channels + # /channels/{channelID}/users - Listing of Users belongs to channelID + # /channels/{channelID}/groups - Listing of User Groups belongs to channelID + location ~ ^/(channels|things)/(.+)/(users|groups) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://users:${MG_USERS_HTTP_PORT}; + break; + } + proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + } + + # Proxy pass to user id to auth service for listing of domains + # /users/{userID}/domains - Listing of Domains belongs to userID + location ~ ^/(users)/(.+)/(domains) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; + break; + } + proxy_pass http://users:${MG_USERS_HTTP_PORT}; + } + + # Proxy pass to domain id to users service for listing of users + # /domains/{domainID}/users - Listing of Users belongs to domainID + location ~ ^/(domains)/(.+)/(users) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://users:${MG_USERS_HTTP_PORT}; + break; + } + proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; + } + + + # Proxy pass to auth service + location ~ ^/(domains) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; + } + # Proxy pass to users service - location ~ ^/(users|groups|password|policies|authorize) { + location ~ ^/(users|groups|password|authorize) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; From 17a9d3628986938e29453df1274160c4ea0f2fac Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:27:20 +0530 Subject: [PATCH 47/71] NOISSUE - Fix panic during revocation of expired certificate (#2101) Signed-off-by: Arvindh --- certs/pki/vault.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/certs/pki/vault.go b/certs/pki/vault.go index 91f4617c0a..1eb10cf205 100644 --- a/certs/pki/vault.go +++ b/certs/pki/vault.go @@ -7,6 +7,7 @@ package pki import ( "context" "encoding/json" + "fmt" "log/slog" "time" @@ -178,12 +179,24 @@ func (p *pkiAgent) Revoke(serial string) (time.Time, error) { if err != nil { return time.Time{}, err } - rev, err := s.Data["revocation_time"].(json.Number).Float64() - if err != nil { - return time.Time{}, err + + // Vault will return a response without errors but with a warning if the certificate is expired. + // The response will not have "revocation_time" in such cases. + if revokeTime, ok := s.Data["revocation_time"]; ok { + switch v := revokeTime.(type) { + case json.Number: + rev, err := v.Float64() + if err != nil { + return time.Time{}, err + } + return time.Unix(0, int64(rev)*int64(time.Second)), nil + + default: + return time.Time{}, fmt.Errorf("unsupported type for revocation_time: %T", v) + } } - return time.Unix(0, int64(rev)*int64(time.Second)), nil + return time.Time{}, nil } func (p *pkiAgent) LoginAndRenew(ctx context.Context) error { From 233ecfa6b6b5b124663a9091b989111f1a8376b8 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:58:23 +0300 Subject: [PATCH 48/71] NOISSUE - Add job for checking `go.mod` changes (#2097) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/check-generated-files.yml | 5 +++++ go.mod | 2 +- go.sum | 9 --------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index 9773002293..c50e121ce9 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -23,6 +23,11 @@ jobs: with: go-version: 1.21.x cache-dependency-path: "go.sum" + + - name: Check for changes in go.mod + run: | + go mod tidy + git diff --exit-code - name: Check for changes in specific paths uses: dorny/paths-filter@v2 diff --git a/go.mod b/go.mod index ce6d068a69..6f3bb409ed 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/gopcua/opcua v0.1.6 github.com/gorilla/websocket v1.5.1 github.com/hashicorp/vault/api v1.12.0 + github.com/hashicorp/vault/api/auth/approle v0.6.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/ivanpirog/coloredcobra v1.0.1 @@ -110,7 +111,6 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/api/auth/approle v0.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect diff --git a/go.sum b/go.sum index 5853168b52..162ef1973c 100644 --- a/go.sum +++ b/go.sum @@ -43,7 +43,6 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= @@ -235,8 +234,6 @@ github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEy github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= -github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= @@ -618,8 +615,6 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -662,8 +657,6 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -716,8 +709,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= From e36919b945adb4e187e8ce340f69cb4d4ac9f6d5 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:57:47 +0530 Subject: [PATCH 49/71] NOISSUE - Fix assign and unassign commands in CLI (#2102) Signed-off-by: Arvindh --- cli/channels.go | 152 +++++++++++++++++++++++++++++------------------- cli/config.go | 2 +- cli/domains.go | 59 ++++++++++++++----- cli/groups.go | 127 +++++++++++++++++++++++++--------------- 4 files changed, 219 insertions(+), 121 deletions(-) diff --git a/cli/channels.go b/cli/channels.go index 6eecc8c373..4e86f7ee04 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -185,36 +185,64 @@ var cmdChannels = []cobra.Command{ }, }, { - Use: "assign user ", - Short: "Assign user", - Long: "Assign user to a channel\n" + + Use: "users ", + Short: "List users", + Long: "List users of a channel\n" + "Usage:\n" + - "\tmagistrala-cli channels assign user '[\"\", \"\"]' $USERTOKEN\n", + "\tmagistrala-cli channels users $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 2 { logUsage(cmd.Use) return } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + pm := mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUsers(args[0], pm, args[1]) + if err != nil { logError(err) return } - if err := sdk.AddUserToChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + + logJSON(ul) + }, + }, + { + Use: "groups ", + Short: "List groups", + Long: "List groups of a channel\n" + + "Usage:\n" + + "\tmagistrala-cli channels groups $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUserGroups(args[0], pm, args[1]) + if err != nil { logError(err) return } - logOK() + + logJSON(ul) }, }, +} + +var channelAssignCmds = []cobra.Command{ { - Use: "unassign user ", - Short: "Unassign user", - Long: "Unassign user from a channel\n" + + Use: "users ", + Short: "Assign users", + Long: "Assign users to a channel\n" + "Usage:\n" + - "\tmagistrala-cli channels unassign user '[\"\", \"\"]' $USERTOKEN\n", + "\tmagistrala-cli channels assign users '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 4 { logUsage(cmd.Use) return } @@ -223,7 +251,7 @@ var cmdChannels = []cobra.Command{ logError(err) return } - if err := sdk.RemoveUserFromChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + if err := sdk.AddUserToChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } @@ -231,13 +259,14 @@ var cmdChannels = []cobra.Command{ }, }, { - Use: "assign group ", - Short: "Assign group", - Long: "Assign group to a channel\n" + + Use: "groups ", + Short: "Assign groups", + Long: "Assign groups to a channel\n" + "Usage:\n" + - "\tmagistrala-cli channels assign group '[\"\", \"\"]' $USERTOKEN\n", + "\tmagistrala-cli channels assign groups '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 3 { logUsage(cmd.Use) return } @@ -253,14 +282,17 @@ var cmdChannels = []cobra.Command{ logOK() }, }, +} + +var channelUnassignCmds = []cobra.Command{ { - Use: "unassign group ", - Short: "Unassign group", - Long: "Unassign group from a channel\n" + + Use: "groups ", + Short: "Unassign groups", + Long: "Unassign groups from a channel\n" + "Usage:\n" + - "\tmagistrala-cli channels unassign group '[\"\", \"\"]' $USERTOKEN\n", + "\tmagistrala-cli channels unassign groups '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 3 { logUsage(cmd.Use) return } @@ -276,56 +308,56 @@ var cmdChannels = []cobra.Command{ logOK() }, }, + { - Use: "users ", - Short: "List users", - Long: "List users of a channel\n" + + Use: "users ", + Short: "Unassign users", + Long: "Unassign users from a channel\n" + "Usage:\n" + - "\tmagistrala-cli channels users $USERTOKEN\n", + "\tmagistrala-cli channels unassign users '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { + if len(args) != 4 { logUsage(cmd.Use) return } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - ul, err := sdk.ListChannelUsers(args[0], pm, args[1]) - if err != nil { + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { logError(err) return } - - logJSON(ul) - }, - }, - { - Use: "groups ", - Short: "List groups", - Long: "List groups of a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels groups $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsage(cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - ul, err := sdk.ListChannelUserGroups(args[0], pm, args[1]) - if err != nil { + if err := sdk.RemoveUserFromChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } - - logJSON(ul) + logOK() }, }, } +func NewChannelAssignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "assign [users | groups]", + Short: "Assign users or groups to a channel", + Long: "Assign users or groups to a channel", + } + for i := range channelAssignCmds { + cmd.AddCommand(&channelAssignCmds[i]) + } + return &cmd +} + +func NewChannelUnassignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "unassign [users | groups]", + Short: "Unassign users or groups from a channel", + Long: "Unassign users or groups from a channel", + } + for i := range channelUnassignCmds { + cmd.AddCommand(&channelUnassignCmds[i]) + } + return &cmd +} + // NewChannelsCmd returns channels command. func NewChannelsCmd() *cobra.Command { cmd := cobra.Command{ @@ -338,5 +370,7 @@ func NewChannelsCmd() *cobra.Command { cmd.AddCommand(&cmdChannels[i]) } + cmd.AddCommand(NewChannelAssignCmds()) + cmd.AddCommand(NewChannelUnassignCmds()) return &cmd } diff --git a/cli/config.go b/cli/config.go index 6f37a39475..ff641ab6d7 100644 --- a/cli/config.go +++ b/cli/config.go @@ -90,7 +90,7 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { ReaderURL: "http://localhost", HTTPAdapterURL: "http://localhost/http:9016", BootstrapURL: "http://localhost", - CertsURL: "https://localhost:9019", + CertsURL: "http://localhost:9019", TLSVerification: false, }, } diff --git a/cli/domains.go b/cli/domains.go index 651885a3d5..3610ff3d37 100644 --- a/cli/domains.go +++ b/cli/domains.go @@ -171,47 +171,52 @@ var cmdDomains = []cobra.Command{ logOK() }, }, +} +var domainAssignCmds = []cobra.Command{ { - Use: "assign user ", - Short: "Assign user", - Long: "Assign user to a domain\n" + + Use: "users ", + Short: "Assign users", + Long: "Assign users to a domain\n" + "Usage:\n" + - "\tmagistrala-cli groups assign user '[\"\", \"\"]' $TOKEN\n", + "\tmagistrala-cli groups assign users '[\"\", \"\"]' $TOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 4 { logUsage(cmd.Use) return } var userIDs []string - if err := json.Unmarshal([]byte(args[2]), &userIDs); err != nil { + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { logError(err) return } - if err := sdk.AddUserToDomain(args[3], mgxsdk.UsersRelationRequest{Relation: args[1], UserIDs: userIDs}, args[4]); err != nil { + if err := sdk.AddUserToDomain(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } logOK() }, }, +} + +var domainUnassignCmds = []cobra.Command{ { - Use: "unassign user ", - Short: "Unassign user", - Long: "Unassign user from a domain\n" + + Use: "users ", + Short: "Unassign users", + Long: "Unassign users from a domain\n" + "Usage:\n" + - "\tmagistrala-cli groups unassign user '[\"\", \"\"]' $TOKEN\n", + "\tmagistrala-cli groups unassign users '[\"\", \"\"]' $TOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 4 { logUsage(cmd.Use) return } var userIDs []string - if err := json.Unmarshal([]byte(args[2]), &userIDs); err != nil { + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { logError(err) return } - if err := sdk.RemoveUserFromDomain(args[3], mgxsdk.UsersRelationRequest{Relation: args[1], UserIDs: userIDs}, args[4]); err != nil { + if err := sdk.RemoveUserFromDomain(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } @@ -220,6 +225,30 @@ var cmdDomains = []cobra.Command{ }, } +func NewDomainAssignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "assign [users]", + Short: "Assign users to a domain", + Long: "Assign users to a domain", + } + for i := range domainAssignCmds { + cmd.AddCommand(&domainAssignCmds[i]) + } + return &cmd +} + +func NewDomainUnassignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "unassign [users]", + Short: "Unassign users from a domain", + Long: "Unassign users from a domain", + } + for i := range domainUnassignCmds { + cmd.AddCommand(&domainUnassignCmds[i]) + } + return &cmd +} + // NewDomainsCmd returns domains command. func NewDomainsCmd() *cobra.Command { cmd := cobra.Command{ @@ -232,5 +261,7 @@ func NewDomainsCmd() *cobra.Command { cmd.AddCommand(&cmdDomains[i]) } + cmd.AddCommand(NewDomainAssignCmds()) + cmd.AddCommand(NewDomainUnassignCmds()) return &cmd } diff --git a/cli/groups.go b/cli/groups.go index 84190c8a3e..294f9e9d15 100644 --- a/cli/groups.go +++ b/cli/groups.go @@ -159,53 +159,6 @@ var cmdGroups = []cobra.Command{ logOK() }, }, - { - Use: "assign user ", - Short: "Assign user", - Long: "Assign user to a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups assign user '[\"\", \"\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logError(err) - return - } - if err := sdk.AddUserToGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { - logError(err) - return - } - logOK() - }, - }, - { - Use: "unassign user ", - Short: "Unassign user", - Long: "Unassign user from a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups unassign user '[\"\", \"\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logError(err) - return - } - if err := sdk.RemoveUserFromGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { - logError(err) - return - } - logOK() - }, - }, - { Use: "users ", Short: "List users", @@ -298,6 +251,84 @@ var cmdGroups = []cobra.Command{ }, } +var groupAssignCmds = []cobra.Command{ + { + Use: "users ", + Short: "Assign users", + Long: "Assign users to a group\n" + + "Usage:\n" + + "\tmagistrala-cli groups assign users '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.AddUserToGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, +} + +var groupUnassignCmds = []cobra.Command{ + { + Use: "users ", + Short: "Unassign users", + Long: "Unassign users from a group\n" + + "Usage:\n" + + "\tmagistrala-cli groups unassign users '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserFromGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, +} + +func NewGroupAssignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "assign [users]", + Short: "Assign users to a group", + Long: "Assign users to a group", + } + + for i := range groupAssignCmds { + cmd.AddCommand(&groupAssignCmds[i]) + } + return &cmd +} + +func NewGroupUnassignCmds() *cobra.Command { + cmd := cobra.Command{ + Use: "unassign [users]", + Short: "Unassign users from a group", + Long: "Unassign users from a group", + } + + for i := range groupUnassignCmds { + cmd.AddCommand(&groupUnassignCmds[i]) + } + return &cmd +} + // NewGroupsCmd returns users command. func NewGroupsCmd() *cobra.Command { cmd := cobra.Command{ @@ -310,5 +341,7 @@ func NewGroupsCmd() *cobra.Command { cmd.AddCommand(&cmdGroups[i]) } + cmd.AddCommand(NewGroupAssignCmds()) + cmd.AddCommand(NewGroupUnassignCmds()) return &cmd } From 5d54aa84fdde47dfa5443c9a9a54707f9b3936ee Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:42:08 +0300 Subject: [PATCH 50/71] NOISSUE - Add Event Subscriber Config (#2054) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- cmd/bootstrap/main.go | 18 +- cmd/lora/main.go | 22 +- cmd/opcua/main.go | 22 +- pkg/events/events.go | 9 +- pkg/events/mocks/subscriber.go | 10 +- pkg/events/nats/publisher_test.go | 192 +++++++++------- pkg/events/nats/setup_test.go | 2 +- pkg/events/nats/subscriber.go | 47 ++-- pkg/events/rabbitmq/publisher_test.go | 194 ++++++++-------- pkg/events/rabbitmq/setup_test.go | 2 +- pkg/events/rabbitmq/subscriber.go | 43 ++-- pkg/events/redis/publisher.go | 5 +- pkg/events/redis/publisher_test.go | 211 ++++++++++-------- pkg/events/redis/setup_test.go | 3 - pkg/events/redis/subscriber.go | 55 ++--- .../store/{brokers_nats.go => store_nats.go} | 4 +- ...{brokers_rabbitmq.go => store_rabbitmq.go} | 4 +- .../{brokers_redis.go => store_redis.go} | 4 +- 18 files changed, 448 insertions(+), 399 deletions(-) rename pkg/events/store/{brokers_nats.go => store_nats.go} (76%) rename pkg/events/store/{brokers_rabbitmq.go => store_rabbitmq.go} (76%) rename pkg/events/store/{brokers_redis.go => store_redis.go} (78%) diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index 78200f43cc..61f56096fc 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -28,6 +28,7 @@ import ( httpserver "github.com/absmach/magistrala/internal/server/http" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/auth" + "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/pkg/uuid" @@ -45,7 +46,7 @@ const ( defDB = "bootstrap" defSvcHTTPPort = "9013" - thingsStream = "magistrala.things" + thingsStream = "events.magistrala.things" streamID = "magistrala.bootstrap" ) @@ -142,6 +143,8 @@ func main() { return } + logger.Info("Subscribed to Event Store") + httpServerConfig := server.Config{Port: defSvcHTTPPort} if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err)) @@ -197,14 +200,15 @@ func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db } func subscribeToThingsES(ctx context.Context, svc bootstrap.Service, cfg config, logger *slog.Logger) error { - subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) + subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger) if err != nil { return err } - handler := consumer.NewEventHandler(svc) - - logger.Info("Subscribed to Redis Event Store") - - return subscriber.Subscribe(ctx, handler) + subConfig := events.SubscriberConfig{ + Stream: thingsStream, + Consumer: cfg.ESConsumerName, + Handler: consumer.NewEventHandler(svc), + } + return subscriber.Subscribe(ctx, subConfig) } diff --git a/cmd/lora/main.go b/cmd/lora/main.go index 348029afc9..78105c3e29 100644 --- a/cmd/lora/main.go +++ b/cmd/lora/main.go @@ -23,8 +23,9 @@ import ( mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/lora" "github.com/absmach/magistrala/lora/api" - "github.com/absmach/magistrala/lora/events" + loraevents "github.com/absmach/magistrala/lora/events" "github.com/absmach/magistrala/lora/mqtt" + "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/messaging/brokers" @@ -44,7 +45,7 @@ const ( thingsRMPrefix = "thing" channelsRMPrefix = "channel" connsRMPrefix = "connection" - thingsStream = "magistrala.things" + thingsStream = "events.magistrala.things" ) type config struct { @@ -147,6 +148,8 @@ func main() { return } + logger.Info("Subscribed to Event Store") + hs := httpserver.New(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(cfg.InstanceID), logger) if cfg.SendTelemetry { @@ -198,21 +201,22 @@ func subscribeToLoRaBroker(svc lora.Service, mc mqttpaho.Client, timeout time.Du } func subscribeToThingsES(ctx context.Context, svc lora.Service, cfg config, logger *slog.Logger) error { - subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) + subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger) if err != nil { return err } - handler := events.NewEventHandler(svc) - - logger.Info("Subscribed to Redis Event Store") - - return subscriber.Subscribe(ctx, handler) + subConfig := events.SubscriberConfig{ + Stream: thingsStream, + Consumer: cfg.ESConsumerName, + Handler: loraevents.NewEventHandler(svc), + } + return subscriber.Subscribe(ctx, subConfig) } func newRouteMapRepository(client *redis.Client, prefix string, logger *slog.Logger) lora.RouteMapRepository { logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix)) - return events.NewRouteMapRepository(client, prefix) + return loraevents.NewRouteMapRepository(client, prefix) } func newService(pub messaging.Publisher, rmConn *redis.Client, thingsRMPrefix, channelsRMPrefix, connsRMPrefix string, logger *slog.Logger) lora.Service { diff --git a/cmd/opcua/main.go b/cmd/opcua/main.go index 2f4f63af3e..54607d0dd9 100644 --- a/cmd/opcua/main.go +++ b/cmd/opcua/main.go @@ -23,8 +23,9 @@ import ( "github.com/absmach/magistrala/opcua" "github.com/absmach/magistrala/opcua/api" "github.com/absmach/magistrala/opcua/db" - "github.com/absmach/magistrala/opcua/events" + opcuaevents "github.com/absmach/magistrala/opcua/events" "github.com/absmach/magistrala/opcua/gopcua" + "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" "github.com/absmach/magistrala/pkg/messaging/brokers" brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" @@ -43,7 +44,7 @@ const ( channelsRMPrefix = "channel" connectionRMPrefix = "connection" - thingsStream = "magistrala.things" + thingsStream = "events.magistrala.things" ) type config struct { @@ -142,6 +143,8 @@ func main() { return } + logger.Info("Subscribed to Event Store") + hs := httpserver.New(ctx, httpCancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, cfg.InstanceID), logger) if cfg.SendTelemetry { @@ -181,21 +184,22 @@ func subscribeToStoredSubs(ctx context.Context, sub opcua.Subscriber, cfg opcua. } func subscribeToThingsES(ctx context.Context, svc opcua.Service, cfg config, logger *slog.Logger) error { - subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, thingsStream, cfg.ESConsumerName, logger) + subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger) if err != nil { return err } - handler := events.NewEventHandler(svc) - - logger.Info("Subscribed to Redis Event Store") - - return subscriber.Subscribe(ctx, handler) + subConfig := events.SubscriberConfig{ + Stream: thingsStream, + Consumer: cfg.ESConsumerName, + Handler: opcuaevents.NewEventHandler(svc), + } + return subscriber.Subscribe(ctx, subConfig) } func newRouteMapRepositoy(client *redis.Client, prefix string, logger *slog.Logger) opcua.RouteMapRepository { logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix)) - return events.NewRouteMapRepository(client, prefix) + return opcuaevents.NewRouteMapRepository(client, prefix) } func newService(sub opcua.Subscriber, browser opcua.Browser, thingRM, chanRM, connRM opcua.RouteMapRepository, opcuaConfig opcua.Config, logger *slog.Logger) opcua.Service { diff --git a/pkg/events/events.go b/pkg/events/events.go index f0281cd061..626bef11d3 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -38,12 +38,19 @@ type EventHandler interface { Handle(ctx context.Context, event Event) error } +// SubscriberConfig represents event subscriber configuration. +type SubscriberConfig struct { + Consumer string + Stream string + Handler EventHandler +} + // Subscriber specifies event subscription API. // //go:generate mockery --name Subscriber --output=./mocks --filename subscriber.go --quiet --note "Copyright (c) Abstract Machines" type Subscriber interface { // Subscribe subscribes to the event stream and consumes events. - Subscribe(ctx context.Context, handler EventHandler) error + Subscribe(ctx context.Context, cfg SubscriberConfig) error // Close gracefully closes event subscriber's connection. Close() error diff --git a/pkg/events/mocks/subscriber.go b/pkg/events/mocks/subscriber.go index 38d4002387..4de1ceb50f 100644 --- a/pkg/events/mocks/subscriber.go +++ b/pkg/events/mocks/subscriber.go @@ -34,17 +34,17 @@ func (_m *Subscriber) Close() error { return r0 } -// Subscribe provides a mock function with given fields: ctx, handler -func (_m *Subscriber) Subscribe(ctx context.Context, handler events.EventHandler) error { - ret := _m.Called(ctx, handler) +// Subscribe provides a mock function with given fields: ctx, cfg +func (_m *Subscriber) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { + ret := _m.Called(ctx, cfg) if len(ret) == 0 { panic("no return value specified for Subscribe") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, events.EventHandler) error); ok { - r0 = rf(ctx, handler) + if rf, ok := ret.Get(0).(func(context.Context, events.SubscriberConfig) error); ok { + r0 = rf(ctx, cfg) } else { r0 = ret.Error(0) } diff --git a/pkg/events/nats/publisher_test.go b/pkg/events/nats/publisher_test.go index ef69550644..20086ea552 100644 --- a/pkg/events/nats/publisher_test.go +++ b/pkg/events/nats/publisher_test.go @@ -19,11 +19,10 @@ import ( ) var ( - streamTopic = "test-topic" - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 + eventsChan = make(chan map[string]interface{}) + logger = mglog.NewMock() + errFailed = errors.New("failed") + numEvents = 100 ) type testEvent struct { @@ -56,14 +55,21 @@ func TestPublish(t *testing.T) { publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer publisher.Close() - _, err = nats.NewSubscriber(context.Background(), "http://invaliurl.com", stream, consumer, logger) + _, err = nats.NewSubscriber(context.Background(), "http://invaliurl.com", logger) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer subcriber.Close() - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -124,107 +130,116 @@ func TestPublish(t *testing.T) { } for _, tc := range cases { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - val := int64(receivedEvent["occurred_at"].(float64)) - if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") + t.Run(tc.desc, func(t *testing.T) { + event := testEvent{Data: tc.event} + + err := publisher.Publish(context.Background(), event) + switch tc.err { + case nil: + receivedEvent := <-eventsChan + + val := int64(receivedEvent["occurred_at"].(float64)) + if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { + delete(receivedEvent, "occurred_at") + delete(tc.event, "occurred_at") + } + + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) + default: + assert.ErrorContains(t, err, tc.err.Error()) } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - default: - assert.ErrorContains(t, err, tc.err.Error()) - } + }) } } func TestPubsub(t *testing.T) { - subcases := []struct { - desc string - stream string - consumer string - errorMessage error - handler events.EventHandler + cases := []struct { + desc string + stream string + consumer string + err error + handler events.EventHandler }{ { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to a stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to the same stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - errorMessage: nats.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with an empty consumer", + stream: "", + consumer: "", + err: nats.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - errorMessage: nats.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with a valid consumer", + stream: "", + consumer: consumer, + err: nats.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: "", - errorMessage: nats.ErrEmptyConsumer, - handler: handler{false}, + desc: "Subscribe to a valid stream with an empty consumer", + stream: fmt.Sprintf("events.%s", stream), + consumer: "", + err: nats.ErrEmptyConsumer, + handler: handler{false}, }, { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic+"1"), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to another stream", + stream: fmt.Sprintf("events.%s.%d", stream, 1), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{true}, + desc: "Subscribe to a stream with malformed handler", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{true}, }, } - for _, pc := range subcases { - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, pc.stream, pc.consumer, logger) - if err != nil { - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) + if err != nil { + assert.Equal(t, err, tc.err) - continue - } + return + } - switch err := subcriber.Subscribe(context.Background(), pc.handler); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - default: - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) - } + cfg := events.SubscriberConfig{ + Stream: tc.stream, + Consumer: tc.consumer, + Handler: tc.handler, + } + switch err := subcriber.Subscribe(context.Background(), cfg); { + case err == nil: + assert.Nil(t, err) + default: + assert.Equal(t, err, tc.err) + } - err = subcriber.Close() - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) + err = subcriber.Close() + assert.Nil(t, err) + }) } } @@ -232,10 +247,15 @@ func TestUnavailablePublish(t *testing.T) { publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) + subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) err = pool.Client.PauseContainer(container.Container.ID) diff --git a/pkg/events/nats/setup_test.go b/pkg/events/nats/setup_test.go index 143e4863b9..e539aca537 100644 --- a/pkg/events/nats/setup_test.go +++ b/pkg/events/nats/setup_test.go @@ -52,7 +52,7 @@ func TestMain(m *testing.M) { } if err := pool.Retry(func() error { - _, err = nats.NewSubscriber(context.Background(), natsURL, stream, consumer, logger) + _, err = nats.NewSubscriber(context.Background(), natsURL, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/pkg/events/nats/subscriber.go b/pkg/events/nats/subscriber.go index 9154678fb5..ca99f83123 100644 --- a/pkg/events/nats/subscriber.go +++ b/pkg/events/nats/subscriber.go @@ -18,9 +18,7 @@ import ( "github.com/nats-io/nats.go/jetstream" ) -const ( - maxReconnects = -1 -) +const maxReconnects = -1 var _ events.Subscriber = (*subEventStore)(nil) @@ -47,22 +45,12 @@ var ( ) type subEventStore struct { - conn *nats.Conn - pubsub messaging.PubSub - stream string - consumer string - logger *slog.Logger + conn *nats.Conn + pubsub messaging.PubSub + logger *slog.Logger } -func NewSubscriber(ctx context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - if stream == "" { - return nil, ErrEmptyStream - } - - if consumer == "" { - return nil, ErrEmptyConsumer - } - +func NewSubscriber(ctx context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { conn, err := nats.Connect(url, nats.MaxReconnects(maxReconnects)) if err != nil { return nil, err @@ -82,20 +70,25 @@ func NewSubscriber(ctx context.Context, url, stream, consumer string, logger *sl } return &subEventStore{ - conn: conn, - pubsub: pubsub, - stream: stream, - consumer: consumer, - logger: logger, + conn: conn, + pubsub: pubsub, + logger: logger, }, nil } -func (es *subEventStore) Subscribe(ctx context.Context, handler events.EventHandler) error { +func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { + if cfg.Stream == "" { + return ErrEmptyStream + } + if cfg.Consumer == "" { + return ErrEmptyConsumer + } + subCfg := messaging.SubscriberConfig{ - ID: es.consumer, - Topic: eventsPrefix + "." + es.stream, + ID: cfg.Consumer, + Topic: cfg.Stream, Handler: &eventHandler{ - handler: handler, + handler: cfg.Handler, ctx: ctx, logger: es.logger, }, @@ -134,7 +127,7 @@ func (eh *eventHandler) Handle(msg *messaging.Message) error { } if err := eh.handler.Handle(eh.ctx, event); err != nil { - eh.logger.Warn(fmt.Sprintf("failed to handle redis event: %s", err)) + eh.logger.Warn(fmt.Sprintf("failed to handle nats event: %s", err)) } return nil diff --git a/pkg/events/rabbitmq/publisher_test.go b/pkg/events/rabbitmq/publisher_test.go index 252784a3c0..f14534654b 100644 --- a/pkg/events/rabbitmq/publisher_test.go +++ b/pkg/events/rabbitmq/publisher_test.go @@ -19,11 +19,10 @@ import ( ) var ( - streamTopic = "test-topic" - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 + eventsChan = make(chan map[string]interface{}) + logger = mglog.NewMock() + errFailed = errors.New("failed") + numEvents = 100 ) type testEvent struct { @@ -56,14 +55,21 @@ func TestPublish(t *testing.T) { publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer publisher.Close() - _, err = rabbitmq.NewSubscriber("http://invaliurl.com", stream, consumer, logger) + _, err = rabbitmq.NewSubscriber("http://invaliurl.com", logger) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, stream, consumer, logger) + subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer subcriber.Close() - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -124,108 +130,117 @@ func TestPublish(t *testing.T) { } for _, tc := range cases { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - val := int64(receivedEvent["occurred_at"].(float64)) - if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") + t.Run(tc.desc, func(t *testing.T) { + event := testEvent{Data: tc.event} + + err := publisher.Publish(context.Background(), event) + switch tc.err { + case nil: + receivedEvent := <-eventsChan + + val := int64(receivedEvent["occurred_at"].(float64)) + if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { + delete(receivedEvent, "occurred_at") + delete(tc.event, "occurred_at") + } + + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) + + default: + assert.ErrorContains(t, err, tc.err.Error()) } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - - default: - assert.ErrorContains(t, err, tc.err.Error(), fmt.Sprintf("%s - expected error: %s", tc.desc, tc.err)) - } + }) } } func TestPubsub(t *testing.T) { - subcases := []struct { - desc string - stream string - consumer string - errorMessage error - handler events.EventHandler + cases := []struct { + desc string + stream string + consumer string + err error + handler events.EventHandler }{ { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to a stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to the same stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - errorMessage: rabbitmq.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with an empty consumer", + stream: "", + consumer: "", + err: rabbitmq.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - errorMessage: rabbitmq.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with a valid consumer", + stream: "", + consumer: consumer, + err: rabbitmq.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: "", - errorMessage: rabbitmq.ErrEmptyConsumer, - handler: handler{false}, + desc: "Subscribe to a valid stream with an empty consumer", + stream: fmt.Sprintf("events.%s", stream), + consumer: "", + err: rabbitmq.ErrEmptyConsumer, + handler: handler{false}, }, { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("%s.%s", stream, streamTopic+"1"), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to another stream", + stream: fmt.Sprintf("events.%s.%d", stream, 1), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("%s.%s", stream, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{true}, + desc: "Subscribe to a stream with malformed handler", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{true}, }, } - for _, pc := range subcases { - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, pc.stream, pc.consumer, logger) - if err != nil { - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) + if err != nil { + assert.Equal(t, err, tc.err) - continue - } + return + } - switch err := subcriber.Subscribe(context.Background(), pc.handler); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - default: - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) - } + cfg := events.SubscriberConfig{ + Stream: tc.stream, + Consumer: tc.consumer, + Handler: tc.handler, + } + switch err := subcriber.Subscribe(context.Background(), cfg); { + case err == nil: + assert.Nil(t, err) + default: + assert.Equal(t, err, tc.err) + } - err = subcriber.Close() - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) + err = subcriber.Close() + assert.Nil(t, err) + }) } } @@ -233,10 +248,15 @@ func TestUnavailablePublish(t *testing.T) { publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, stream, consumer, logger) + subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) err = pool.Client.PauseContainer(container.Container.ID) diff --git a/pkg/events/rabbitmq/setup_test.go b/pkg/events/rabbitmq/setup_test.go index 4db3c7c254..dcbf066afd 100644 --- a/pkg/events/rabbitmq/setup_test.go +++ b/pkg/events/rabbitmq/setup_test.go @@ -51,7 +51,7 @@ func TestMain(m *testing.M) { } if err := pool.Retry(func() error { - _, err = rabbitmq.NewSubscriber(rabbitmqURL, stream, consumer, logger) + _, err = rabbitmq.NewSubscriber(rabbitmqURL, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/pkg/events/rabbitmq/subscriber.go b/pkg/events/rabbitmq/subscriber.go index 88d2713f44..bba6b16317 100644 --- a/pkg/events/rabbitmq/subscriber.go +++ b/pkg/events/rabbitmq/subscriber.go @@ -30,22 +30,12 @@ var ( ) type subEventStore struct { - conn *amqp.Connection - pubsub messaging.PubSub - stream string - consumer string - logger *slog.Logger + conn *amqp.Connection + pubsub messaging.PubSub + logger *slog.Logger } -func NewSubscriber(url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - if stream == "" { - return nil, ErrEmptyStream - } - - if consumer == "" { - return nil, ErrEmptyConsumer - } - +func NewSubscriber(url string, logger *slog.Logger) (events.Subscriber, error) { conn, err := amqp.Dial(url) if err != nil { return nil, err @@ -64,20 +54,25 @@ func NewSubscriber(url, stream, consumer string, logger *slog.Logger) (events.Su } return &subEventStore{ - conn: conn, - pubsub: pubsub, - stream: stream, - consumer: consumer, - logger: logger, + conn: conn, + pubsub: pubsub, + logger: logger, }, nil } -func (es *subEventStore) Subscribe(ctx context.Context, handler events.EventHandler) error { +func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { + if cfg.Stream == "" { + return ErrEmptyStream + } + if cfg.Consumer == "" { + return ErrEmptyConsumer + } + subCfg := messaging.SubscriberConfig{ - ID: es.consumer, - Topic: eventsPrefix + "." + es.stream, + ID: cfg.Consumer, + Topic: cfg.Stream, Handler: &eventHandler{ - handler: handler, + handler: cfg.Handler, ctx: ctx, logger: es.logger, }, @@ -116,7 +111,7 @@ func (eh *eventHandler) Handle(msg *messaging.Message) error { } if err := eh.handler.Handle(eh.ctx, event); err != nil { - eh.logger.Warn(fmt.Sprintf("failed to handle redis event: %s", err)) + eh.logger.Warn(fmt.Sprintf("failed to handle rabbitmq event: %s", err)) } return nil diff --git a/pkg/events/redis/publisher.go b/pkg/events/redis/publisher.go index a6f5c90aca..e6a29626c0 100644 --- a/pkg/events/redis/publisher.go +++ b/pkg/events/redis/publisher.go @@ -1,9 +1,6 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !nats && !rabbitmq -// +build !nats,!rabbitmq - package redis import ( @@ -32,7 +29,7 @@ func NewPublisher(ctx context.Context, url, stream string, flushPeriod time.Dura es := &pubEventStore{ client: redis.NewClient(opts), unpublishedEvents: make(chan *redis.XAddArgs, events.MaxUnpublishedEvents), - stream: stream, + stream: eventsPrefix + stream, flushPeriod: flushPeriod, } diff --git a/pkg/events/redis/publisher_test.go b/pkg/events/redis/publisher_test.go index 9bf226dd76..ca1320a4b0 100644 --- a/pkg/events/redis/publisher_test.go +++ b/pkg/events/redis/publisher_test.go @@ -1,9 +1,6 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !nats && !rabbitmq -// +build !nats,!rabbitmq - package redis_test import ( @@ -23,13 +20,12 @@ import ( ) var ( - streamName = "magistrala.eventstest" - consumer = "test-consumer" - streamTopic = "test-topic" - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 + stream = "tests.events" + consumer = "test-consumer" + eventsChan = make(chan map[string]interface{}) + logger = mglog.NewMock() + errFailed = errors.New("failed") + numEvents = 100 ) type testEvent struct { @@ -60,19 +56,26 @@ func TestPublish(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) - _, err = redis.NewPublisher(context.Background(), "http://invaliurl.com", streamName, events.UnpublishedEventsCheckInterval) + _, err = redis.NewPublisher(context.Background(), "http://invaliurl.com", stream, events.UnpublishedEventsCheckInterval) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - publisher, err := redis.NewPublisher(context.Background(), redisURL, streamName, events.UnpublishedEventsCheckInterval) + publisher, err := redis.NewPublisher(context.Background(), redisURL, stream, events.UnpublishedEventsCheckInterval) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer publisher.Close() - _, err = redis.NewSubscriber("http://invaliurl.com", streamName, consumer, logger) + _, err = redis.NewSubscriber("http://invaliurl.com", logger) assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - subcriber, err := redis.NewSubscriber(redisURL, streamName, consumer, logger) + subcriber, err := redis.NewSubscriber(redisURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) + defer subcriber.Close() - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) cases := []struct { @@ -133,30 +136,32 @@ func TestPublish(t *testing.T) { } for _, tc := range cases { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - roa, err := strconv.ParseInt(receivedEvent["occurred_at"].(string), 10, 64) - assert.Nil(t, err, fmt.Sprintf("%s - got unexpected error: %s", tc.desc, err)) - if assert.WithinRange(t, time.Unix(0, roa), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") + t.Run(tc.desc, func(t *testing.T) { + event := testEvent{Data: tc.event} + + err := publisher.Publish(context.Background(), event) + switch tc.err { + case nil: + receivedEvent := <-eventsChan + + roa, err := strconv.ParseInt(receivedEvent["occurred_at"].(string), 10, 64) + assert.Nil(t, err) + if assert.WithinRange(t, time.Unix(0, roa), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { + delete(receivedEvent, "occurred_at") + delete(tc.event, "occurred_at") + } + + assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) + assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) + assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) + assert.Equal(t, tc.event["status"], receivedEvent["status"]) + assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) + assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) + + default: + assert.ErrorContains(t, err, tc.err.Error()) } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - - default: - assert.ErrorContains(t, err, tc.err.Error(), fmt.Sprintf("%s - expected error: %s", tc.desc, tc.err)) - } + }) } } @@ -164,94 +169,104 @@ func TestPubsub(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) - subcases := []struct { - desc string - stream string - consumer string - errorMessage error - handler events.EventHandler + cases := []struct { + desc string + stream string + consumer string + err error + handler events.EventHandler }{ { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("%s.%s", streamName, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to a stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("%s.%s", streamName, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to the same stream", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - errorMessage: redis.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with an empty consumer", + stream: "", + consumer: "", + err: redis.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - errorMessage: redis.ErrEmptyStream, - handler: handler{false}, + desc: "Subscribe to an empty stream with a valid consumer", + stream: "", + consumer: consumer, + err: redis.ErrEmptyStream, + handler: handler{false}, }, { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("%s.%s", streamName, streamTopic), - consumer: "", - errorMessage: redis.ErrEmptyConsumer, - handler: handler{false}, + desc: "Subscribe to a valid stream with an empty consumer", + stream: fmt.Sprintf("events.%s", stream), + consumer: "", + err: redis.ErrEmptyConsumer, + handler: handler{false}, }, { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("%s.%s", streamName, streamTopic+"1"), - consumer: consumer, - errorMessage: nil, - handler: handler{false}, + desc: "Subscribe to another stream", + stream: fmt.Sprintf("events.%s.%d", stream, 1), + consumer: consumer, + err: nil, + handler: handler{false}, }, { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("%s.%s", streamName, streamTopic), - consumer: consumer, - errorMessage: nil, - handler: handler{true}, + desc: "Subscribe to a stream with malformed handler", + stream: fmt.Sprintf("events.%s", stream), + consumer: consumer, + err: nil, + handler: handler{true}, }, } - for _, pc := range subcases { - subcriber, err := redis.NewSubscriber(redisURL, pc.stream, pc.consumer, logger) - if err != nil { - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) - - continue - } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + subcriber, err := redis.NewSubscriber(redisURL, logger) + if err != nil { + assert.Equal(t, err, tc.err) - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) + return + } - switch err := subcriber.Subscribe(context.Background(), pc.handler); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) - default: - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s got expected error: %s - got: %s", pc.desc, pc.errorMessage, err)) - } + cfg := events.SubscriberConfig{ + Stream: tc.stream, + Consumer: tc.consumer, + Handler: tc.handler, + } + switch err := subcriber.Subscribe(context.Background(), cfg); { + case err == nil: + assert.Nil(t, err) + default: + assert.Equal(t, err, tc.err) + } - err = subcriber.Close() - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", pc.desc, err)) + err = subcriber.Close() + assert.Nil(t, err) + }) } } func TestUnavailablePublish(t *testing.T) { - publisher, err := redis.NewPublisher(context.Background(), redisURL, streamName, time.Second) + publisher, err := redis.NewPublisher(context.Background(), redisURL, stream, time.Second) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - subcriber, err := redis.NewSubscriber(redisURL, streamName, consumer, logger) + subcriber, err := redis.NewSubscriber(redisURL, logger) assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - err = subcriber.Subscribe(context.Background(), handler{}) + cfg := events.SubscriberConfig{ + Stream: "events." + stream, + Consumer: consumer, + Handler: handler{}, + } + err = subcriber.Subscribe(context.Background(), cfg) assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) err = pool.Client.PauseContainer(container.Container.ID) diff --git a/pkg/events/redis/setup_test.go b/pkg/events/redis/setup_test.go index 541cb2a3d1..719e0996c3 100644 --- a/pkg/events/redis/setup_test.go +++ b/pkg/events/redis/setup_test.go @@ -1,9 +1,6 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !nats && !rabbitmq -// +build !nats,!rabbitmq - package redis_test import ( diff --git a/pkg/events/redis/subscriber.go b/pkg/events/redis/subscriber.go index 64d1724d2e..910ecca348 100644 --- a/pkg/events/redis/subscriber.go +++ b/pkg/events/redis/subscriber.go @@ -1,9 +1,6 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !nats && !rabbitmq -// +build !nats,!rabbitmq - package redis import ( @@ -17,9 +14,10 @@ import ( ) const ( - eventCount = 100 - exists = "BUSYGROUP Consumer Group name already exists" - group = "magistrala" + eventsPrefix = "events." + eventCount = 100 + exists = "BUSYGROUP Consumer Group name already exists" + group = "magistrala" ) var _ events.Subscriber = (*subEventStore)(nil) @@ -33,36 +31,31 @@ var ( ) type subEventStore struct { - client *redis.Client - stream string - consumer string - logger *slog.Logger + client *redis.Client + logger *slog.Logger } -func NewSubscriber(url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - if stream == "" { - return nil, ErrEmptyStream - } - - if consumer == "" { - return nil, ErrEmptyConsumer - } - +func NewSubscriber(url string, logger *slog.Logger) (events.Subscriber, error) { opts, err := redis.ParseURL(url) if err != nil { return nil, err } return &subEventStore{ - client: redis.NewClient(opts), - stream: stream, - consumer: consumer, - logger: logger, + client: redis.NewClient(opts), + logger: logger, }, nil } -func (es *subEventStore) Subscribe(ctx context.Context, handler events.EventHandler) error { - err := es.client.XGroupCreateMkStream(ctx, es.stream, group, "$").Err() +func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { + if cfg.Stream == "" { + return ErrEmptyStream + } + if cfg.Consumer == "" { + return ErrEmptyConsumer + } + + err := es.client.XGroupCreateMkStream(ctx, cfg.Stream, group, "$").Err() if err != nil && err.Error() != exists { return err } @@ -71,12 +64,12 @@ func (es *subEventStore) Subscribe(ctx context.Context, handler events.EventHand for { msgs, err := es.client.XReadGroup(ctx, &redis.XReadGroupArgs{ Group: group, - Consumer: es.consumer, - Streams: []string{es.stream, ">"}, + Consumer: cfg.Consumer, + Streams: []string{cfg.Stream, ">"}, Count: eventCount, }).Result() if err != nil { - es.logger.Warn(fmt.Sprintf("failed to read from Redis stream: %s", err)) + es.logger.Warn(fmt.Sprintf("failed to read from redis stream: %s", err)) continue } @@ -84,7 +77,7 @@ func (es *subEventStore) Subscribe(ctx context.Context, handler events.EventHand continue } - es.handle(ctx, msgs[0].Messages, handler) + es.handle(ctx, cfg.Stream, msgs[0].Messages, cfg.Handler) } }() @@ -103,7 +96,7 @@ func (re redisEvent) Encode() (map[string]interface{}, error) { return re.Data, nil } -func (es *subEventStore) handle(ctx context.Context, msgs []redis.XMessage, h events.EventHandler) { +func (es *subEventStore) handle(ctx context.Context, stream string, msgs []redis.XMessage, h events.EventHandler) { for _, msg := range msgs { event := redisEvent{ Data: msg.Values, @@ -115,7 +108,7 @@ func (es *subEventStore) handle(ctx context.Context, msgs []redis.XMessage, h ev return } - if err := es.client.XAck(ctx, es.stream, group, msg.ID).Err(); err != nil { + if err := es.client.XAck(ctx, stream, group, msg.ID).Err(); err != nil { es.logger.Warn(fmt.Sprintf("failed to ack redis event: %s", err)) return diff --git a/pkg/events/store/brokers_nats.go b/pkg/events/store/store_nats.go similarity index 76% rename from pkg/events/store/brokers_nats.go rename to pkg/events/store/store_nats.go index 282fc6ad5e..e344253bb2 100644 --- a/pkg/events/store/brokers_nats.go +++ b/pkg/events/store/store_nats.go @@ -28,8 +28,8 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(ctx context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := nats.NewSubscriber(ctx, url, stream, consumer, logger) +func NewSubscriber(ctx context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { + pb, err := nats.NewSubscriber(ctx, url, logger) if err != nil { return nil, err } diff --git a/pkg/events/store/brokers_rabbitmq.go b/pkg/events/store/store_rabbitmq.go similarity index 76% rename from pkg/events/store/brokers_rabbitmq.go rename to pkg/events/store/store_rabbitmq.go index bf895502f2..0af15e0d70 100644 --- a/pkg/events/store/brokers_rabbitmq.go +++ b/pkg/events/store/store_rabbitmq.go @@ -28,8 +28,8 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(_ context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := rabbitmq.NewSubscriber(url, stream, consumer, logger) +func NewSubscriber(_ context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { + pb, err := rabbitmq.NewSubscriber(url, logger) if err != nil { return nil, err } diff --git a/pkg/events/store/brokers_redis.go b/pkg/events/store/store_redis.go similarity index 78% rename from pkg/events/store/brokers_redis.go rename to pkg/events/store/store_redis.go index 711310123e..136d01b794 100644 --- a/pkg/events/store/brokers_redis.go +++ b/pkg/events/store/store_redis.go @@ -28,8 +28,8 @@ func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, er return pb, nil } -func NewSubscriber(_ context.Context, url, stream, consumer string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := redis.NewSubscriber(url, stream, consumer, logger) +func NewSubscriber(_ context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { + pb, err := redis.NewSubscriber(url, logger) if err != nil { return nil, err } From f116e280dd639fceef207f5f343f8b141e177ce4 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:51:34 +0300 Subject: [PATCH 51/71] MG-1887 - Add support for OAuth2.0 (#2103) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/check-generated-files.yml | 4 +- auth.pb.go | 611 +++++++++++--------- auth.proto | 3 + auth/api/grpc/client.go | 20 +- auth/api/grpc/endpoint.go | 1 + auth/api/grpc/requests.go | 7 +- auth/api/grpc/server.go | 11 +- auth/api/http/keys/endpoint_test.go | 5 +- auth/jwt/token_test.go | 196 ++++++- auth/jwt/tokenizer.go | 109 +++- auth/keys.go | 24 +- auth/service.go | 4 + auth/service_test.go | 18 +- auth_grpc.pb.go | 2 +- cmd/auth/main.go | 32 +- cmd/users/main.go | 51 +- docker/.env | 8 + docker/docker-compose.yml | 14 + docker/nginx/nginx-key.conf | 2 +- docker/nginx/nginx-x509.conf | 2 +- go.mod | 4 + go.sum | 6 +- pkg/messaging/message.pb.go | 4 +- pkg/oauth2/doc.go | 6 + pkg/oauth2/google/doc.go | 6 + pkg/oauth2/google/provider.go | 183 ++++++ pkg/oauth2/mocks/provider.go | 205 +++++++ pkg/oauth2/oauth2.go | 83 +++ pkg/sdk/go/groups_test.go | 5 +- pkg/sdk/go/users_test.go | 5 +- users/api/clients.go | 69 ++- users/api/endpoint_test.go | 5 +- users/api/logging.go | 20 + users/api/metrics.go | 11 + users/api/transport.go | 5 +- users/clients.go | 6 + users/events/events.go | 17 + users/events/streams.go | 21 + users/mocks/service.go | 34 ++ users/postgres/init.go | 8 + users/service.go | 56 +- users/service_test.go | 161 +++++- users/tracing/tracing.go | 14 + 43 files changed, 1672 insertions(+), 386 deletions(-) create mode 100644 pkg/oauth2/doc.go create mode 100644 pkg/oauth2/google/doc.go create mode 100644 pkg/oauth2/google/provider.go create mode 100644 pkg/oauth2/mocks/provider.go create mode 100644 pkg/oauth2/oauth2.go diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index c50e121ce9..e60ad814f3 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -62,8 +62,8 @@ jobs: - name: Set up protoc if: steps.changes.outputs.proto == 'true' run: | - PROTOC_VERSION=25.1 - PROTOC_GEN_VERSION=v1.31.0 + PROTOC_VERSION=25.3 + PROTOC_GEN_VERSION=v1.32.0 PROTOC_GRPC_VERSION=v1.3.0 # Download and install protoc diff --git a/auth.pb.go b/auth.pb.go index fc9b881932..6d9ace3530 100644 --- a/auth.pb.go +++ b/auth.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.3 // source: auth.proto package magistrala @@ -204,9 +204,12 @@ type IssueReq struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` - Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + OauthProvider string `protobuf:"bytes,4,opt,name=oauth_provider,json=oauthProvider,proto3" json:"oauth_provider,omitempty"` + OauthAccessToken string `protobuf:"bytes,5,opt,name=oauth_access_token,json=oauthAccessToken,proto3" json:"oauth_access_token,omitempty"` + OauthRefreshToken string `protobuf:"bytes,6,opt,name=oauth_refresh_token,json=oauthRefreshToken,proto3" json:"oauth_refresh_token,omitempty"` } func (x *IssueReq) Reset() { @@ -262,6 +265,27 @@ func (x *IssueReq) GetType() uint32 { return 0 } +func (x *IssueReq) GetOauthProvider() string { + if x != nil { + return x.OauthProvider + } + return "" +} + +func (x *IssueReq) GetOauthAccessToken() string { + if x != nil { + return x.OauthAccessToken + } + return "" +} + +func (x *IssueReq) GetOauthRefreshToken() string { + if x != nil { + return x.OauthRefreshToken + } + return "" +} + type RefreshReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1877,217 +1901,162 @@ var file_auth_proto_rawDesc = []byte{ 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, - 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, - 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, - 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, - 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, - 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, - 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, - 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, - 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, - 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, - 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, - 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, - 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0xec, 0x01, 0x0a, 0x08, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, + 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, + 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, - 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, + 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, + 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, + 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, - 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, @@ -2095,88 +2064,152 @@ var file_auth_proto_rawDesc = []byte{ 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, - 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, - 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, - 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, - 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, - 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, - 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, - 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, - 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, - 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, - 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, - 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, + 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, + 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, + 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, + 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, + 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, + 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, + 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, + 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, + 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, + 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, + 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, + 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, - 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/auth.proto b/auth.proto index 3b370b995b..114b25409d 100644 --- a/auth.proto +++ b/auth.proto @@ -57,6 +57,9 @@ message IssueReq { string user_id = 1; optional string domain_id = 2; uint32 type = 3; + string oauth_provider = 4; + string oauth_access_token = 5; + string oauth_refresh_token = 6; } message RefreshReq { diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index 566a47bc34..4b0c8f8d33 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -174,7 +174,16 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() - res, err := client.issue(ctx, issueReq{userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.Type)}) + res, err := client.issue(ctx, issueReq{ + userID: req.GetUserId(), + domainID: req.GetDomainId(), + keyType: auth.KeyType(req.GetType()), + oauthToken: auth.OAuthToken{ + Provider: req.GetOauthProvider(), + AccessToken: req.GetOauthAccessToken(), + RefreshToken: req.GetOauthRefreshToken(), + }, + }) if err != nil { return &magistrala.Token{}, decodeError(err) } @@ -183,7 +192,14 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(issueReq) - return &magistrala.IssueReq{UserId: req.userID, DomainId: &req.domainID, Type: uint32(req.keyType)}, nil + return &magistrala.IssueReq{ + UserId: req.userID, + DomainId: &req.domainID, + Type: uint32(req.keyType), + OauthProvider: req.oauthToken.Provider, + OauthAccessToken: req.oauthToken.AccessToken, + OauthRefreshToken: req.oauthToken.RefreshToken, + }, nil } func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { diff --git a/auth/api/grpc/endpoint.go b/auth/api/grpc/endpoint.go index c691ea732e..f2db6b6530 100644 --- a/auth/api/grpc/endpoint.go +++ b/auth/api/grpc/endpoint.go @@ -21,6 +21,7 @@ func issueEndpoint(svc auth.Service) endpoint.Endpoint { Type: req.keyType, User: req.userID, Domain: req.domainID, + OAuth: req.oauthToken, } tkn, err := svc.Issue(ctx, "", key) if err != nil { diff --git a/auth/api/grpc/requests.go b/auth/api/grpc/requests.go index 7df318cee2..057d82c01f 100644 --- a/auth/api/grpc/requests.go +++ b/auth/api/grpc/requests.go @@ -21,9 +21,10 @@ func (req identityReq) validate() error { } type issueReq struct { - userID string - domainID string // optional - keyType auth.KeyType + userID string + domainID string // optional + keyType auth.KeyType + oauthToken auth.OAuthToken } func (req issueReq) validate() error { diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index 7af9e075a5..1bac27e1fd 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -273,7 +273,16 @@ func (s *grpcServer) ListPermissions(ctx context.Context, req *magistrala.ListPe func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(*magistrala.IssueReq) - return issueReq{userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.GetType())}, nil + return issueReq{ + userID: req.GetUserId(), + domainID: req.GetDomainId(), + keyType: auth.KeyType(req.GetType()), + oauthToken: auth.OAuthToken{ + Provider: req.GetOauthProvider(), + AccessToken: req.GetOauthAccessToken(), + RefreshToken: req.GetOauthRefreshToken(), + }, + }, nil } func decodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 0f2ec5eba8..7721124b8d 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -21,6 +21,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mglog "github.com/absmach/magistrala/logger" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -72,7 +73,9 @@ func newService() (auth.Service, *mocks.KeyRepository) { drepo := new(mocks.DomainsRepository) idProvider := uuid.NewMock() - t := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + t := jwt.New([]byte(secret), provider) return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), krepo } diff --git a/auth/jwt/token_test.go b/auth/jwt/token_test.go index 4996e1d24f..9a15e0483d 100644 --- a/auth/jwt/token_test.go +++ b/auth/jwt/token_test.go @@ -4,18 +4,24 @@ package jwt_test import ( + "context" "fmt" + "strings" "testing" "time" "github.com/absmach/magistrala/auth" authjwt "github.com/absmach/magistrala/auth/jwt" + "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) const ( @@ -31,18 +37,6 @@ var ( reposecret = []byte("test") ) -func key() auth.Key { - exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) - return auth.Key{ - ID: "66af4a67-3823-438a-abd7-efdb613eaef6", - Type: auth.AccessKey, - Issuer: "magistrala.auth", - Subject: "66af4a67-3823-438a-abd7-efdb613eaef6", - IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: exp, - } -} - func newToken(issuerName string, key auth.Key) string { builder := jwt.NewBuilder() builder. @@ -62,7 +56,9 @@ func newToken(issuerName string, key auth.Key) string { } func TestIssue(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) cases := []struct { desc string @@ -74,6 +70,24 @@ func TestIssue(t *testing.T) { key: key(), err: nil, }, + { + desc: "issue token with OAuth token", + key: auth.Key{ + ID: testsutil.GenerateUUID(t), + Type: auth.AccessKey, + Subject: testsutil.GenerateUUID(t), + User: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: time.Now().Add(10 * time.Minute).Round(time.Second), + OAuth: auth.OAuthToken{ + Provider: "test", + AccessToken: strings.Repeat("a", 1024), + RefreshToken: strings.Repeat("b", 1024), + }, + }, + err: nil, + }, } for _, tc := range cases { @@ -86,7 +100,9 @@ func TestIssue(t *testing.T) { } func TestParse(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) token, err := tokenizer.Issue(key()) require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) @@ -156,3 +172,155 @@ func TestParse(t *testing.T) { } } } + +func TestParseOAuthToken(t *testing.T) { + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) + + validKey := oauthKey(t) + invalidKey := oauthKey(t) + invalidKey.OAuth.Provider = "invalid" + + cases := []struct { + desc string + token auth.Key + issuedToken string + key auth.Key + validateErr error + refreshToken oauth2.Token + refreshErr error + err error + }{ + { + desc: "parse valid key", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: nil, + refreshErr: nil, + err: nil, + }, + { + desc: "parse invalid key but refreshed", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: svcerr.ErrAuthentication, + refreshToken: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + refreshErr: nil, + err: nil, + }, + { + desc: "parse invalid key but not refreshed", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: svcerr.ErrAuthentication, + refreshToken: oauth2.Token{}, + refreshErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with different provider", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", "b"), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid access token", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", 123, "b"), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid refresh token", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", 123), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid provider", + issuedToken: invalidOauthToken(t, invalidKey, "test", "a", "b"), + err: svcerr.ErrAuthentication, + }, + } + + for _, tc := range cases { + tokenCall := provider.On("Name").Return("test") + tokenCall1 := provider.On("Validate", context.Background(), mock.Anything).Return(tc.validateErr) + tokenCall2 := provider.On("Refresh", context.Background(), mock.Anything).Return(tc.refreshToken, tc.refreshErr) + if tc.issuedToken == "" { + var err error + tc.issuedToken, err = tokenizer.Issue(tc.token) + require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) + } + key, err := tokenizer.Parse(tc.issuedToken) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key)) + } + tokenCall.Unset() + tokenCall1.Unset() + tokenCall2.Unset() + } +} + +func key() auth.Key { + exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) + return auth.Key{ + ID: "66af4a67-3823-438a-abd7-efdb613eaef6", + Type: auth.AccessKey, + Issuer: "magistrala.auth", + Subject: "66af4a67-3823-438a-abd7-efdb613eaef6", + IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: exp, + } +} + +func oauthKey(t *testing.T) auth.Key { + return auth.Key{ + ID: testsutil.GenerateUUID(t), + Type: auth.AccessKey, + Issuer: "magistrala.auth", + Subject: testsutil.GenerateUUID(t), + User: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: time.Now().UTC().Add(10 * time.Minute).Round(time.Second), + OAuth: auth.OAuthToken{ + Provider: "test", + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + } +} + +func invalidOauthToken(t *testing.T, key auth.Key, provider, accessToken, refreshToken interface{}) string { + builder := jwt.NewBuilder() + builder. + Issuer(issuerName). + IssuedAt(key.IssuedAt). + Subject(key.Subject). + Claim(tokenType, key.Type). + Expiration(key.ExpiresAt) + builder.Claim(userField, key.User) + builder.Claim(domainField, key.Domain) + if provider != nil { + builder.Claim("oauth_provider", provider) + if accessToken != nil { + builder.Claim(provider.(string), map[string]interface{}{"access_token": accessToken}) + } + if refreshToken != nil { + builder.Claim(provider.(string), map[string]interface{}{"refresh_token": refreshToken}) + } + } + if key.ID != "" { + builder.JwtID(key.ID) + } + tkn, err := builder.Build() + require.Nil(t, err, fmt.Sprintf("building token expected to succeed: %s", err)) + signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, reposecret)) + require.Nil(t, err, fmt.Sprintf("signing token expected to succeed: %s", err)) + return string(signedTkn) +} diff --git a/auth/jwt/tokenizer.go b/auth/jwt/tokenizer.go index b3fc92e63e..5627d0609a 100644 --- a/auth/jwt/tokenizer.go +++ b/auth/jwt/tokenizer.go @@ -12,6 +12,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" ) @@ -32,29 +33,41 @@ var ( ErrValidateJWTToken = errors.New("failed to validate jwt token") // ErrJSONHandle indicates an error in handling JSON. ErrJSONHandle = errors.New("failed to perform operation JSON") + + // errInvalidProvider indicates an invalid OAuth2.0 provider. + errInvalidProvider = errors.New("invalid OAuth2.0 provider") ) const ( - issuerName = "magistrala.auth" - tokenType = "type" - userField = "user" - domainField = "domain" + issuerName = "magistrala.auth" + tokenType = "type" + userField = "user" + domainField = "domain" + oauthProviderField = "oauth_provider" + oauthAccessTokenField = "access_token" + oauthRefreshTokenField = "refresh_token" ) type tokenizer struct { - secret []byte + secret []byte + providers map[string]oauth2.Provider } var _ auth.Tokenizer = (*tokenizer)(nil) // NewRepository instantiates an implementation of Token repository. -func New(secret []byte) auth.Tokenizer { +func New(secret []byte, providers ...oauth2.Provider) auth.Tokenizer { + providersMap := make(map[string]oauth2.Provider) + for _, provider := range providers { + providersMap[provider.Name()] = provider + } return &tokenizer{ - secret: secret, + secret: secret, + providers: providersMap, } } -func (repo *tokenizer) Issue(key auth.Key) (string, error) { +func (tok *tokenizer) Issue(key auth.Key) (string, error) { builder := jwt.NewBuilder() builder. Issuer(issuerName). @@ -64,6 +77,19 @@ func (repo *tokenizer) Issue(key auth.Key) (string, error) { Expiration(key.ExpiresAt) builder.Claim(userField, key.User) builder.Claim(domainField, key.Domain) + + if key.OAuth.Provider != "" { + provider, ok := tok.providers[key.OAuth.Provider] + if !ok { + return "", errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + builder.Claim(oauthProviderField, provider.Name()) + builder.Claim(provider.Name(), map[string]interface{}{ + oauthAccessTokenField: key.OAuth.AccessToken, + oauthRefreshTokenField: key.OAuth.RefreshToken, + }) + } + if key.ID != "" { builder.JwtID(key.ID) } @@ -71,18 +97,18 @@ func (repo *tokenizer) Issue(key auth.Key) (string, error) { if err != nil { return "", errors.Wrap(svcerr.ErrAuthentication, err) } - signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, repo.secret)) + signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, tok.secret)) if err != nil { return "", errors.Wrap(ErrSignJWT, err) } return string(signedTkn), nil } -func (repo *tokenizer) Parse(token string) (auth.Key, error) { +func (tok *tokenizer) Parse(token string) (auth.Key, error) { tkn, err := jwt.Parse( []byte(token), jwt.WithValidate(true), - jwt.WithKey(jwa.HS512, repo.secret), + jwt.WithKey(jwa.HS512, tok.secret), ) if err != nil { if errors.Contains(err, errJWTExpiryKey) { @@ -125,5 +151,66 @@ func (repo *tokenizer) Parse(token string) (auth.Key, error) { key.Subject = tkn.Subject() key.IssuedAt = tkn.IssuedAt() key.ExpiresAt = tkn.Expiration() + + oauthProvider, ok := tkn.Get(oauthProviderField) + if ok { + provider, ok := oauthProvider.(string) + if !ok { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + if provider != "" { + prov, ok := tok.providers[provider] + if !ok { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + key.OAuth.Provider = prov.Name() + + key, err = parseOAuthToken(context.Background(), prov, tkn, key) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + + return key, nil + } + } + + return key, nil +} + +func parseOAuthToken(ctx context.Context, provider oauth2.Provider, token jwt.Token, key auth.Key) (auth.Key, error) { + oauthToken, ok := token.Get(provider.Name()) + if ok { + claims, ok := oauthToken.(map[string]interface{}) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid claims for %s token", provider.Name())) + } + accessToken, ok := claims[oauthAccessTokenField].(string) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid access token claim for %s token", provider.Name())) + } + refreshToken, ok := claims[oauthRefreshTokenField].(string) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid refresh token claim for %s token", provider.Name())) + } + + switch provider.Validate(ctx, accessToken) { + case nil: + key.OAuth.AccessToken = accessToken + default: + token, err := provider.Refresh(ctx, refreshToken) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + key.OAuth.AccessToken = token.AccessToken + key.OAuth.RefreshToken = token.RefreshToken + + return key, nil + } + + key.OAuth.RefreshToken = refreshToken + + return key, nil + } + return key, nil } diff --git a/auth/keys.go b/auth/keys.go index 030852e1ba..b5801b0443 100644 --- a/auth/keys.go +++ b/auth/keys.go @@ -58,16 +58,24 @@ func (kt KeyType) String() string { } } +// OAuthToken represents OAuth token. +type OAuthToken struct { + Provider string `json:"provider,omitempty"` + AccessToken string `json:"access_token,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` +} + // Key represents API key. type Key struct { - ID string `json:"id,omitempty"` - Type KeyType `json:"type,omitempty"` - Issuer string `json:"issuer,omitempty"` - Subject string `json:"subject,omitempty"` // user ID - User string `json:"user,omitempty"` - Domain string `json:"domain,omitempty"` // domain user ID - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` + ID string `json:"id,omitempty"` + Type KeyType `json:"type,omitempty"` + Issuer string `json:"issuer,omitempty"` + Subject string `json:"subject,omitempty"` // user ID + User string `json:"user,omitempty"` + Domain string `json:"domain,omitempty"` // domain user ID + IssuedAt time.Time `json:"issued_at,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + OAuth OAuthToken `json:"oauth,omitempty"` } func (key Key) String() string { diff --git a/auth/service.go b/auth/service.go index 0626036c6e..cdead70227 100644 --- a/auth/service.go +++ b/auth/service.go @@ -428,6 +428,10 @@ func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token key.User = k.User key.Type = AccessKey + key.OAuth.Provider = k.OAuth.Provider + key.OAuth.AccessToken = k.OAuth.AccessToken + key.OAuth.RefreshToken = k.OAuth.RefreshToken + key.Subject, err = svc.checkUserDomain(ctx, key) if err != nil { return Token{}, errors.Wrap(svcerr.ErrAuthorization, err) diff --git a/auth/service_test.go b/auth/service_test.go index 6e16c59589..f46a5246c5 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -69,7 +70,9 @@ func newService() (auth.Service, string) { drepo = new(mocks.DomainsRepository) idProvider := uuid.NewMock() - t := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + t := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -86,7 +89,10 @@ func newService() (auth.Service, string) { func TestIssue(t *testing.T) { svc, accessToken := newService() - n := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + n := jwt.New([]byte(secret), provider) + apikey := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -627,7 +633,9 @@ func TestIdentify(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) repocall4.Unset() - te := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + te := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -721,7 +729,9 @@ func TestAuthorize(t *testing.T) { repocall2.Unset() repocall3.Unset() - te := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + te := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), diff --git a/auth_grpc.pb.go b/auth_grpc.pb.go index d02da3e57d..c60c51381c 100644 --- a/auth_grpc.pb.go +++ b/auth_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.1 +// - protoc v4.25.3 // source: auth.proto package magistrala diff --git a/cmd/auth/main.go b/cmd/auth/main.go index bacbeccd9c..7349685bbf 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -30,6 +30,8 @@ import ( grpcserver "github.com/absmach/magistrala/internal/server/grpc" httpserver "github.com/absmach/magistrala/internal/server/http" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/oauth2" + "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/uuid" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/authzed-go/v1" @@ -44,13 +46,14 @@ import ( ) const ( - svcName = "auth" - envPrefixHTTP = "MG_AUTH_HTTP_" - envPrefixGrpc = "MG_AUTH_GRPC_" - envPrefixDB = "MG_AUTH_DB_" - defDB = "auth" - defSvcHTTPPort = "8180" - defSvcGRPCPort = "8181" + svcName = "auth" + envPrefixHTTP = "MG_AUTH_HTTP_" + envPrefixGrpc = "MG_AUTH_GRPC_" + envPrefixDB = "MG_AUTH_DB_" + envPrefixGoogle = "MG_GOOGLE_" + defDB = "auth" + defSvcHTTPPort = "8180" + defSvcGRPCPort = "8181" ) type config struct { @@ -127,7 +130,15 @@ func main() { return } - svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient) + oauthConfig := oauth2.Config{} + if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + oauthProvider := google.NewProvider(oauthConfig, "", "") + + svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient, oauthProvider) httpServerConfig := server.Config{Port: defSvcHTTPPort} if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { @@ -201,13 +212,14 @@ func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, sch return nil } -func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { +func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental, providers ...oauth2.Provider) auth.Service { database := postgres.NewDatabase(db, dbConfig, tracer) keysRepo := apostgres.New(database) domainsRepo := apostgres.NewDomainRepository(database) pa := spicedb.NewPolicyAgent(spicedbClient, logger) idProvider := uuid.New() - t := jwt.New([]byte(cfg.SecretKey)) + + t := jwt.New([]byte(cfg.SecretKey), providers...) svc := auth.New(keysRepo, domainsRepo, idProvider, t, pa, cfg.AccessDuration, cfg.RefreshDuration, cfg.InvitationDuration) svc = api.LoggingMiddleware(svc, logger) diff --git a/cmd/users/main.go b/cmd/users/main.go index 98a9437541..ae84549b94 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -34,6 +34,8 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/oauth2" + googleoauth "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" capi "github.com/absmach/magistrala/users/api" @@ -50,29 +52,32 @@ import ( ) const ( - svcName = "users" - envPrefixDB = "MG_USERS_DB_" - envPrefixHTTP = "MG_USERS_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "users" - defSvcHTTPPort = "9002" + svcName = "users" + envPrefixDB = "MG_USERS_DB_" + envPrefixHTTP = "MG_USERS_HTTP_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixGoogle = "MG_GOOGLE_" + defDB = "users" + defSvcHTTPPort = "9002" streamID = "magistrala.users" ) type config struct { - LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"` - AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"` - AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"` - PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"` - ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"` - JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_USERS_INSTANCE_ID" envDefault:""` - ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - SelfRegister bool `env:"MG_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` - PassRegex *regexp.Regexp + LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"` + AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"` + AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"` + PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"` + ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"` + JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"MG_USERS_INSTANCE_ID" envDefault:""` + ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + SelfRegister bool `env:"MG_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` + OAuthUIRedirectURL string `env:"MG_OAUTH_UI_REDIRECT_URL" envDefault:"http://localhost:9095/domains"` + OAuthUIErrorURL string `env:"MG_OAUTH_UI_ERROR_URL" envDefault:"http://localhost:9095/error"` + PassRegex *regexp.Regexp } func main() { @@ -172,8 +177,16 @@ func main() { return } + oauthConfig := oauth2.Config{} + if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + oauthProvider := googleoauth.NewProvider(oauthConfig, cfg.OAuthUIRedirectURL, cfg.OAuthUIErrorURL) + mux := chi.NewRouter() - httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID), logger) + httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID, oauthProvider), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/docker/.env b/docker/.env index 2f6910de42..5860920b3e 100644 --- a/docker/.env +++ b/docker/.env @@ -165,6 +165,8 @@ MG_USERS_DB_SSL_ROOT_CERT= MG_USERS_RESET_PWD_TEMPLATE=users.tmpl MG_USERS_INSTANCE_ID= MG_USERS_ALLOW_SELF_REGISTER=true +MG_OAUTH_UI_REDIRECT_URL="http://localhost:9095/domains" +MG_OAUTH_UI_ERROR_URL="http://localhost:9095/error" ### Email utility MG_EMAIL_HOST=smtp.mailtrap.io @@ -175,6 +177,12 @@ MG_EMAIL_FROM_ADDRESS=from@example.com MG_EMAIL_FROM_NAME=Example MG_EMAIL_TEMPLATE=email.tmpl +### Google OAuth2 +MG_GOOGLE_CLIENT_ID= +MG_GOOGLE_CLIENT_SECRET= +MG_GOOGLE_REDIRECT_URL= +MG_GOOGLE_STATE= + ### Things MG_THINGS_LOG_LEVEL=debug MG_THINGS_STANDALONE_ID= diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 93781c863e..2fd6152613 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -125,6 +125,10 @@ services: MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} MG_AUTH_ADAPTER_INSTANCE_ID: ${MG_AUTH_ADAPTER_INSTANCE_ID} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} ports: - ${MG_AUTH_HTTP_PORT}:${MG_AUTH_HTTP_PORT} - ${MG_AUTH_GRPC_PORT}:${MG_AUTH_GRPC_PORT} @@ -441,6 +445,12 @@ services: MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} + MG_OAUTH_UI_REDIRECT_URL: ${MG_OAUTH_UI_REDIRECT_URL} + MG_OAUTH_UI_ERROR_URL: ${MG_OAUTH_UI_ERROR_URL} ports: - ${MG_USERS_HTTP_PORT}:${MG_USERS_HTTP_PORT} networks: @@ -725,6 +735,10 @@ services: MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index b33a0667b0..f561229a9b 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -120,7 +120,7 @@ http { } # Proxy pass to users service - location ~ ^/(users|groups|password|authorize) { + location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index 69cc38c759..49f0170e60 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -129,7 +129,7 @@ http { } # Proxy pass to users service - location ~ ^/(users|groups|password|authorize) { + location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; diff --git a/go.mod b/go.mod index 6f3bb409ed..fb5e018e49 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( go.opentelemetry.io/otel/trace v1.22.0 golang.org/x/crypto v0.19.0 golang.org/x/net v0.21.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac @@ -66,6 +67,8 @@ require ( ) require ( + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -179,6 +182,7 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 162ef1973c..f1186c3a31 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -170,6 +169,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -661,8 +661,8 @@ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/messaging/message.pb.go b/pkg/messaging/message.pb.go index 971df627ac..4767c3ecec 100644 --- a/pkg/messaging/message.pb.go +++ b/pkg/messaging/message.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.3 // source: pkg/messaging/message.proto package messaging diff --git a/pkg/oauth2/doc.go b/pkg/oauth2/doc.go new file mode 100644 index 0000000000..2d7e006f53 --- /dev/null +++ b/pkg/oauth2/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package oauth2 contains the domain concept definitions needed to support +// Magistrala ui service OAuth2 functionality. +package oauth2 diff --git a/pkg/oauth2/google/doc.go b/pkg/oauth2/google/doc.go new file mode 100644 index 0000000000..74f7ada5e4 --- /dev/null +++ b/pkg/oauth2/google/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package google contains the domain concept definitions needed to support +// Magistrala services for Google OAuth2 functionality. +package google diff --git a/pkg/oauth2/google/provider.go b/pkg/oauth2/google/provider.go new file mode 100644 index 0000000000..daba42b6e4 --- /dev/null +++ b/pkg/oauth2/google/provider.go @@ -0,0 +1,183 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package google + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + mfclients "github.com/absmach/magistrala/pkg/clients" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" + "golang.org/x/oauth2" + googleoauth2 "golang.org/x/oauth2/google" +) + +const ( + providerName = "google" + defTimeout = 1 * time.Minute + userInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + tokenInfoURL = "https://oauth2.googleapis.com/tokeninfo?access_token=" +) + +var scopes = []string{ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", +} + +var _ mgoauth2.Provider = (*config)(nil) + +type config struct { + config *oauth2.Config + state string + uiRedirectURL string + errorURL string +} + +// NewProvider returns a new Google OAuth provider. +func NewProvider(cfg mgoauth2.Config, uiRedirectURL, errorURL string) mgoauth2.Provider { + return &config{ + config: &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Endpoint: googleoauth2.Endpoint, + RedirectURL: cfg.RedirectURL, + Scopes: scopes, + }, + state: cfg.State, + uiRedirectURL: uiRedirectURL, + errorURL: errorURL, + } +} + +func (cfg *config) Name() string { + return providerName +} + +func (cfg *config) State() string { + return cfg.state +} + +func (cfg *config) RedirectURL() string { + return cfg.uiRedirectURL +} + +func (cfg *config) ErrorURL() string { + return cfg.errorURL +} + +func (cfg *config) IsEnabled() bool { + return cfg.config.ClientID != "" && cfg.config.ClientSecret != "" +} + +func (cfg *config) UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) { + token, err := cfg.config.Exchange(ctx, code) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + if token.RefreshToken == "" { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + resp, err := http.Get(userInfoURL + url.QueryEscape(token.AccessToken)) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + + var user struct { + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Picture string `json:"picture"` + } + if err := json.Unmarshal(data, &user); err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + + if user.ID == "" || user.Name == "" || user.Email == "" { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + client := mfclients.Client{ + ID: user.ID, + Name: user.Name, + Credentials: mfclients.Credentials{ + Identity: user.Email, + }, + Metadata: map[string]interface{}{ + "oauth_provider": providerName, + "profile_picture": user.Picture, + }, + Status: mfclients.EnabledStatus, + } + + return client, *token, nil +} + +func (cfg *config) Validate(ctx context.Context, token string) error { + client := &http.Client{ + Timeout: defTimeout, + } + req, err := http.NewRequest(http.MethodGet, tokenInfoURL+token, http.NoBody) + if err != nil { + return svcerr.ErrAuthentication + } + res, err := client.Do(req) + if err != nil { + return svcerr.ErrAuthentication + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return svcerr.ErrAuthentication + } + + return nil +} + +func (cfg *config) Refresh(ctx context.Context, token string) (oauth2.Token, error) { + payload := strings.NewReader(fmt.Sprintf("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", token, cfg.config.ClientID, cfg.config.ClientSecret)) + client := &http.Client{ + Timeout: defTimeout, + } + req, err := http.NewRequest(http.MethodPost, cfg.config.Endpoint.TokenURL, payload) + if err != nil { + return oauth2.Token{}, err + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := client.Do(req) + if err != nil { + return oauth2.Token{}, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return oauth2.Token{}, err + } + var tokenData oauth2.Token + if err := json.Unmarshal(body, &tokenData); err != nil { + return oauth2.Token{}, err + } + tokenData.RefreshToken = token + + return tokenData, nil +} diff --git a/pkg/oauth2/mocks/provider.go b/pkg/oauth2/mocks/provider.go new file mode 100644 index 0000000000..ff3ad9fa69 --- /dev/null +++ b/pkg/oauth2/mocks/provider.go @@ -0,0 +1,205 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + clients "github.com/absmach/magistrala/pkg/clients" + + mock "github.com/stretchr/testify/mock" + + xoauth2 "golang.org/x/oauth2" +) + +// Provider is an autogenerated mock type for the Provider type +type Provider struct { + mock.Mock +} + +// ErrorURL provides a mock function with given fields: +func (_m *Provider) ErrorURL() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ErrorURL") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// IsEnabled provides a mock function with given fields: +func (_m *Provider) IsEnabled() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsEnabled") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *Provider) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// RedirectURL provides a mock function with given fields: +func (_m *Provider) RedirectURL() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for RedirectURL") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Refresh provides a mock function with given fields: ctx, token +func (_m *Provider) Refresh(ctx context.Context, token string) (xoauth2.Token, error) { + ret := _m.Called(ctx, token) + + if len(ret) == 0 { + panic("no return value specified for Refresh") + } + + var r0 xoauth2.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (xoauth2.Token, error)); ok { + return rf(ctx, token) + } + if rf, ok := ret.Get(0).(func(context.Context, string) xoauth2.Token); ok { + r0 = rf(ctx, token) + } else { + r0 = ret.Get(0).(xoauth2.Token) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, token) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// State provides a mock function with given fields: +func (_m *Provider) State() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for State") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// UserDetails provides a mock function with given fields: ctx, code +func (_m *Provider) UserDetails(ctx context.Context, code string) (clients.Client, xoauth2.Token, error) { + ret := _m.Called(ctx, code) + + if len(ret) == 0 { + panic("no return value specified for UserDetails") + } + + var r0 clients.Client + var r1 xoauth2.Token + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, xoauth2.Token, error)); ok { + return rf(ctx, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { + r0 = rf(ctx, code) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) xoauth2.Token); ok { + r1 = rf(ctx, code) + } else { + r1 = ret.Get(1).(xoauth2.Token) + } + + if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { + r2 = rf(ctx, code) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Validate provides a mock function with given fields: ctx, token +func (_m *Provider) Validate(ctx context.Context, token string) error { + ret := _m.Called(ctx, token) + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, token) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *Provider { + mock := &Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/oauth2/oauth2.go b/pkg/oauth2/oauth2.go new file mode 100644 index 0000000000..0a9bf00e9f --- /dev/null +++ b/pkg/oauth2/oauth2.go @@ -0,0 +1,83 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package oauth2 + +import ( + "context" + "errors" + + mfclients "github.com/absmach/magistrala/pkg/clients" + "golang.org/x/oauth2" +) + +// State is the state of the OAuth2 flow. +type State uint8 + +const ( + // SignIn is the state for the sign-in flow. + SignIn State = iota + // SignUp is the state for the sign-up flow. + SignUp +) + +func (s State) String() string { + switch s { + case SignIn: + return "signin" + case SignUp: + return "signup" + default: + return "unknown" + } +} + +// ToState converts string value to a valid OAuth2 state. +func ToState(state string) (State, error) { + switch state { + case "signin": + return SignIn, nil + case "signup": + return SignUp, nil + } + + return State(0), errors.New("invalid state") +} + +// Config is the configuration for the OAuth2 provider. +type Config struct { + ClientID string `env:"CLIENT_ID" envDefault:""` + ClientSecret string `env:"CLIENT_SECRET" envDefault:""` + State string `env:"STATE" envDefault:""` + RedirectURL string `env:"REDIRECT_URL" envDefault:""` +} + +// Provider is an interface that provides the OAuth2 flow for a specific provider +// (e.g. Google, GitHub, etc.) +// +//go:generate mockery --name Provider --output=./mocks --filename provider.go --quiet --note "Copyright (c) Abstract Machines" +type Provider interface { + // Name returns the name of the OAuth2 provider. + Name() string + + // State returns the current state for the OAuth2 flow. + State() string + + // RedirectURL returns the URL to redirect the user to after completing the OAuth2 flow. + RedirectURL() string + + // ErrorURL returns the URL to redirect the user to in case of an error during the OAuth2 flow. + ErrorURL() string + + // IsEnabled checks if the OAuth2 provider is enabled. + IsEnabled() bool + + // UserDetails retrieves the user's details and OAuth tokens from the OAuth2 provider. + UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) + + // Validate checks the validity of the access token. + Validate(ctx context.Context, token string) error + + // Refresh refreshes the access token using the refresh token. + Refresh(ctx context.Context, token string) (oauth2.Token, error) +} diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 6bd6f59e8e..9ac23c7620 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -22,6 +22,7 @@ import ( svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" @@ -41,7 +42,9 @@ func setupGroups() (*httptest.Server, *mocks.Repository, *authmocks.AuthClient) logger := mglog.NewMock() mux := chi.NewRouter() - api.MakeHandler(csvc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + api.MakeHandler(csvc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), grepo, auth } diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 2483fb3fad..ca5191d160 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -21,6 +21,7 @@ import ( repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" @@ -47,7 +48,9 @@ func setupUsers() (*httptest.Server, *umocks.Repository, *gmocks.Repository, *au logger := mglog.NewMock() mux := chi.NewRouter() - api.MakeHandler(csvc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + api.MakeHandler(csvc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), crepo, gRepo, auth } diff --git a/users/api/clients.go b/users/api/clients.go index 84283d4e72..991728d8c6 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -22,7 +23,7 @@ import ( ) // MakeHandler returns a HTTP handler for API endpoints. -func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger) http.Handler { +func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, providers ...oauth2.Provider) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } @@ -170,6 +171,11 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger) http.Han api.EncodeResponse, opts..., ), "list_users_by_domain_id").ServeHTTP) + + for _, provider := range providers { + r.HandleFunc("/oauth/callback/"+provider.Name(), oauth2CallbackHandler(provider, svc)) + } + return r } @@ -515,3 +521,64 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err ListPerms: lp, }, nil } + +// oauth2CallbackHandler is a http.HandlerFunc that handles OAuth2 callbacks. +func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !oauth.IsEnabled() { + http.Redirect(w, r, oauth.ErrorURL()+"?error=oauth%20provider%20is%20disabled", http.StatusSeeOther) + return + } + // state is prefixed with signin- or signup- to indicate which flow we should use + var state string + var flow oauth2.State + var err error + if strings.Contains(r.FormValue("state"), "-") { + state = strings.Split(r.FormValue("state"), "-")[1] + flow, err = oauth2.ToState(strings.Split(r.FormValue("state"), "-")[0]) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) //nolint:goconst + return + } + } + + if state != oauth.State() { + http.Redirect(w, r, oauth.ErrorURL()+"?error=invalid%20state", http.StatusSeeOther) + return + } + + if code := r.FormValue("code"); code != "" { + client, token, err := oauth.UserDetails(r.Context(), code) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) + return + } + + jwt, err := svc.OAuthCallback(r.Context(), oauth.Name(), flow, token, client) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "access_token", + Value: jwt.AccessToken, + Path: "/", + HttpOnly: true, + Secure: true, + }) + http.SetCookie(w, &http.Cookie{ + Name: "refresh_token", + Value: *jwt.RefreshToken, + Path: "/", + HttpOnly: true, + Secure: true, + }) + + http.Redirect(w, r, oauth.RedirectURL(), http.StatusFound) + return + } + + http.Redirect(w, r, oauth.ErrorURL()+"?error=empty%20code", http.StatusSeeOther) + } +} diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index ba9a78e015..3ec2ea791d 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -23,6 +23,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" httpapi "github.com/absmach/magistrala/users/api" "github.com/absmach/magistrala/users/mocks" @@ -89,7 +90,9 @@ func newUsersServer() (*httptest.Server, *mocks.Service) { logger := mglog.NewMock() mux := chi.NewRouter() - httpapi.MakeHandler(svc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + httpapi.MakeHandler(svc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), svc } diff --git a/users/api/logging.go b/users/api/logging.go index 70b693694f..e570995206 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -10,7 +10,9 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" + "golang.org/x/oauth2" ) var _ users.Service = (*loggingMiddleware)(nil) @@ -397,3 +399,21 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id str }(time.Now()) return lm.svc.Identify(ctx, token) } + +func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (token *magistrala.Token, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("provider", provider), + slog.String("state", state.String()), + slog.String("user_id", client.ID), + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn("OAuth callback failed to complete successfully", args...) + return + } + lm.logger.Info("OAuth callback completed successfully", args...) + }(time.Now()) + return lm.svc.OAuthCallback(ctx, provider, state, oauthToken, client) +} diff --git a/users/api/metrics.go b/users/api/metrics.go index dd17297fab..8c93d29b1f 100644 --- a/users/api/metrics.go +++ b/users/api/metrics.go @@ -9,8 +9,10 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-kit/kit/metrics" + "golang.org/x/oauth2" ) var _ users.Service = (*metricsMiddleware)(nil) @@ -191,3 +193,12 @@ func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (string }(time.Now()) return ms.svc.Identify(ctx, token) } + +func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + method := provider + "_oauth_callback_" + state.String() + defer func(begin time.Time) { + ms.counter.With("method", method).Add(1) + ms.latency.With("method", method).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.OAuthCallback(ctx, provider, state, token, client) +} diff --git a/users/api/transport.go b/users/api/transport.go index af6255833b..354d7b5d5f 100644 --- a/users/api/transport.go +++ b/users/api/transport.go @@ -9,14 +9,15 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" ) // MakeHandler returns a HTTP handler for Users and Groups API endpoints. -func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { - clientsHandler(cls, mux, logger) +func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, providers ...oauth2.Provider) http.Handler { + clientsHandler(cls, mux, logger, providers...) groupsHandler(grps, mux, logger) mux.Get("/health", magistrala.Health("users", instanceID)) diff --git a/users/clients.go b/users/clients.go index ed1871f47d..d6b3c328e4 100644 --- a/users/clients.go +++ b/users/clients.go @@ -8,6 +8,8 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" + "golang.org/x/oauth2" ) // Service specifies an API that must be fullfiled by the domain service @@ -73,4 +75,8 @@ type Service interface { // After an access token expires, the refresh token is used to get // a new pair of access and refresh tokens. RefreshToken(ctx context.Context, accessToken, domainID string) (*magistrala.Token, error) + + // OAuthCallback handles the callback from any supported OAuth provider. + // It processes the OAuth tokens and either signs in or signs up the user based on the provided state. + OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client clients.Client) (*magistrala.Token, error) } diff --git a/users/events/events.go b/users/events/events.go index 95bba47903..9b1692c2ff 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -28,6 +28,7 @@ const ( refreshToken = clientPrefix + "refresh_token" resetSecret = clientPrefix + "reset_secret" sendPasswordReset = clientPrefix + "send_password_reset" + oauthCallback = clientPrefix + "oauth_callback" ) var ( @@ -44,6 +45,7 @@ var ( _ events.Event = (*refreshTokenEvent)(nil) _ events.Event = (*resetSecretEvent)(nil) _ events.Event = (*sendPasswordResetEvent)(nil) + _ events.Event = (*oauthCallbackEvent)(nil) ) type createClientEvent struct { @@ -410,3 +412,18 @@ func (spre sendPasswordResetEvent) Encode() (map[string]interface{}, error) { "user": spre.user, }, nil } + +type oauthCallbackEvent struct { + state string + provider string + clientID string +} + +func (oce oauthCallbackEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": oauthCallback, + "state": oce.state, + "provider": oce.provider, + "client_id": oce.clientID, + }, nil +} diff --git a/users/events/streams.go b/users/events/streams.go index c810413c28..1b9b801564 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -10,7 +10,9 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" + "golang.org/x/oauth2" ) const streamID = "magistrala.users" @@ -295,3 +297,22 @@ func (es *eventStore) SendPasswordReset(ctx context.Context, host, email, user, return es.Publish(ctx, event) } + +func (es *eventStore) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + token, err := es.svc.OAuthCallback(ctx, provider, state, oauthToken, client) + if err != nil { + return token, err + } + + event := oauthCallbackEvent{ + provider: provider, + state: state.String(), + clientID: client.ID, + } + + if err := es.Publish(ctx, event); err != nil { + return token, err + } + + return token, nil +} diff --git a/users/mocks/service.go b/users/mocks/service.go index 1546c4858b..69bd46130d 100644 --- a/users/mocks/service.go +++ b/users/mocks/service.go @@ -12,6 +12,10 @@ import ( magistrala "github.com/absmach/magistrala" mock "github.com/stretchr/testify/mock" + + oauth2 "github.com/absmach/magistrala/pkg/oauth2" + + xoauth2 "golang.org/x/oauth2" ) // Service is an autogenerated mock type for the Service type @@ -207,6 +211,36 @@ func (_m *Service) ListMembers(ctx context.Context, token string, objectKind str return r0, r1 } +// OAuthCallback provides a mock function with given fields: ctx, provider, state, token, client +func (_m *Service) OAuthCallback(ctx context.Context, provider string, state oauth2.State, token xoauth2.Token, client clients.Client) (*magistrala.Token, error) { + ret := _m.Called(ctx, provider, state, token, client) + + if len(ret) == 0 { + panic("no return value specified for OAuthCallback") + } + + var r0 *magistrala.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) (*magistrala.Token, error)); ok { + return rf(ctx, provider, state, token, client) + } + if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) *magistrala.Token); ok { + r0 = rf(ctx, provider, state, token, client) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*magistrala.Token) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) error); ok { + r1 = rf(ctx, provider, state, token, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RefreshToken provides a mock function with given fields: ctx, accessToken, domainID func (_m *Service) RefreshToken(ctx context.Context, accessToken string, domainID string) (*magistrala.Token, error) { ret := _m.Called(ctx, accessToken, domainID) diff --git a/users/postgres/init.go b/users/postgres/init.go index 9e8a4903ff..85e88fafd3 100644 --- a/users/postgres/init.go +++ b/users/postgres/init.go @@ -37,6 +37,14 @@ func Migration() *migrate.MemoryMigrationSource { `DROP TABLE IF EXISTS clients`, }, }, + { + // To support creation of clients from Oauth2 provider + Id: "clients_02", + Up: []string{ + `ALTER TABLE clients ALTER COLUMN secret DROP NOT NULL`, + }, + Down: []string{}, + }, }, } } diff --git a/users/service.go b/users/service.go index 6f6acb13ba..3947b8a271 100644 --- a/users/service.go +++ b/users/service.go @@ -5,6 +5,7 @@ package users import ( "context" + "fmt" "regexp" "time" @@ -14,7 +15,9 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users/postgres" + "golang.org/x/oauth2" "golang.org/x/sync/errgroup" ) @@ -80,14 +83,14 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien return mgclients.Client{}, err } - if cli.Credentials.Secret == "" { - return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, repoerr.ErrMissingSecret) - } - hash, err := svc.hasher.Hash(cli.Credentials.Secret) - if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + if cli.Credentials.Secret != "" { + hash, err := svc.hasher.Hash(cli.Credentials.Secret) + if err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + cli.Credentials.Secret = hash } - cli.Credentials.Secret = hash + if cli.Status != mgclients.DisabledStatus && cli.Status != mgclients.EnabledStatus { return mgclients.Client{}, svcerr.ErrInvalidStatus } @@ -586,6 +589,45 @@ func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, per return res.GetId(), nil } +func (svc service) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + switch state { + case mgoauth2.SignIn: + rclient, err := svc.clients.RetrieveByIdentity(ctx, client.Credentials.Identity) + if err != nil { + if errors.Contains(err, repoerr.ErrNotFound) { + return &magistrala.Token{}, errors.New("user not signed up") + } + return &magistrala.Token{}, err + } + claims := &magistrala.IssueReq{ + UserId: rclient.ID, + Type: 0, + OauthProvider: provider, + OauthAccessToken: token.AccessToken, + OauthRefreshToken: token.RefreshToken, + } + return svc.auth.Issue(ctx, claims) + case mgoauth2.SignUp: + rclient, err := svc.RegisterClient(ctx, "", client) + if err != nil { + if errors.Contains(err, repoerr.ErrConflict) { + return &magistrala.Token{}, errors.New("user already exists") + } + return &magistrala.Token{}, err + } + claims := &magistrala.IssueReq{ + UserId: rclient.ID, + Type: 0, + OauthProvider: provider, + OauthAccessToken: token.AccessToken, + OauthRefreshToken: token.RefreshToken, + } + return svc.auth.Issue(ctx, claims) + default: + return &magistrala.Token{}, fmt.Errorf("unknown state %s", state) + } +} + func (svc service) Identify(ctx context.Context, token string) (string, error) { user, err := svc.auth.Identify(ctx, &magistrala.IdentityReq{Token: token}) if err != nil { diff --git a/users/service_test.go b/users/service_test.go index ebd63823fb..8657a3c7aa 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -18,6 +18,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/hasher" @@ -25,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) var ( @@ -160,7 +162,7 @@ func TestRegisterClient(t *testing.T) { }, addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, - err: repoerr.ErrMissingSecret, + err: nil, }, { desc: "register a new client with a weak secret", @@ -2465,3 +2467,160 @@ func TestViewProfile(t *testing.T) { repoCall1.Unset() } } + +func TestOAuthCallback(t *testing.T) { + svc, cRepo, auth, _ := newService(true) + + cases := []struct { + desc string + provider string + state mgoauth2.State + token oauth2.Token + client mgclients.Client + retrieveByIdentityResponse mgclients.Client + retrieveByIdentityErr error + addPoliciesResponse *magistrala.AddPoliciesRes + addPoliciesErr error + deletePoliciesResponse *magistrala.DeletePoliciesRes + deletePoliciesErr error + saveResponse mgclients.Client + saveErr error + issueResponse *magistrala.Token + issueErr error + err error + }{ + { + desc: "oauth signin callback with successfully", + provider: "google", + state: mgoauth2.SignIn, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + issueResponse: &magistrala.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: &validToken, + AccessType: "Bearer", + }, + err: nil, + }, + { + desc: "oauth signin callback with error", + provider: "google", + state: mgoauth2.SignIn, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{}, + retrieveByIdentityErr: repoerr.ErrNotFound, + issueResponse: &magistrala.Token{}, + err: errors.New("user not signed up"), + }, + { + desc: "oauth signup callback with successfully", + provider: "google", + state: mgoauth2.SignUp, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + addPoliciesResponse: &magistrala.AddPoliciesRes{ + Added: true, + }, + addPoliciesErr: nil, + saveResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + saveErr: nil, + issueResponse: &magistrala.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: &validToken, + AccessType: "Bearer", + }, + err: nil, + }, + { + desc: "oauth signup callback with error", + provider: "google", + state: mgoauth2.SignUp, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + addPoliciesResponse: &magistrala.AddPoliciesRes{ + Added: true, + }, + addPoliciesErr: nil, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + deletePoliciesErr: nil, + saveResponse: mgclients.Client{}, + saveErr: repoerr.ErrConflict, + issueResponse: &magistrala.Token{}, + err: errors.New("user already exists"), + }, + } + + for _, tc := range cases { + switch tc.state { + case mgoauth2.SignUp: + repoCall := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.saveResponse, tc.saveErr) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesErr) + repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesErr) + repoCall3 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + if err == nil { + assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) + assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + case mgoauth2.SignIn: + repoCall := cRepo.On("RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) + repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + if err == nil { + assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) + assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity) + repoCall.Unset() + repoCall1.Unset() + } + } +} diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index 547b6319c7..29788dbfec 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -8,9 +8,11 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "golang.org/x/oauth2" ) var _ users.Service = (*tracingMiddleware)(nil) @@ -192,3 +194,15 @@ func (tm *tracingMiddleware) Identify(ctx context.Context, token string) (string return tm.svc.Identify(ctx, token) } + +// OAuthCallback traces the "OAuthCallback" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + ctx, span := tm.tracer.Start(ctx, "svc_oauth_callback", trace.WithAttributes( + attribute.String("provider", provider), + attribute.String("state", state.String()), + attribute.String("client_id", client.ID), + )) + defer span.End() + + return tm.svc.OAuthCallback(ctx, provider, state, token, client) +} From 292c08bf25fd0cf7c53416d011f86588a3a7b5d5 Mon Sep 17 00:00:00 2001 From: Ian Ngethe Muchiri <100555904+ianmuchyri@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:09:29 +0300 Subject: [PATCH 52/71] NOISSUE - Update UI service env variables (#2105) Signed-off-by: ianmuchyri --- docker/.env | 4 +++- docker/docker-compose.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docker/.env b/docker/.env index 5860920b3e..d551f94294 100644 --- a/docker/.env +++ b/docker/.env @@ -165,7 +165,7 @@ MG_USERS_DB_SSL_ROOT_CERT= MG_USERS_RESET_PWD_TEMPLATE=users.tmpl MG_USERS_INSTANCE_ID= MG_USERS_ALLOW_SELF_REGISTER=true -MG_OAUTH_UI_REDIRECT_URL="http://localhost:9095/domains" +MG_OAUTH_UI_REDIRECT_URL="http://localhost:9095/tokens/secure" MG_OAUTH_UI_ERROR_URL="http://localhost:9095/error" ### Email utility @@ -275,6 +275,8 @@ MG_UI_DB_SSL_MODE=disable MG_UI_DB_SSL_CERT= MG_UI_DB_SSL_KEY= MG_UI_DB_SSL_ROOT_CERT= +MG_UI_HASH_KEY=5jx4x2Qg9OUmzpP5dbveWQ +MG_UI_BLOCK_KEY=UtgZjr92jwRY6SPUndHXiyl9QY8qTUyZ ## Addons Services ### Bootstrap diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2fd6152613..160975e9a4 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -738,7 +738,9 @@ services: MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} - MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} + MG_UI_HASH_KEY: ${MG_UI_HASH_KEY} + MG_UI_BLOCK_KEY: ${MG_UI_BLOCK_KEY} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: From 145fdb359754bb08eccbb611e374d79512e2380e Mon Sep 17 00:00:00 2001 From: Nataly Musilah <115026536+Musilah@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:12:55 +0300 Subject: [PATCH 53/71] NOISSUE - Update timescale reader (#2085) Signed-off-by: Musilah Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Co-authored-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- api/openapi/readers.yml | 45 ++++++++++-- cli/message.go | 8 ++- internal/apiutil/errors.go | 12 ++++ pkg/sdk/go/message.go | 43 ++++++++++-- pkg/sdk/go/sdk.go | 20 +++++- pkg/sdk/mocks/sdk.go | 8 +-- readers/api/endpoint_test.go | 125 +++++++++++++++++++++++++++++++++- readers/api/requests.go | 24 +++++++ readers/api/transport.go | 24 ++++++- readers/messages.go | 2 + readers/timescale/messages.go | 14 ++-- 11 files changed, 298 insertions(+), 27 deletions(-) diff --git a/api/openapi/readers.yml b/api/openapi/readers.yml index 47cea33ebd..2391060e27 100644 --- a/api/openapi/readers.yml +++ b/api/openapi/readers.yml @@ -26,7 +26,7 @@ servers: - url: https://localhost:9009 - url: http://localhost:9011 - url: https://localhost:9011 - + tags: - name: readers description: Everything about your Readers @@ -57,14 +57,16 @@ paths: - $ref: "#/components/parameters/DataValue" - $ref: "#/components/parameters/From" - $ref: "#/components/parameters/To" + - $ref: "#/components/parameters/Aggregation" + - $ref: "#/components/parameters/Interval" responses: - '200': + "200": $ref: "#/components/responses/MessagesPageRes" - '400': + "400": description: Failed due to malformed query parameters. - '401': + "401": description: Missing or invalid access token provided. - '500': + "500": $ref: "#/components/responses/ServiceError" /health: get: @@ -72,9 +74,9 @@ paths: tags: - health responses: - '200': + "200": $ref: "#/components/responses/HealthRes" - '500': + "500": $ref: "#/components/responses/ServiceError" components: @@ -226,6 +228,7 @@ components: in: query schema: type: number + example: 1709218556069 required: false To: name: to @@ -233,6 +236,34 @@ components: in: query schema: type: number + example: 1709218757503 + required: false + Aggregation: + name: aggregation + description: Aggregation function. + in: query + schema: + type: string + enum: + - MAX + - AVG + - MIN + - SUM + - COUNT + - max + - min + - sum + - avg + - count + example: MAX + required: false + Interval: + name: interval + description: Aggregation interval. + in: query + schema: + type: string + example: 10s required: false responses: diff --git a/cli/message.go b/cli/message.go index 79f86571fb..538b51bc80 100644 --- a/cli/message.go +++ b/cli/message.go @@ -38,9 +38,11 @@ var cmdMessages = []cobra.Command{ logUsage(cmd.Use) return } - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, + pageMetadata := mgxsdk.MessagePageMetadata{ + PageMetadata: mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + }, } m, err := sdk.ReadMessages(pageMetadata, args[0], args[1]) diff --git a/internal/apiutil/errors.go b/internal/apiutil/errors.go index bca1c710ed..11b880106d 100644 --- a/internal/apiutil/errors.go +++ b/internal/apiutil/errors.go @@ -164,4 +164,16 @@ var ( // ErrRollbackTx indicates failed to rollback transaction. ErrRollbackTx = errors.New("failed to rollback transaction") + + // ErrInvalidAggregation indicates invalid aggregation value. + ErrInvalidAggregation = errors.New("invalid aggregation value") + + // ErrInvalidInterval indicates invalid interval value. + ErrInvalidInterval = errors.New("invalid interval value") + + // ErrMissingFrom indicates missing from value. + ErrMissingFrom = errors.New("missing from time value") + + // ErrMissingTo indicates missing to value. + ErrMissingTo = errors.New("missing to time value") ) diff --git a/pkg/sdk/go/message.go b/pkg/sdk/go/message.go index 591436ef51..e5a044885e 100644 --- a/pkg/sdk/go/message.go +++ b/pkg/sdk/go/message.go @@ -7,6 +7,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strconv" "strings" "github.com/absmach/magistrala/internal/apiutil" @@ -23,14 +25,14 @@ func (sdk mgSDK) SendMessage(chanName, msg, key string) errors.SDKError { subtopicPart = fmt.Sprintf("/%s", strings.ReplaceAll(chanNameParts[1], ".", "/")) } - url := fmt.Sprintf("%s/channels/%s/messages%s", sdk.httpAdapterURL, chanID, subtopicPart) + reqURL := fmt.Sprintf("%s/channels/%s/messages%s", sdk.httpAdapterURL, chanID, subtopicPart) - _, _, err := sdk.processRequest(http.MethodPost, url, ThingPrefix+key, []byte(msg), nil, http.StatusAccepted) + _, _, err := sdk.processRequest(http.MethodPost, reqURL, ThingPrefix+key, []byte(msg), nil, http.StatusAccepted) return err } -func (sdk mgSDK) ReadMessages(pm PageMetadata, chanName, token string) (MessagesPage, errors.SDKError) { +func (sdk mgSDK) ReadMessages(pm MessagePageMetadata, chanName, token string) (MessagesPage, errors.SDKError) { chanNameParts := strings.SplitN(chanName, ".", channelParts) chanID := chanNameParts[0] subtopicPart := "" @@ -39,7 +41,7 @@ func (sdk mgSDK) ReadMessages(pm PageMetadata, chanName, token string) (Messages } readMessagesEndpoint := fmt.Sprintf("channels/%s/messages%s", chanID, subtopicPart) - url, err := sdk.withQueryParams(sdk.readerURL, readMessagesEndpoint, pm) + msgURL, err := sdk.withMessageQueryParams(sdk.readerURL, readMessagesEndpoint, pm) if err != nil { return MessagesPage{}, errors.NewSDKError(err) } @@ -47,7 +49,7 @@ func (sdk mgSDK) ReadMessages(pm PageMetadata, chanName, token string) (Messages header := make(map[string]string) header["Content-Type"] = string(sdk.msgContentType) - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, header, http.StatusOK) + _, body, sdkerr := sdk.processRequest(http.MethodGet, msgURL, token, nil, header, http.StatusOK) if sdkerr != nil { return MessagesPage{}, sdkerr } @@ -69,3 +71,34 @@ func (sdk *mgSDK) SetContentType(ct ContentType) errors.SDKError { return nil } + +func (sdk mgSDK) withMessageQueryParams(baseURL, endpoint string, mpm MessagePageMetadata) (string, error) { + b, err := json.Marshal(mpm) + if err != nil { + return "", err + } + q := map[string]interface{}{} + if err := json.Unmarshal(b, &q); err != nil { + return "", err + } + ret := url.Values{} + for k, v := range q { + switch t := v.(type) { + case string: + ret.Add(k, t) + case float64: + ret.Add(k, strconv.FormatFloat(t, 'f', -1, 64)) + case uint64: + ret.Add(k, strconv.FormatUint(t, 10)) + case int64: + ret.Add(k, strconv.FormatInt(t, 10)) + case json.Number: + ret.Add(k, t.String()) + case bool: + ret.Add(k, strconv.FormatBool(t)) + } + } + qs := ret.Encode() + + return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, qs), nil +} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 1bb8ff3033..8a4d980c73 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -69,6 +69,22 @@ var ( ErrInvalidJWT = errors.New("invalid JWT") ) +type MessagePageMetadata struct { + PageMetadata + Subtopic string `json:"subtopic,omitempty"` + Publisher string `json:"publisher,omitempty"` + Comparator string `json:"comparator,omitempty"` + BoolValue *bool `json:"vb,omitempty"` + StringValue string `json:"vs,omitempty"` + DataValue string `json:"vd,omitempty"` + From float64 `json:"from,omitempty"` + To float64 `json:"to,omitempty"` + Aggregation string `json:"aggregation,omitempty"` + Interval string `json:"interval,omitempty"` + Value float64 `json:"value,omitempty"` + Protocol string `json:"protocol,omitempty"` +} + type PageMetadata struct { Total uint64 `json:"total"` Offset uint64 `json:"offset"` @@ -828,13 +844,13 @@ type SDK interface { // ReadMessages read messages of specified channel. // // example: - // pm := sdk.PageMetadata{ + // pm := sdk.MessagePageMetadata{ // Offset: 0, // Limit: 10, // } // msgs, _ := sdk.ReadMessages(pm,"channelID", "token") // fmt.Println(msgs) - ReadMessages(pm PageMetadata, chanID, token string) (MessagesPage, errors.SDKError) + ReadMessages(pm MessagePageMetadata, chanID, 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 eadcb80409..56123f54f9 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -1805,7 +1805,7 @@ func (_m *SDK) Parents(id string, pm sdk.PageMetadata, token string) (sdk.Groups } // ReadMessages provides a mock function with given fields: pm, chanID, token -func (_m *SDK) ReadMessages(pm sdk.PageMetadata, chanID string, token string) (sdk.MessagesPage, errors.SDKError) { +func (_m *SDK) ReadMessages(pm sdk.MessagePageMetadata, chanID string, token string) (sdk.MessagesPage, errors.SDKError) { ret := _m.Called(pm, chanID, token) if len(ret) == 0 { @@ -1814,16 +1814,16 @@ func (_m *SDK) ReadMessages(pm sdk.PageMetadata, chanID string, token string) (s var r0 sdk.MessagesPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.MessagesPage, errors.SDKError)); ok { + 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.PageMetadata, string, string) sdk.MessagesPage); ok { + if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string) sdk.MessagesPage); ok { r0 = rf(pm, chanID, token) } else { r0 = ret.Get(0).(sdk.MessagesPage) } - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string, string) errors.SDKError); ok { + if rf, ok := ret.Get(1).(func(sdk.MessagePageMetadata, string, string) errors.SDKError); ok { r1 = rf(pm, chanID, token) } else { if ret.Get(1) != nil { diff --git a/readers/api/endpoint_test.go b/readers/api/endpoint_test.go index 6c77b46b2a..cbbee4d5a3 100644 --- a/readers/api/endpoint_test.go +++ b/readers/api/endpoint_test.go @@ -414,7 +414,6 @@ func TestReadAll(t *testing.T) { key: thingToken, status: http.StatusBadRequest, }, - { desc: "read page with non-float to as thing", url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID), @@ -431,6 +430,68 @@ func TestReadAll(t *testing.T) { Messages: messages[5:15], }, }, + { + desc: "read page with aggregation as thing", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), + key: thingToken, + status: http.StatusBadRequest, + }, + { + desc: "read page with interval as thing", + url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), + key: thingToken, + status: http.StatusOK, + res: pageRes{ + Total: uint64(len(messages)), + Messages: messages[0:10], + }, + }, + { + desc: "read page with aggregation and interval as thing", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), + key: thingToken, + 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), + key: thingToken, + status: http.StatusOK, + res: pageRes{ + Total: uint64(len(messages[5:20])), + Messages: messages[5:15], + }, + }, + { + 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), + key: thingToken, + 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), + key: thingToken, + 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), + key: thingToken, + 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), + key: thingToken, + 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), + key: thingToken, + 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), @@ -711,6 +772,68 @@ func TestReadAll(t *testing.T) { Messages: messages[5:15], }, }, + { + desc: "read page with aggregation as user", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), + key: userToken, + status: http.StatusBadRequest, + }, + { + desc: "read page with interval as user", + url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), + key: userToken, + status: http.StatusOK, + res: pageRes{ + Total: uint64(len(messages)), + Messages: messages[0:10], + }, + }, + { + desc: "read page with aggregation and interval as user", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), + key: userToken, + 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), + key: userToken, + status: http.StatusOK, + res: pageRes{ + Total: uint64(len(messages[5:20])), + Messages: messages[5:15], + }, + }, + { + 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), + key: userToken, + 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), + key: userToken, + 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), + key: userToken, + 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), + key: userToken, + 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), + key: userToken, + status: http.StatusBadRequest, + }, } for _, tc := range cases { diff --git a/readers/api/requests.go b/readers/api/requests.go index b157d106e1..b1386a71fa 100644 --- a/readers/api/requests.go +++ b/readers/api/requests.go @@ -4,12 +4,18 @@ package api import ( + "slices" + "strings" + "time" + "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/readers" ) const maxLimitSize = 1000 +var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"} + type listMessagesReq struct { chanID string token string @@ -39,5 +45,23 @@ func (req listMessagesReq) validate() error { return apiutil.ErrInvalidComparator } + if req.pageMeta.Aggregation != "" { + if req.pageMeta.From == 0 { + return apiutil.ErrMissingFrom + } + + if req.pageMeta.To == 0 { + return apiutil.ErrMissingTo + } + + if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) { + return apiutil.ErrInvalidAggregation + } + + if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil { + return apiutil.ErrInvalidInterval + } + } + return nil } diff --git a/readers/api/transport.go b/readers/api/transport.go index 5cab942a80..2c55d8a98b 100644 --- a/readers/api/transport.go +++ b/readers/api/transport.go @@ -36,6 +36,9 @@ const ( comparatorKey = "comparator" fromKey = "from" toKey = "to" + aggregationKey = "aggregation" + intervalKey = "interval" + defInterval = "1s" defLimit = 10 defOffset = 0 defFormat = "messages" @@ -141,6 +144,19 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { return nil, errors.Wrap(apiutil.ErrValidation, err) } + aggregation, err := apiutil.ReadStringQuery(r, aggregationKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + var interval string + if aggregation != "" { + interval, err = apiutil.ReadStringQuery(r, intervalKey, defInterval) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + } + req := listMessagesReq{ chanID: chi.URLParam(r, "chanID"), token: apiutil.ExtractBearerToken(r), @@ -160,6 +176,8 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { BoolValue: vb, From: from, To: to, + Aggregation: aggregation, + Interval: interval, }, } return req, nil @@ -196,7 +214,11 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrMissingID), errors.Contains(err, apiutil.ErrLimitSize), errors.Contains(err, apiutil.ErrOffsetSize), - errors.Contains(err, apiutil.ErrInvalidComparator): + errors.Contains(err, apiutil.ErrInvalidComparator), + errors.Contains(err, apiutil.ErrInvalidAggregation), + errors.Contains(err, apiutil.ErrInvalidInterval), + errors.Contains(err, apiutil.ErrMissingFrom), + errors.Contains(err, apiutil.ErrMissingTo): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, svcerr.ErrAuthorization), diff --git a/readers/messages.go b/readers/messages.go index acda83d776..eb14ebeb0a 100644 --- a/readers/messages.go +++ b/readers/messages.go @@ -55,6 +55,8 @@ type PageMetadata struct { From float64 `json:"from,omitempty"` To float64 `json:"to,omitempty"` Format string `json:"format,omitempty"` + Aggregation string `json:"aggregation,omitempty"` + Interval string `json:"interval,omitempty"` } // ParseValueComparator convert comparison operator keys into mathematic anotation. diff --git a/readers/timescale/messages.go b/readers/timescale/messages.go index 26857a56ae..e16304ab5e 100644 --- a/readers/timescale/messages.go +++ b/readers/timescale/messages.go @@ -37,7 +37,14 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( format = rpm.Format } - q := fmt.Sprintf(`SELECT * FROM %s WHERE %s ORDER BY %s DESC LIMIT :limit OFFSET :offset;`, format, fmtCondition(chanID, rpm), order) + q := fmt.Sprintf(`SELECT * FROM %s WHERE %s ORDER BY %s DESC LIMIT :limit OFFSET :offset;`, format, fmtCondition(rpm), order) + totalQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE %s;`, format, fmtCondition(rpm)) + + // If aggregation is provided, add time_bucket and aggregation to the query + if rpm.Aggregation != "" { + q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) + totalQuery = fmt.Sprintf(`SELECT COUNT(*) FROM (SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1) AS subquery;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) + } params := map[string]interface{}{ "channel": chanID, @@ -94,8 +101,7 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( } } - q = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE %s;`, format, fmtCondition(chanID, rpm)) - rows, err = tr.db.NamedQuery(q, params) + rows, err = tr.db.NamedQuery(totalQuery, params) if err != nil { return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) } @@ -112,7 +118,7 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( return page, nil } -func fmtCondition(chanID string, rpm readers.PageMetadata) string { +func fmtCondition(rpm readers.PageMetadata) string { condition := `channel = :channel` var query map[string]interface{} From 49d25af724ea656c70d7cf2561b61333a7d7acc2 Mon Sep 17 00:00:00 2001 From: Ian Ngethe Muchiri <100555904+ianmuchyri@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:38:00 +0300 Subject: [PATCH 54/71] NOISSUE - Update UI environment variables (#2107) Signed-off-by: ianmuchyri --- docker/.env | 57 ++++++++++++++++++++------------------- docker/docker-compose.yml | 1 + 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/docker/.env b/docker/.env index d551f94294..2161902502 100644 --- a/docker/.env +++ b/docker/.env @@ -140,6 +140,33 @@ MG_INVITATIONS_DB_SSL_KEY= MG_INVITATIONS_DB_SSL_ROOT_CERT= MG_INVITATIONS_INSTANCE_ID= +### UI +MG_UI_LOG_LEVEL=debug +MG_UI_PORT=9095 +MG_HTTP_ADAPTER_URL=http://http-adapter:8008 +MG_READER_URL=http://timescale-reader:9011 +MG_THINGS_URL=http://things:9000 +MG_USERS_URL=http://users:9002 +MG_INVITATIONS_URL=http://invitations:9020 +MG_DOMAINS_URL=http://auth:8189 +MG_BOOTSTRAP_URL=http://bootstrap:9013 +MG_UI_HOST_URL=http://localhost:9095 +MG_UI_VERIFICATION_TLS=false +MG_UI_CONTENT_TYPE=application/senml+json +MG_UI_INSTANCE_ID= +MG_UI_DB_HOST=ui-db +MG_UI_DB_PORT=5432 +MG_UI_DB_USER=magistrala +MG_UI_DB_PASS=magistrala +MG_UI_DB_NAME=ui +MG_UI_DB_SSL_MODE=disable +MG_UI_DB_SSL_CERT= +MG_UI_DB_SSL_KEY= +MG_UI_DB_SSL_ROOT_CERT= +MG_UI_HASH_KEY=5jx4x2Qg9OUmzpP5dbveWQ +MG_UI_BLOCK_KEY=UtgZjr92jwRY6SPUndHXiyl9QY8qTUyZ +MG_UI_PATH_PREFIX=/ui + ### Users MG_USERS_LOG_LEVEL=debug MG_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH @@ -165,8 +192,8 @@ MG_USERS_DB_SSL_ROOT_CERT= MG_USERS_RESET_PWD_TEMPLATE=users.tmpl MG_USERS_INSTANCE_ID= MG_USERS_ALLOW_SELF_REGISTER=true -MG_OAUTH_UI_REDIRECT_URL="http://localhost:9095/tokens/secure" -MG_OAUTH_UI_ERROR_URL="http://localhost:9095/error" +MG_OAUTH_UI_REDIRECT_URL=http://localhost:9095${MG_UI_PATH_PREFIX}/tokens/secure +MG_OAUTH_UI_ERROR_URL=http://localhost:9095${MG_UI_PATH_PREFIX}/error ### Email utility MG_EMAIL_HOST=smtp.mailtrap.io @@ -252,32 +279,6 @@ MG_WS_ADAPTER_HTTP_SERVER_CERT= MG_WS_ADAPTER_HTTP_SERVER_KEY= MG_WS_ADAPTER_INSTANCE_ID= -### UI -MG_UI_LOG_LEVEL=debug -MG_UI_PORT=9095 -MG_HTTP_ADAPTER_URL=http://http-adapter:8008 -MG_READER_URL=http://mongodb-reader:9007 -MG_THINGS_URL=http://things:9000 -MG_USERS_URL=http://users:9002 -MG_INVITATIONS_URL=http://invitations:9020 -MG_DOMAINS_URL=http://auth:8189 -MG_BOOTSTRAP_URL=http://bootstrap:9013 -MG_UI_HOST_URL=http://localhost:9095 -MG_UI_VERIFICATION_TLS=false -MG_UI_CONTENT_TYPE=application/senml+json -MG_UI_INSTANCE_ID= -MG_UI_DB_HOST=ui-db -MG_UI_DB_PORT=5432 -MG_UI_DB_USER=magistrala -MG_UI_DB_PASS=magistrala -MG_UI_DB_NAME=ui -MG_UI_DB_SSL_MODE=disable -MG_UI_DB_SSL_CERT= -MG_UI_DB_SSL_KEY= -MG_UI_DB_SSL_ROOT_CERT= -MG_UI_HASH_KEY=5jx4x2Qg9OUmzpP5dbveWQ -MG_UI_BLOCK_KEY=UtgZjr92jwRY6SPUndHXiyl9QY8qTUyZ - ## Addons Services ### Bootstrap MG_BOOTSTRAP_LOG_LEVEL=debug diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 160975e9a4..d5c278eab4 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -741,6 +741,7 @@ services: MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} MG_UI_HASH_KEY: ${MG_UI_HASH_KEY} MG_UI_BLOCK_KEY: ${MG_UI_BLOCK_KEY} + MG_UI_PATH_PREFIX: ${MG_UI_PATH_PREFIX} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: From 2200a2d914786d882c7c442904d8498d19a91b8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:26:25 +0100 Subject: [PATCH 55/71] NOISSUE - Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 (#2108) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index fb5e018e49..4bd3293c10 100644 --- a/go.mod +++ b/go.mod @@ -91,7 +91,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/go.sum b/go.sum index f1186c3a31..044f2c005e 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,9 @@ github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= @@ -184,6 +185,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -720,6 +722,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 6929ea4493c03f855a77a528607c32d1f32268da Mon Sep 17 00:00:00 2001 From: Ian Ngethe Muchiri <100555904+ianmuchyri@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:02:12 +0300 Subject: [PATCH 56/71] NOISSUE - Update Aggregation SQL query (#2111) Signed-off-by: ianmuchyri --- readers/timescale/messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readers/timescale/messages.go b/readers/timescale/messages.go index e16304ab5e..aa1589d033 100644 --- a/readers/timescale/messages.go +++ b/readers/timescale/messages.go @@ -42,7 +42,7 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( // If aggregation is provided, add time_bucket and aggregation to the query if rpm.Aggregation != "" { - q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) + q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) *1000 AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) totalQuery = fmt.Sprintf(`SELECT COUNT(*) FROM (SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1) AS subquery;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) } From 110ac4b43a979830d6772b26e2fc70dee61970a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:13:33 +0100 Subject: [PATCH 57/71] NOISSUE - Bump github.com/lestrrat-go/jwx/v2 from 2.0.19 to 2.0.21 (#2110) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 4bd3293c10..6f3347d331 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/jackc/pgtype v1.14.1 github.com/jackc/pgx/v5 v5.5.2 github.com/jmoiron/sqlx v1.3.5 - github.com/lestrrat-go/jwx/v2 v2.0.19 + github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/mitchellh/mapstructure v1.5.0 github.com/nats-io/nats.go v1.32.0 github.com/oklog/ulid/v2 v2.1.0 @@ -46,7 +46,7 @@ require ( github.com/rubenv/sql-migrate v1.6.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 go.mongodb.org/mongo-driver v1.13.1 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 @@ -55,7 +55,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 - golang.org/x/crypto v0.19.0 + golang.org/x/crypto v0.21.0 golang.org/x/net v0.21.0 golang.org/x/oauth2 v0.17.0 golang.org/x/sync v0.6.0 @@ -125,7 +125,7 @@ require ( github.com/klauspost/compress v1.17.4 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect @@ -161,7 +161,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -178,7 +178,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 044f2c005e..e651dd119e 100644 --- a/go.sum +++ b/go.sum @@ -336,14 +336,14 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= -github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.0.2/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= -github.com/lestrrat-go/jwx/v2 v2.0.19 h1:ekv1qEZE6BVct89QA+pRF6+4pCpfVrOnEJnTnT4RXoY= -github.com/lestrrat-go/jwx/v2 v2.0.19/go.mod h1:l3im3coce1lL2cDeAjqmaR+Awx+X8Ih+2k8BuHNJ4CU= +github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= +github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= @@ -508,8 +508,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -518,10 +518,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -617,8 +617,9 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -711,8 +712,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From b6a1cb848ebea26e80beea1f99e04237654cb313 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:11:20 +0300 Subject: [PATCH 58/71] NOISSUE - Add Secret Validation on Registration (#2109) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- cmd/users/main.go | 4 +- internal/api/common.go | 10 ++-- internal/api/common_test.go | 4 +- internal/apiutil/errors.go | 6 +-- pkg/sdk/go/groups_test.go | 4 +- pkg/sdk/go/things_test.go | 4 +- pkg/sdk/go/tokens_test.go | 2 +- pkg/sdk/go/users_test.go | 21 +++----- users/api/clients.go | 7 ++- users/api/endpoint_test.go | 33 ++++++------ users/api/requests.go | 20 ++++++- users/api/requests_test.go | 105 ++++++++++++++++++++++++++++++------ users/api/transport.go | 5 +- users/service.go | 18 ++----- users/service_test.go | 33 +----------- 15 files changed, 163 insertions(+), 113 deletions(-) diff --git a/cmd/users/main.go b/cmd/users/main.go index ae84549b94..d96e648c71 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -186,7 +186,7 @@ func main() { oauthProvider := googleoauth.NewProvider(oauthConfig, cfg.OAuthUIRedirectURL, cfg.OAuthUIErrorURL) mux := chi.NewRouter() - httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID, oauthProvider), logger) + httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID, cfg.PassRegex, oauthProvider), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) @@ -219,7 +219,7 @@ func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error())) } - csvc := users.NewService(cRepo, authClient, emailerClient, hsr, idp, c.PassRegex, c.SelfRegister) + csvc := users.NewService(cRepo, authClient, emailerClient, hsr, idp, c.SelfRegister) gsvc := mggroups.NewService(gRepo, idp, authClient) csvc, err = uevents.NewEventStoreMiddleware(ctx, csvc, c.ESURL) diff --git a/internal/api/common.go b/internal/api/common.go index 886b934657..998402ad1c 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -106,8 +106,7 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { w.Header().Set("Content-Type", ContentType) switch { - case errors.Contains(err, apiutil.ErrInvalidSecret), - errors.Contains(err, svcerr.ErrMalformedEntity), + case errors.Contains(err, svcerr.ErrMalformedEntity), errors.Contains(err, errors.ErrMalformedEntity), errors.Contains(err, apiutil.ErrMissingID), errors.Contains(err, apiutil.ErrEmptyList), @@ -121,7 +120,12 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrInvalidQueryParams), errors.Contains(err, apiutil.ErrInvalidStatus), errors.Contains(err, apiutil.ErrMissingRelation), - errors.Contains(err, apiutil.ErrValidation): + errors.Contains(err, apiutil.ErrValidation), + errors.Contains(err, apiutil.ErrMissingIdentity), + errors.Contains(err, apiutil.ErrMissingSecret), + errors.Contains(err, apiutil.ErrMissingPass), + errors.Contains(err, apiutil.ErrMissingConfPass), + errors.Contains(err, apiutil.ErrPasswordFormat): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, apiutil.ErrBearerToken): diff --git a/internal/api/common_test.go b/internal/api/common_test.go index c243aac1b5..ac1adf40c4 100644 --- a/internal/api/common_test.go +++ b/internal/api/common_test.go @@ -225,7 +225,7 @@ func TestEncodeError(t *testing.T) { { desc: "BadRequest", errs: []error{ - apiutil.ErrInvalidSecret, + apiutil.ErrMissingSecret, svcerr.ErrMalformedEntity, errors.ErrMalformedEntity, apiutil.ErrMissingID, @@ -240,7 +240,7 @@ func TestEncodeError(t *testing.T) { { desc: "BadRequest with validation error", errs: []error{ - errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidSecret), + errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), errors.Wrap(apiutil.ErrValidation, svcerr.ErrMalformedEntity), errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), diff --git a/internal/apiutil/errors.go b/internal/apiutil/errors.go index 11b880106d..2f2261fa03 100644 --- a/internal/apiutil/errors.go +++ b/internal/apiutil/errors.go @@ -132,6 +132,9 @@ var ( // ErrMissingSecret indicates missing secret. ErrMissingSecret = errors.New("missing secret") + // ErrPasswordFormat indicates weak password. + ErrPasswordFormat = errors.New("password does not meet the requirements") + // ErrMissingOwner indicates missing entity owner. ErrMissingOwner = errors.New("missing entity owner") @@ -144,9 +147,6 @@ var ( // ErrMissingName indicates missing identity name. ErrMissingName = errors.New("missing identity name") - // ErrInvalidSecret indicates invalid secret. - ErrInvalidSecret = errors.New("missing secret") - // ErrInvalidLevel indicates an invalid group level. ErrInvalidLevel = errors.New("invalid group level (should be between 0 and 5)") diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 9ac23c7620..5b82db9a92 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -37,14 +37,14 @@ func setupGroups() (*httptest.Server, *mocks.Repository, *authmocks.AuthClient) grepo := new(mocks.Repository) auth := new(authmocks.AuthClient) - csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, passRegex, true) + csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, true) gsvc := groups.NewService(grepo, idProvider, auth) logger := mglog.NewMock() mux := chi.NewRouter() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") - api.MakeHandler(csvc, gsvc, mux, logger, "", provider) + api.MakeHandler(csvc, gsvc, mux, logger, "", passRegex, provider) return httptest.NewServer(mux), grepo, auth } diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 40fd38d0ee..613b57084f 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -892,8 +892,8 @@ func TestUpdateThingSecret(t *testing.T) { newSecret: "newSecret", token: validToken, response: sdk.Thing{}, - repoErr: apiutil.ErrInvalidSecret, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, apiutil.ErrInvalidSecret), http.StatusBadRequest), + repoErr: apiutil.ErrMissingSecret, + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, apiutil.ErrMissingSecret), http.StatusBadRequest), }, } for _, tc := range cases { diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go index ebf6cfbd53..ad5f07a957 100644 --- a/pkg/sdk/go/tokens_test.go +++ b/pkg/sdk/go/tokens_test.go @@ -62,7 +62,7 @@ func TestIssueToken(t *testing.T) { desc: "issue token for an empty user", login: sdk.Login{}, token: &magistrala.Token{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), }, { desc: "issue token for invalid identity", diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index ca5191d160..54b0f93f87 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -43,14 +43,14 @@ func setupUsers() (*httptest.Server, *umocks.Repository, *gmocks.Repository, *au gRepo := new(gmocks.Repository) auth := new(authmocks.AuthClient) - csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, passRegex, true) + csvc := users.NewService(crepo, auth, emailer, phasher, idProvider, true) gsvc := groups.NewService(gRepo, idProvider, auth) logger := mglog.NewMock() mux := chi.NewRouter() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") - api.MakeHandler(csvc, gsvc, mux, logger, "", provider) + api.MakeHandler(csvc, gsvc, mux, logger, "", passRegex, provider) return httptest.NewServer(mux), crepo, gRepo, auth } @@ -62,7 +62,7 @@ func TestCreateClient(t *testing.T) { user := sdk.User{ Name: "clientname", Tags: []string{"tag1", "tag2"}, - Credentials: sdk.Credentials{Identity: "admin@example.com", Secret: "secret"}, + Credentials: sdk.Credentials{Identity: "admin@example.com", Secret: "12345678"}, Status: mgclients.EnabledStatus.String(), } conf := sdk.Config{ @@ -96,7 +96,7 @@ func TestCreateClient(t *testing.T) { client: sdk.User{}, response: sdk.User{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), }, { desc: "register a user that can't be marshalled", @@ -135,7 +135,7 @@ func TestCreateClient(t *testing.T) { }, response: sdk.User{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), }, { desc: "register user with empty identity", @@ -147,14 +147,7 @@ func TestCreateClient(t *testing.T) { }, response: sdk.User{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), http.StatusBadRequest), - }, - { - desc: "register empty user", - client: sdk.User{}, - response: sdk.User{}, - token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), }, { desc: "register user with every field defined", @@ -828,7 +821,7 @@ func TestUpdateClientSecret(t *testing.T) { newSecret: "newSecret", token: validToken, response: sdk.User{}, - repoErr: apiutil.ErrInvalidSecret, + repoErr: apiutil.ErrMissingSecret, err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrNotFound, repoerr.ErrMissingSecret), http.StatusBadRequest), }, } diff --git a/users/api/clients.go b/users/api/clients.go index 991728d8c6..65b6527d8e 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -8,6 +8,7 @@ import ( "encoding/json" "log/slog" "net/http" + "regexp" "strings" "github.com/absmach/magistrala/auth" @@ -22,8 +23,12 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) +var passRegex = regexp.MustCompile("^.{8,}$") + // MakeHandler returns a HTTP handler for API endpoints. -func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, providers ...oauth2.Provider) http.Handler { +func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler { + passRegex = pr + opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index 3ec2ea791d..dc1dcb4a0c 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "net/http/httptest" + "regexp" "strings" "testing" @@ -44,11 +45,11 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - validToken = "valid" - inValidToken = "invalid" - inValid = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - ErrPasswordFormat = errors.New("password does not meet the requirements") + validToken = "valid" + inValidToken = "invalid" + inValid = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + passRegex = regexp.MustCompile("^.{8,}$") ) const contentType = "application/json" @@ -92,7 +93,7 @@ func newUsersServer() (*httptest.Server, *mocks.Service) { mux := chi.NewRouter() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") - httpapi.MakeHandler(svc, gsvc, mux, logger, "", provider) + httpapi.MakeHandler(svc, gsvc, mux, logger, "", passRegex, provider) return httptest.NewServer(mux), svc } @@ -1166,8 +1167,8 @@ func TestPasswordReset(t *testing.T) { data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "weak", "weak"), token: validToken, contentType: contentType, - status: http.StatusInternalServerError, - err: ErrPasswordFormat, + status: http.StatusBadRequest, + err: apiutil.ErrPasswordFormat, }, { desc: "password reset with empty token", @@ -1182,7 +1183,7 @@ func TestPasswordReset(t *testing.T) { data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "", ""), token: validToken, contentType: contentType, - status: http.StatusInternalServerError, + status: http.StatusBadRequest, err: apiutil.ErrValidation, }, { @@ -1338,7 +1339,7 @@ func TestUpdateClientSecret(t *testing.T) { }{ { desc: "update user secret with valid token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, client: mgclients.Client{ ID: client.ID, Credentials: mgclients.Credentials{ @@ -1353,7 +1354,7 @@ func TestUpdateClientSecret(t *testing.T) { }, { desc: "update user secret with empty token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, client: mgclients.Client{ ID: client.ID, Credentials: mgclients.Credentials{ @@ -1368,7 +1369,7 @@ func TestUpdateClientSecret(t *testing.T) { }, { desc: "update user secret with invalid token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), + data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, client: mgclients.Client{ ID: client.ID, Credentials: mgclients.Credentials{ @@ -1384,7 +1385,7 @@ func TestUpdateClientSecret(t *testing.T) { { desc: "update user secret with empty secret", - data: fmt.Sprintf(`{"secret": "%s"}`, ""), + data: `{"old_secret": "", "new_secret": "strongersecret"}`, client: mgclients.Client{ ID: client.ID, Credentials: mgclients.Credentials{ @@ -1395,11 +1396,11 @@ func TestUpdateClientSecret(t *testing.T) { contentType: contentType, token: validToken, status: http.StatusBadRequest, - err: apiutil.ErrBearerKey, + err: apiutil.ErrMissingPass, }, { desc: "update user secret with invalid contentype", - data: fmt.Sprintf(`{"secret": "%s"}`, ""), + data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, client: mgclients.Client{ ID: client.ID, Credentials: mgclients.Credentials{ @@ -1479,7 +1480,7 @@ func TestIssueToken(t *testing.T) { desc: "issue token with empty identity", data: fmt.Sprintf(`{"identity": "%s", "secret": "%s", "domainID": "%s"}`, "", secret, validID), contentType: contentType, - status: http.StatusInternalServerError, + status: http.StatusBadRequest, err: apiutil.ErrValidation, }, { diff --git a/users/api/requests.go b/users/api/requests.go index 394cca1240..471f726096 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -20,6 +20,15 @@ func (req createClientReq) validate() error { if len(req.client.Name) > api.MaxNameSize { return apiutil.ErrNameSize } + if req.client.Credentials.Identity == "" { + return apiutil.ErrMissingIdentity + } + if req.client.Credentials.Secret == "" { + return apiutil.ErrMissingPass + } + if !passRegex.MatchString(req.client.Credentials.Secret) { + return apiutil.ErrPasswordFormat + } return req.client.Validate() } @@ -180,6 +189,12 @@ func (req updateClientSecretReq) validate() error { if req.token == "" { return apiutil.ErrBearerToken } + if req.OldSecret == "" || req.NewSecret == "" { + return apiutil.ErrMissingPass + } + if !passRegex.MatchString(req.NewSecret) { + return apiutil.ErrPasswordFormat + } return nil } @@ -211,7 +226,7 @@ func (req loginClientReq) validate() error { return apiutil.ErrMissingIdentity } if req.Secret == "" { - return apiutil.ErrMissingSecret + return apiutil.ErrMissingPass } return nil @@ -265,6 +280,9 @@ func (req resetTokenReq) validate() error { if req.Password != req.ConfPass { return apiutil.ErrInvalidResetPass } + if !passRegex.MatchString(req.ConfPass) { + return apiutil.ErrPasswordFormat + } return nil } diff --git a/users/api/requests_test.go b/users/api/requests_test.go index 571233ae86..760e50eda1 100644 --- a/users/api/requests_test.go +++ b/users/api/requests_test.go @@ -17,6 +17,7 @@ import ( const ( valid = "valid" invalid = "invalid" + secret = "QJg58*aMan7j" ) var validID = testsutil.GenerateUUID(&testing.T{}) @@ -36,7 +37,7 @@ func TestCreateClientReqValidate(t *testing.T) { Name: valid, Credentials: mgclients.Credentials{ Identity: "example@example.com", - Secret: valid, + Secret: secret, }, }, }, @@ -51,7 +52,7 @@ func TestCreateClientReqValidate(t *testing.T) { Name: valid, Credentials: mgclients.Credentials{ Identity: "example@example.com", - Secret: valid, + Secret: secret, }, }, }, @@ -67,6 +68,49 @@ func TestCreateClientReqValidate(t *testing.T) { }, err: apiutil.ErrNameSize, }, + { + desc: "missing identity in request", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: valid, + Credentials: mgclients.Credentials{ + Secret: valid, + }, + }, + }, + err: apiutil.ErrMissingIdentity, + }, + { + desc: "missing secret in request", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: valid, + Credentials: mgclients.Credentials{ + Identity: "example@example.com", + }, + }, + }, + err: apiutil.ErrMissingPass, + }, + { + desc: "invalid secret in request", + req: createClientReq{ + token: valid, + client: mgclients.Client{ + ID: validID, + Name: valid, + Credentials: mgclients.Credentials{ + Identity: "example@example.com", + Secret: "invalid", + }, + }, + }, + err: apiutil.ErrPasswordFormat, + }, } for _, tc := range cases { err := tc.req.validate() @@ -411,8 +455,8 @@ func TestUpdateClientSecretReqValidate(t *testing.T) { desc: "valid request", req: updateClientSecretReq{ token: valid, - OldSecret: valid, - NewSecret: valid, + OldSecret: secret, + NewSecret: secret, }, err: nil, }, @@ -420,11 +464,38 @@ func TestUpdateClientSecretReqValidate(t *testing.T) { desc: "empty token", req: updateClientSecretReq{ token: "", - OldSecret: valid, - NewSecret: valid, + OldSecret: secret, + NewSecret: secret, }, err: apiutil.ErrBearerToken, }, + { + desc: "missing old secret", + req: updateClientSecretReq{ + token: valid, + OldSecret: "", + NewSecret: secret, + }, + err: apiutil.ErrMissingPass, + }, + { + desc: "missing new secret", + req: updateClientSecretReq{ + token: valid, + OldSecret: secret, + NewSecret: "", + }, + err: apiutil.ErrMissingPass, + }, + { + desc: "invalid new secret", + req: updateClientSecretReq{ + token: valid, + OldSecret: secret, + NewSecret: "invalid", + }, + err: apiutil.ErrPasswordFormat, + }, } for _, c := range cases { err := c.req.validate() @@ -479,7 +550,7 @@ func TestLoginClientReqValidate(t *testing.T) { desc: "valid request", req: loginClientReq{ Identity: "eaxmple,example.com", - Secret: valid, + Secret: secret, }, err: nil, }, @@ -487,7 +558,7 @@ func TestLoginClientReqValidate(t *testing.T) { desc: "empty identity", req: loginClientReq{ Identity: "", - Secret: valid, + Secret: secret, }, err: apiutil.ErrMissingIdentity, }, @@ -497,7 +568,7 @@ func TestLoginClientReqValidate(t *testing.T) { Identity: "eaxmple,example.com", Secret: "", }, - err: apiutil.ErrMissingSecret, + err: apiutil.ErrMissingPass, }, } for _, c := range cases { @@ -580,8 +651,8 @@ func TestResetTokenReqValidate(t *testing.T) { desc: "valid request", req: resetTokenReq{ Token: valid, - Password: valid, - ConfPass: valid, + Password: secret, + ConfPass: secret, }, err: nil, }, @@ -589,8 +660,8 @@ func TestResetTokenReqValidate(t *testing.T) { desc: "empty token", req: resetTokenReq{ Token: "", - Password: valid, - ConfPass: valid, + Password: secret, + ConfPass: secret, }, err: apiutil.ErrBearerToken, }, @@ -599,7 +670,7 @@ func TestResetTokenReqValidate(t *testing.T) { req: resetTokenReq{ Token: valid, Password: "", - ConfPass: valid, + ConfPass: secret, }, err: apiutil.ErrMissingPass, }, @@ -607,7 +678,7 @@ func TestResetTokenReqValidate(t *testing.T) { desc: "empty confpass", req: resetTokenReq{ Token: valid, - Password: valid, + Password: secret, ConfPass: "", }, err: apiutil.ErrMissingConfPass, @@ -616,8 +687,8 @@ func TestResetTokenReqValidate(t *testing.T) { desc: "mismatching password and confpass", req: resetTokenReq{ Token: valid, - Password: "valid2", - ConfPass: valid, + Password: "secret", + ConfPass: secret, }, err: apiutil.ErrInvalidResetPass, }, diff --git a/users/api/transport.go b/users/api/transport.go index 354d7b5d5f..e3defd315f 100644 --- a/users/api/transport.go +++ b/users/api/transport.go @@ -6,6 +6,7 @@ package api import ( "log/slog" "net/http" + "regexp" "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/groups" @@ -16,8 +17,8 @@ import ( ) // MakeHandler returns a HTTP handler for Users and Groups API endpoints. -func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, providers ...oauth2.Provider) http.Handler { - clientsHandler(cls, mux, logger, providers...) +func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler { + clientsHandler(cls, mux, logger, pr, providers...) groupsHandler(grps, mux, logger) mux.Get("/health", magistrala.Health("users", instanceID)) diff --git a/users/service.go b/users/service.go index 3947b8a271..c566fff904 100644 --- a/users/service.go +++ b/users/service.go @@ -6,7 +6,6 @@ package users import ( "context" "fmt" - "regexp" "time" "github.com/absmach/magistrala" @@ -25,9 +24,6 @@ var ( // ErrRecoveryToken indicates error in generating password recovery token. ErrRecoveryToken = errors.New("failed to generate password recovery token") - // ErrPasswordFormat indicates weak password. - ErrPasswordFormat = errors.New("password does not meet the requirements") - // ErrFailedPolicyUpdate indicates a failure to update user policy. ErrFailedPolicyUpdate = errors.New("failed to update user policy") @@ -50,19 +46,17 @@ type service struct { auth magistrala.AuthServiceClient hasher Hasher email Emailer - passRegex *regexp.Regexp selfRegister bool } // NewService returns a new Users service implementation. -func NewService(crepo postgres.Repository, authClient magistrala.AuthServiceClient, emailer Emailer, hasher Hasher, idp magistrala.IDProvider, pr *regexp.Regexp, selfRegister bool) Service { +func NewService(crepo postgres.Repository, authClient magistrala.AuthServiceClient, emailer Emailer, hasher Hasher, idp magistrala.IDProvider, selfRegister bool) Service { return service{ clients: crepo, auth: authClient, hasher: hasher, email: emailer, idProvider: idp, - passRegex: pr, selfRegister: selfRegister, } } @@ -92,10 +86,10 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien } if cli.Status != mgclients.DisabledStatus && cli.Status != mgclients.EnabledStatus { - return mgclients.Client{}, svcerr.ErrInvalidStatus + return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidStatus) } if cli.Role != mgclients.UserRole && cli.Role != mgclients.AdminRole { - return mgclients.Client{}, svcerr.ErrInvalidRole + return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidRole) } cli.ID = clientID cli.CreatedAt = time.Now() @@ -313,9 +307,6 @@ func (svc service) ResetSecret(ctx context.Context, resetToken, secret string) e if c.Credentials.Identity == "" { return repoerr.ErrNotFound } - if !svc.passRegex.MatchString(secret) { - return ErrPasswordFormat - } secret, err = svc.hasher.Hash(secret) if err != nil { return err @@ -340,9 +331,6 @@ func (svc service) UpdateClientSecret(ctx context.Context, token, oldSecret, new if err != nil { return mgclients.Client{}, err } - if !svc.passRegex.MatchString(newSecret) { - return mgclients.Client{}, ErrPasswordFormat - } dbClient, err := svc.clients.RetrieveByID(ctx, id) if err != nil { return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) diff --git a/users/service_test.go b/users/service_test.go index 8657a3c7aa..41ce243379 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -6,7 +6,6 @@ package users_test import ( "context" "fmt" - "regexp" "strings" "testing" @@ -43,7 +42,6 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - passRegex = regexp.MustCompile("^.{8,}$") validToken = "token" inValidToken = "invalid" validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" @@ -57,7 +55,7 @@ func newService(selfRegister bool) (users.Service, *mocks.Repository, *authmocks cRepo := new(mocks.Repository) auth := new(authmocks.AuthClient) e := mocks.NewEmailer() - return users.NewService(cRepo, auth, e, phasher, idProvider, passRegex, selfRegister), cRepo, auth, e + return users.NewService(cRepo, auth, e, phasher, idProvider, selfRegister), cRepo, auth, e } func TestRegisterClient(t *testing.T) { @@ -164,19 +162,6 @@ func TestRegisterClient(t *testing.T) { deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, err: nil, }, - { - desc: "register a new client with a weak secret", - client: mgclients.Client{ - Name: "clientWithWeakSecret", - Credentials: mgclients.Credentials{ - Identity: "clientwithweaksecret@example.com", - Secret: "weak", - }, - }, - addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, - deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, - err: nil, - }, { desc: " register a client with a secret that is too long", client: mgclients.Client{ @@ -1180,14 +1165,6 @@ func TestUpdateClientSecret(t *testing.T) { identifyErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, - { - desc: "update client secret with weak secret", - oldSecret: client.Credentials.Secret, - newSecret: "weak", - token: validToken, - identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, - err: users.ErrPasswordFormat, - }, { desc: "update client secret with failed to retrieve client by ID", oldSecret: client.Credentials.Secret, @@ -2364,14 +2341,6 @@ func TestResetSecret(t *testing.T) { }, err: repoerr.ErrNotFound, }, - { - desc: "reset secret with invalid secret format", - token: validToken, - newSecret: "weak", - identifyResponse: &magistrala.IdentityRes{UserId: client.ID}, - retrieveByIDResponse: client, - err: users.ErrPasswordFormat, - }, { desc: "reset secret with failed to update secret", token: validToken, From 291c2a3ad926ab61717bff41051d8a8790febd56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:10:20 +0100 Subject: [PATCH 59/71] NOISSUE - Bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#2112) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f3347d331..78d3e7d33a 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac google.golang.org/grpc v1.60.1 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/go.sum b/go.sum index e651dd119e..738374d356 100644 --- a/go.sum +++ b/go.sum @@ -802,8 +802,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 7ee657c9ce965ed12d5f9a18f910f812b357fac0 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:55:17 +0300 Subject: [PATCH 60/71] NOISSUE - Remove OAuth2.0 tokens from Magistrala token (#2106) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: rodneyosodo --- auth.pb.go | 607 +++++++++++++--------------- auth.proto | 5 +- auth/api/grpc/client.go | 14 +- auth/api/grpc/endpoint.go | 1 - auth/api/grpc/requests.go | 7 +- auth/api/grpc/server.go | 5 - auth/api/http/keys/endpoint_test.go | 5 +- auth/jwt/token_test.go | 158 +------- auth/jwt/tokenizer.go | 120 ++---- auth/keys.go | 24 +- auth/service.go | 6 +- auth/service_test.go | 17 +- cmd/auth/main.go | 31 +- docker/docker-compose.yml | 4 - pkg/oauth2/google/provider.go | 78 +--- pkg/oauth2/mocks/provider.go | 107 ++--- pkg/oauth2/oauth2.go | 11 +- users/api/clients.go | 10 +- users/api/logging.go | 6 +- users/api/metrics.go | 7 +- users/clients.go | 3 +- users/events/events.go | 2 - users/events/streams.go | 6 +- users/mocks/service.go | 20 +- users/service.go | 19 +- users/service_test.go | 43 +- users/tracing/tracing.go | 6 +- 27 files changed, 450 insertions(+), 872 deletions(-) diff --git a/auth.pb.go b/auth.pb.go index 6d9ace3530..739335b134 100644 --- a/auth.pb.go +++ b/auth.pb.go @@ -204,12 +204,9 @@ type IssueReq struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` - Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` - OauthProvider string `protobuf:"bytes,4,opt,name=oauth_provider,json=oauthProvider,proto3" json:"oauth_provider,omitempty"` - OauthAccessToken string `protobuf:"bytes,5,opt,name=oauth_access_token,json=oauthAccessToken,proto3" json:"oauth_access_token,omitempty"` - OauthRefreshToken string `protobuf:"bytes,6,opt,name=oauth_refresh_token,json=oauthRefreshToken,proto3" json:"oauth_refresh_token,omitempty"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` } func (x *IssueReq) Reset() { @@ -265,27 +262,6 @@ func (x *IssueReq) GetType() uint32 { return 0 } -func (x *IssueReq) GetOauthProvider() string { - if x != nil { - return x.OauthProvider - } - return "" -} - -func (x *IssueReq) GetOauthAccessToken() string { - if x != nil { - return x.OauthAccessToken - } - return "" -} - -func (x *IssueReq) GetOauthRefreshToken() string { - if x != nil { - return x.OauthRefreshToken - } - return "" -} - type RefreshReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1901,162 +1877,217 @@ var file_auth_proto_rawDesc = []byte{ 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0xec, 0x01, 0x0a, 0x08, 0x49, 0x73, 0x73, - 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, - 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x61, - 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, - 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, + 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, + 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, + 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, + 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, + 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, + 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, + 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, + 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, - 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, - 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, - 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, - 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, - 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, - 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, - 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, + 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, @@ -2064,152 +2095,88 @@ var file_auth_proto_rawDesc = []byte{ 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, - 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, - 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, - 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, - 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, - 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, - 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, - 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, - 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, - 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, - 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, - 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, - 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, - 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, - 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, + 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, + 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, + 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, + 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, + 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, + 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, + 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, + 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, + 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, + 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, + 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, - 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/auth.proto b/auth.proto index 114b25409d..6cd0b1c44b 100644 --- a/auth.proto +++ b/auth.proto @@ -56,10 +56,7 @@ message IdentityRes { message IssueReq { string user_id = 1; optional string domain_id = 2; - uint32 type = 3; - string oauth_provider = 4; - string oauth_access_token = 5; - string oauth_refresh_token = 6; + uint32 type = 3; } message RefreshReq { diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index 4b0c8f8d33..1ad27c5849 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -178,11 +178,6 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.GetType()), - oauthToken: auth.OAuthToken{ - Provider: req.GetOauthProvider(), - AccessToken: req.GetOauthAccessToken(), - RefreshToken: req.GetOauthRefreshToken(), - }, }) if err != nil { return &magistrala.Token{}, decodeError(err) @@ -193,12 +188,9 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(issueReq) return &magistrala.IssueReq{ - UserId: req.userID, - DomainId: &req.domainID, - Type: uint32(req.keyType), - OauthProvider: req.oauthToken.Provider, - OauthAccessToken: req.oauthToken.AccessToken, - OauthRefreshToken: req.oauthToken.RefreshToken, + UserId: req.userID, + DomainId: &req.domainID, + Type: uint32(req.keyType), }, nil } diff --git a/auth/api/grpc/endpoint.go b/auth/api/grpc/endpoint.go index f2db6b6530..c691ea732e 100644 --- a/auth/api/grpc/endpoint.go +++ b/auth/api/grpc/endpoint.go @@ -21,7 +21,6 @@ func issueEndpoint(svc auth.Service) endpoint.Endpoint { Type: req.keyType, User: req.userID, Domain: req.domainID, - OAuth: req.oauthToken, } tkn, err := svc.Issue(ctx, "", key) if err != nil { diff --git a/auth/api/grpc/requests.go b/auth/api/grpc/requests.go index 057d82c01f..7df318cee2 100644 --- a/auth/api/grpc/requests.go +++ b/auth/api/grpc/requests.go @@ -21,10 +21,9 @@ func (req identityReq) validate() error { } type issueReq struct { - userID string - domainID string // optional - keyType auth.KeyType - oauthToken auth.OAuthToken + userID string + domainID string // optional + keyType auth.KeyType } func (req issueReq) validate() error { diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index 1bac27e1fd..63974aabc4 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -277,11 +277,6 @@ func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, er userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.GetType()), - oauthToken: auth.OAuthToken{ - Provider: req.GetOauthProvider(), - AccessToken: req.GetOauthAccessToken(), - RefreshToken: req.GetOauthRefreshToken(), - }, }, nil } diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 7721124b8d..0f2ec5eba8 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -21,7 +21,6 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mglog "github.com/absmach/magistrala/logger" svcerr "github.com/absmach/magistrala/pkg/errors/service" - oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -73,9 +72,7 @@ func newService() (auth.Service, *mocks.KeyRepository) { drepo := new(mocks.DomainsRepository) idProvider := uuid.NewMock() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - t := jwt.New([]byte(secret), provider) + t := jwt.New([]byte(secret)) return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), krepo } diff --git a/auth/jwt/token_test.go b/auth/jwt/token_test.go index 9a15e0483d..8cb560aa18 100644 --- a/auth/jwt/token_test.go +++ b/auth/jwt/token_test.go @@ -4,9 +4,7 @@ package jwt_test import ( - "context" "fmt" - "strings" "testing" "time" @@ -15,13 +13,10 @@ import ( "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "golang.org/x/oauth2" ) const ( @@ -56,9 +51,7 @@ func newToken(issuerName string, key auth.Key) string { } func TestIssue(t *testing.T) { - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - tokenizer := authjwt.New([]byte(secret), provider) + tokenizer := authjwt.New([]byte(secret)) cases := []struct { desc string @@ -80,11 +73,6 @@ func TestIssue(t *testing.T) { Domain: testsutil.GenerateUUID(t), IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), ExpiresAt: time.Now().Add(10 * time.Minute).Round(time.Second), - OAuth: auth.OAuthToken{ - Provider: "test", - AccessToken: strings.Repeat("a", 1024), - RefreshToken: strings.Repeat("b", 1024), - }, }, err: nil, }, @@ -100,9 +88,7 @@ func TestIssue(t *testing.T) { } func TestParse(t *testing.T) { - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - tokenizer := authjwt.New([]byte(secret), provider) + tokenizer := authjwt.New([]byte(secret)) token, err := tokenizer.Issue(key()) require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) @@ -173,99 +159,6 @@ func TestParse(t *testing.T) { } } -func TestParseOAuthToken(t *testing.T) { - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - tokenizer := authjwt.New([]byte(secret), provider) - - validKey := oauthKey(t) - invalidKey := oauthKey(t) - invalidKey.OAuth.Provider = "invalid" - - cases := []struct { - desc string - token auth.Key - issuedToken string - key auth.Key - validateErr error - refreshToken oauth2.Token - refreshErr error - err error - }{ - { - desc: "parse valid key", - token: validKey, - issuedToken: "", - key: validKey, - validateErr: nil, - refreshErr: nil, - err: nil, - }, - { - desc: "parse invalid key but refreshed", - token: validKey, - issuedToken: "", - key: validKey, - validateErr: svcerr.ErrAuthentication, - refreshToken: oauth2.Token{ - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, - refreshErr: nil, - err: nil, - }, - { - desc: "parse invalid key but not refreshed", - token: validKey, - issuedToken: "", - key: validKey, - validateErr: svcerr.ErrAuthentication, - refreshToken: oauth2.Token{}, - refreshErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "parse invalid key with different provider", - issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", "b"), - err: svcerr.ErrAuthentication, - }, - { - desc: "parse invalid key with invalid access token", - issuedToken: invalidOauthToken(t, invalidKey, "invalid", 123, "b"), - err: svcerr.ErrAuthentication, - }, - { - desc: "parse invalid key with invalid refresh token", - issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", 123), - err: svcerr.ErrAuthentication, - }, - { - desc: "parse invalid key with invalid provider", - issuedToken: invalidOauthToken(t, invalidKey, "test", "a", "b"), - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - tokenCall := provider.On("Name").Return("test") - tokenCall1 := provider.On("Validate", context.Background(), mock.Anything).Return(tc.validateErr) - tokenCall2 := provider.On("Refresh", context.Background(), mock.Anything).Return(tc.refreshToken, tc.refreshErr) - if tc.issuedToken == "" { - var err error - tc.issuedToken, err = tokenizer.Issue(tc.token) - require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) - } - key, err := tokenizer.Parse(tc.issuedToken) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key)) - } - tokenCall.Unset() - tokenCall1.Unset() - tokenCall2.Unset() - } -} - func key() auth.Key { exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) return auth.Key{ @@ -277,50 +170,3 @@ func key() auth.Key { ExpiresAt: exp, } } - -func oauthKey(t *testing.T) auth.Key { - return auth.Key{ - ID: testsutil.GenerateUUID(t), - Type: auth.AccessKey, - Issuer: "magistrala.auth", - Subject: testsutil.GenerateUUID(t), - User: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: time.Now().UTC().Add(10 * time.Minute).Round(time.Second), - OAuth: auth.OAuthToken{ - Provider: "test", - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, - } -} - -func invalidOauthToken(t *testing.T, key auth.Key, provider, accessToken, refreshToken interface{}) string { - builder := jwt.NewBuilder() - builder. - Issuer(issuerName). - IssuedAt(key.IssuedAt). - Subject(key.Subject). - Claim(tokenType, key.Type). - Expiration(key.ExpiresAt) - builder.Claim(userField, key.User) - builder.Claim(domainField, key.Domain) - if provider != nil { - builder.Claim("oauth_provider", provider) - if accessToken != nil { - builder.Claim(provider.(string), map[string]interface{}{"access_token": accessToken}) - } - if refreshToken != nil { - builder.Claim(provider.(string), map[string]interface{}{"refresh_token": refreshToken}) - } - } - if key.ID != "" { - builder.JwtID(key.ID) - } - tkn, err := builder.Build() - require.Nil(t, err, fmt.Sprintf("building token expected to succeed: %s", err)) - signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, reposecret)) - require.Nil(t, err, fmt.Sprintf("signing token expected to succeed: %s", err)) - return string(signedTkn) -} diff --git a/auth/jwt/tokenizer.go b/auth/jwt/tokenizer.go index 5627d0609a..b8ad9f3e8d 100644 --- a/auth/jwt/tokenizer.go +++ b/auth/jwt/tokenizer.go @@ -12,7 +12,6 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/oauth2" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" ) @@ -33,9 +32,6 @@ var ( ErrValidateJWTToken = errors.New("failed to validate jwt token") // ErrJSONHandle indicates an error in handling JSON. ErrJSONHandle = errors.New("failed to perform operation JSON") - - // errInvalidProvider indicates an invalid OAuth2.0 provider. - errInvalidProvider = errors.New("invalid OAuth2.0 provider") ) const ( @@ -49,21 +45,15 @@ const ( ) type tokenizer struct { - secret []byte - providers map[string]oauth2.Provider + secret []byte } var _ auth.Tokenizer = (*tokenizer)(nil) // NewRepository instantiates an implementation of Token repository. -func New(secret []byte, providers ...oauth2.Provider) auth.Tokenizer { - providersMap := make(map[string]oauth2.Provider) - for _, provider := range providers { - providersMap[provider.Name()] = provider - } +func New(secret []byte) auth.Tokenizer { return &tokenizer{ - secret: secret, - providers: providersMap, + secret: secret, } } @@ -78,18 +68,6 @@ func (tok *tokenizer) Issue(key auth.Key) (string, error) { builder.Claim(userField, key.User) builder.Claim(domainField, key.Domain) - if key.OAuth.Provider != "" { - provider, ok := tok.providers[key.OAuth.Provider] - if !ok { - return "", errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) - } - builder.Claim(oauthProviderField, provider.Name()) - builder.Claim(provider.Name(), map[string]interface{}{ - oauthAccessTokenField: key.OAuth.AccessToken, - oauthRefreshTokenField: key.OAuth.RefreshToken, - }) - } - if key.ID != "" { builder.JwtID(key.ID) } @@ -105,6 +83,20 @@ func (tok *tokenizer) Issue(key auth.Key) (string, error) { } func (tok *tokenizer) Parse(token string) (auth.Key, error) { + tkn, err := tok.validateToken(token) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + + key, err := toKey(tkn) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + + return key, nil +} + +func (tok *tokenizer) validateToken(token string) (jwt.Token, error) { tkn, err := jwt.Parse( []byte(token), jwt.WithValidate(true), @@ -112,10 +104,10 @@ func (tok *tokenizer) Parse(token string) (auth.Key, error) { ) if err != nil { if errors.Contains(err, errJWTExpiryKey) { - return auth.Key{}, ErrExpiry + return nil, ErrExpiry } - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + return nil, err } validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError { if t.Issuer() != issuerName { @@ -124,25 +116,29 @@ func (tok *tokenizer) Parse(token string) (auth.Key, error) { return nil }) if err := jwt.Validate(tkn, jwt.WithValidator(validator)); err != nil { - return auth.Key{}, errors.Wrap(ErrValidateJWTToken, err) + return nil, errors.Wrap(ErrValidateJWTToken, err) } - jsn, err := json.Marshal(tkn.PrivateClaims()) + return tkn, nil +} + +func toKey(tkn jwt.Token) (auth.Key, error) { + data, err := json.Marshal(tkn.PrivateClaims()) if err != nil { return auth.Key{}, errors.Wrap(ErrJSONHandle, err) } var key auth.Key - if err := json.Unmarshal(jsn, &key); err != nil { + if err := json.Unmarshal(data, &key); err != nil { return auth.Key{}, errors.Wrap(ErrJSONHandle, err) } tType, ok := tkn.Get(tokenType) if !ok { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + return auth.Key{}, err } ktype, err := strconv.ParseInt(fmt.Sprintf("%v", tType), 10, 64) if err != nil { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + return auth.Key{}, err } key.ID = tkn.JwtID() @@ -152,65 +148,5 @@ func (tok *tokenizer) Parse(token string) (auth.Key, error) { key.IssuedAt = tkn.IssuedAt() key.ExpiresAt = tkn.Expiration() - oauthProvider, ok := tkn.Get(oauthProviderField) - if ok { - provider, ok := oauthProvider.(string) - if !ok { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) - } - if provider != "" { - prov, ok := tok.providers[provider] - if !ok { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) - } - key.OAuth.Provider = prov.Name() - - key, err = parseOAuthToken(context.Background(), prov, tkn, key) - if err != nil { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - - return key, nil - } - } - - return key, nil -} - -func parseOAuthToken(ctx context.Context, provider oauth2.Provider, token jwt.Token, key auth.Key) (auth.Key, error) { - oauthToken, ok := token.Get(provider.Name()) - if ok { - claims, ok := oauthToken.(map[string]interface{}) - if !ok { - return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid claims for %s token", provider.Name())) - } - accessToken, ok := claims[oauthAccessTokenField].(string) - if !ok { - return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid access token claim for %s token", provider.Name())) - } - refreshToken, ok := claims[oauthRefreshTokenField].(string) - if !ok { - return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid refresh token claim for %s token", provider.Name())) - } - - switch provider.Validate(ctx, accessToken) { - case nil: - key.OAuth.AccessToken = accessToken - default: - token, err := provider.Refresh(ctx, refreshToken) - if err != nil { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - key.OAuth.AccessToken = token.AccessToken - key.OAuth.RefreshToken = token.RefreshToken - - return key, nil - } - - key.OAuth.RefreshToken = refreshToken - - return key, nil - } - return key, nil } diff --git a/auth/keys.go b/auth/keys.go index b5801b0443..030852e1ba 100644 --- a/auth/keys.go +++ b/auth/keys.go @@ -58,24 +58,16 @@ func (kt KeyType) String() string { } } -// OAuthToken represents OAuth token. -type OAuthToken struct { - Provider string `json:"provider,omitempty"` - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` -} - // Key represents API key. type Key struct { - ID string `json:"id,omitempty"` - Type KeyType `json:"type,omitempty"` - Issuer string `json:"issuer,omitempty"` - Subject string `json:"subject,omitempty"` // user ID - User string `json:"user,omitempty"` - Domain string `json:"domain,omitempty"` // domain user ID - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` - OAuth OAuthToken `json:"oauth,omitempty"` + ID string `json:"id,omitempty"` + Type KeyType `json:"type,omitempty"` + Issuer string `json:"issuer,omitempty"` + Subject string `json:"subject,omitempty"` // user ID + User string `json:"user,omitempty"` + Domain string `json:"domain,omitempty"` // domain user ID + IssuedAt time.Time `json:"issued_at,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` } func (key Key) String() string { diff --git a/auth/service.go b/auth/service.go index cdead70227..5e96ac7b9f 100644 --- a/auth/service.go +++ b/auth/service.go @@ -385,6 +385,7 @@ func (svc service) accessKey(ctx context.Context, key Key) (Token, error) { if err != nil { return Token{}, errors.Wrap(errIssueTmp, err) } + key.ExpiresAt = time.Now().Add(svc.refreshDuration) key.Type = RefreshKey refresh, err := svc.tokenizer.Issue(key) @@ -428,10 +429,6 @@ func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token key.User = k.User key.Type = AccessKey - key.OAuth.Provider = k.OAuth.Provider - key.OAuth.AccessToken = k.OAuth.AccessToken - key.OAuth.RefreshToken = k.OAuth.RefreshToken - key.Subject, err = svc.checkUserDomain(ctx, key) if err != nil { return Token{}, errors.Wrap(svcerr.ErrAuthorization, err) @@ -442,6 +439,7 @@ func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token if err != nil { return Token{}, errors.Wrap(errIssueTmp, err) } + key.ExpiresAt = time.Now().Add(svc.refreshDuration) key.Type = RefreshKey refresh, err := svc.tokenizer.Issue(key) diff --git a/auth/service_test.go b/auth/service_test.go index f46a5246c5..ba46445a83 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -15,7 +15,6 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" - oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -70,9 +69,7 @@ func newService() (auth.Service, string) { drepo = new(mocks.DomainsRepository) idProvider := uuid.NewMock() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - t := jwt.New([]byte(secret), provider) + t := jwt.New([]byte(secret)) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -89,9 +86,7 @@ func newService() (auth.Service, string) { func TestIssue(t *testing.T) { svc, accessToken := newService() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - n := jwt.New([]byte(secret), provider) + n := jwt.New([]byte(secret)) apikey := auth.Key{ IssuedAt: time.Now(), @@ -633,9 +628,7 @@ func TestIdentify(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) repocall4.Unset() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - te := jwt.New([]byte(secret), provider) + te := jwt.New([]byte(secret)) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -729,9 +722,7 @@ func TestAuthorize(t *testing.T) { repocall2.Unset() repocall3.Unset() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - te := jwt.New([]byte(secret), provider) + te := jwt.New([]byte(secret)) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 7349685bbf..d0924a5941 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -30,8 +30,6 @@ import ( grpcserver "github.com/absmach/magistrala/internal/server/grpc" httpserver "github.com/absmach/magistrala/internal/server/http" mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/oauth2" - "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/uuid" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/authzed-go/v1" @@ -46,14 +44,13 @@ import ( ) const ( - svcName = "auth" - envPrefixHTTP = "MG_AUTH_HTTP_" - envPrefixGrpc = "MG_AUTH_GRPC_" - envPrefixDB = "MG_AUTH_DB_" - envPrefixGoogle = "MG_GOOGLE_" - defDB = "auth" - defSvcHTTPPort = "8180" - defSvcGRPCPort = "8181" + svcName = "auth" + envPrefixHTTP = "MG_AUTH_HTTP_" + envPrefixGrpc = "MG_AUTH_GRPC_" + envPrefixDB = "MG_AUTH_DB_" + defDB = "auth" + defSvcHTTPPort = "8180" + defSvcGRPCPort = "8181" ) type config struct { @@ -130,15 +127,7 @@ func main() { return } - oauthConfig := oauth2.Config{} - if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) - exitCode = 1 - return - } - oauthProvider := google.NewProvider(oauthConfig, "", "") - - svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient, oauthProvider) + svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient) httpServerConfig := server.Config{Port: defSvcHTTPPort} if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { @@ -212,14 +201,14 @@ func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, sch return nil } -func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental, providers ...oauth2.Provider) auth.Service { +func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { database := postgres.NewDatabase(db, dbConfig, tracer) keysRepo := apostgres.New(database) domainsRepo := apostgres.NewDomainRepository(database) pa := spicedb.NewPolicyAgent(spicedbClient, logger) idProvider := uuid.New() - t := jwt.New([]byte(cfg.SecretKey), providers...) + t := jwt.New([]byte(cfg.SecretKey)) svc := auth.New(keysRepo, domainsRepo, idProvider, t, pa, cfg.AccessDuration, cfg.RefreshDuration, cfg.InvitationDuration) svc = api.LoggingMiddleware(svc, logger) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d5c278eab4..452cae381f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -125,10 +125,6 @@ services: MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} MG_AUTH_ADAPTER_INSTANCE_ID: ${MG_AUTH_ADAPTER_INSTANCE_ID} - MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} - MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} - MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} - MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} ports: - ${MG_AUTH_HTTP_PORT}:${MG_AUTH_HTTP_PORT} - ${MG_AUTH_GRPC_PORT}:${MG_AUTH_GRPC_PORT} diff --git a/pkg/oauth2/google/provider.go b/pkg/oauth2/google/provider.go index daba42b6e4..c4c6ad9bc2 100644 --- a/pkg/oauth2/google/provider.go +++ b/pkg/oauth2/google/provider.go @@ -6,11 +6,9 @@ package google import ( "context" "encoding/json" - "fmt" "io" "net/http" "net/url" - "strings" "time" mfclients "github.com/absmach/magistrala/pkg/clients" @@ -77,28 +75,29 @@ func (cfg *config) IsEnabled() bool { return cfg.config.ClientID != "" && cfg.config.ClientSecret != "" } -func (cfg *config) UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) { +func (cfg *config) Exchange(ctx context.Context, code string) (oauth2.Token, error) { token, err := cfg.config.Exchange(ctx, code) if err != nil { - return mfclients.Client{}, oauth2.Token{}, err - } - if token.RefreshToken == "" { - return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + return oauth2.Token{}, err } - resp, err := http.Get(userInfoURL + url.QueryEscape(token.AccessToken)) + return *token, nil +} + +func (cfg *config) UserInfo(accessToken string) (mfclients.Client, error) { + resp, err := http.Get(userInfoURL + url.QueryEscape(accessToken)) if err != nil { - return mfclients.Client{}, oauth2.Token{}, err + return mfclients.Client{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + return mfclients.Client{}, svcerr.ErrAuthentication } data, err := io.ReadAll(resp.Body) if err != nil { - return mfclients.Client{}, oauth2.Token{}, err + return mfclients.Client{}, err } var user struct { @@ -108,11 +107,11 @@ func (cfg *config) UserDetails(ctx context.Context, code string) (mfclients.Clie Picture string `json:"picture"` } if err := json.Unmarshal(data, &user); err != nil { - return mfclients.Client{}, oauth2.Token{}, err + return mfclients.Client{}, err } if user.ID == "" || user.Name == "" || user.Email == "" { - return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + return mfclients.Client{}, svcerr.ErrAuthentication } client := mfclients.Client{ @@ -128,56 +127,5 @@ func (cfg *config) UserDetails(ctx context.Context, code string) (mfclients.Clie Status: mfclients.EnabledStatus, } - return client, *token, nil -} - -func (cfg *config) Validate(ctx context.Context, token string) error { - client := &http.Client{ - Timeout: defTimeout, - } - req, err := http.NewRequest(http.MethodGet, tokenInfoURL+token, http.NoBody) - if err != nil { - return svcerr.ErrAuthentication - } - res, err := client.Do(req) - if err != nil { - return svcerr.ErrAuthentication - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return svcerr.ErrAuthentication - } - - return nil -} - -func (cfg *config) Refresh(ctx context.Context, token string) (oauth2.Token, error) { - payload := strings.NewReader(fmt.Sprintf("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", token, cfg.config.ClientID, cfg.config.ClientSecret)) - client := &http.Client{ - Timeout: defTimeout, - } - req, err := http.NewRequest(http.MethodPost, cfg.config.Endpoint.TokenURL, payload) - if err != nil { - return oauth2.Token{}, err - } - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, err := client.Do(req) - if err != nil { - return oauth2.Token{}, err - } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - return oauth2.Token{}, err - } - var tokenData oauth2.Token - if err := json.Unmarshal(body, &tokenData); err != nil { - return oauth2.Token{}, err - } - tokenData.RefreshToken = token - - return tokenData, nil + return client, nil } diff --git a/pkg/oauth2/mocks/provider.go b/pkg/oauth2/mocks/provider.go index ff3ad9fa69..0cdbf61279 100644 --- a/pkg/oauth2/mocks/provider.go +++ b/pkg/oauth2/mocks/provider.go @@ -37,6 +37,34 @@ func (_m *Provider) ErrorURL() string { return r0 } +// Exchange provides a mock function with given fields: ctx, code +func (_m *Provider) Exchange(ctx context.Context, code string) (xoauth2.Token, error) { + ret := _m.Called(ctx, code) + + if len(ret) == 0 { + panic("no return value specified for Exchange") + } + + var r0 xoauth2.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (xoauth2.Token, error)); ok { + return rf(ctx, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string) xoauth2.Token); ok { + r0 = rf(ctx, code) + } else { + r0 = ret.Get(0).(xoauth2.Token) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, code) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // IsEnabled provides a mock function with given fields: func (_m *Provider) IsEnabled() bool { ret := _m.Called() @@ -91,34 +119,6 @@ func (_m *Provider) RedirectURL() string { return r0 } -// Refresh provides a mock function with given fields: ctx, token -func (_m *Provider) Refresh(ctx context.Context, token string) (xoauth2.Token, error) { - ret := _m.Called(ctx, token) - - if len(ret) == 0 { - panic("no return value specified for Refresh") - } - - var r0 xoauth2.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (xoauth2.Token, error)); ok { - return rf(ctx, token) - } - if rf, ok := ret.Get(0).(func(context.Context, string) xoauth2.Token); ok { - r0 = rf(ctx, token) - } else { - r0 = ret.Get(0).(xoauth2.Token) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // State provides a mock function with given fields: func (_m *Provider) State() string { ret := _m.Called() @@ -137,57 +137,32 @@ func (_m *Provider) State() string { return r0 } -// UserDetails provides a mock function with given fields: ctx, code -func (_m *Provider) UserDetails(ctx context.Context, code string) (clients.Client, xoauth2.Token, error) { - ret := _m.Called(ctx, code) +// UserInfo provides a mock function with given fields: accessToken +func (_m *Provider) UserInfo(accessToken string) (clients.Client, error) { + ret := _m.Called(accessToken) if len(ret) == 0 { - panic("no return value specified for UserDetails") + panic("no return value specified for UserInfo") } var r0 clients.Client - var r1 xoauth2.Token - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, xoauth2.Token, error)); ok { - return rf(ctx, code) + var r1 error + if rf, ok := ret.Get(0).(func(string) (clients.Client, error)); ok { + return rf(accessToken) } - if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { - r0 = rf(ctx, code) + if rf, ok := ret.Get(0).(func(string) clients.Client); ok { + r0 = rf(accessToken) } else { r0 = ret.Get(0).(clients.Client) } - if rf, ok := ret.Get(1).(func(context.Context, string) xoauth2.Token); ok { - r1 = rf(ctx, code) - } else { - r1 = ret.Get(1).(xoauth2.Token) - } - - if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { - r2 = rf(ctx, code) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// Validate provides a mock function with given fields: ctx, token -func (_m *Provider) Validate(ctx context.Context, token string) error { - ret := _m.Called(ctx, token) - - if len(ret) == 0 { - panic("no return value specified for Validate") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, token) + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(accessToken) } else { - r0 = ret.Error(0) + r1 = ret.Error(1) } - return r0 + return r0, r1 } // NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff --git a/pkg/oauth2/oauth2.go b/pkg/oauth2/oauth2.go index 0a9bf00e9f..6a7a668ace 100644 --- a/pkg/oauth2/oauth2.go +++ b/pkg/oauth2/oauth2.go @@ -72,12 +72,9 @@ type Provider interface { // IsEnabled checks if the OAuth2 provider is enabled. IsEnabled() bool - // UserDetails retrieves the user's details and OAuth tokens from the OAuth2 provider. - UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) + // Exchange converts an authorization code into a token. + Exchange(ctx context.Context, code string) (oauth2.Token, error) - // Validate checks the validity of the access token. - Validate(ctx context.Context, token string) error - - // Refresh refreshes the access token using the refresh token. - Refresh(ctx context.Context, token string) (oauth2.Token, error) + // UserInfo retrieves the user's information using the access token. + UserInfo(accessToken string) (mfclients.Client, error) } diff --git a/users/api/clients.go b/users/api/clients.go index 65b6527d8e..e0b295d996 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -553,13 +553,19 @@ func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service) http.Handle } if code := r.FormValue("code"); code != "" { - client, token, err := oauth.UserDetails(r.Context(), code) + token, err := oauth.Exchange(r.Context(), code) if err != nil { http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) return } - jwt, err := svc.OAuthCallback(r.Context(), oauth.Name(), flow, token, client) + client, err := oauth.UserInfo(token.AccessToken) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) + return + } + + jwt, err := svc.OAuthCallback(r.Context(), flow, client) if err != nil { http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) return diff --git a/users/api/logging.go b/users/api/logging.go index e570995206..6d43161d61 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -12,7 +12,6 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" - "golang.org/x/oauth2" ) var _ users.Service = (*loggingMiddleware)(nil) @@ -400,11 +399,10 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id str return lm.svc.Identify(ctx, token) } -func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (token *magistrala.Token, err error) { +func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, state mgoauth2.State, client mgclients.Client) (token *magistrala.Token, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("provider", provider), slog.String("state", state.String()), slog.String("user_id", client.ID), } @@ -415,5 +413,5 @@ func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, provider string, } lm.logger.Info("OAuth callback completed successfully", args...) }(time.Now()) - return lm.svc.OAuthCallback(ctx, provider, state, oauthToken, client) + return lm.svc.OAuthCallback(ctx, state, client) } diff --git a/users/api/metrics.go b/users/api/metrics.go index 8c93d29b1f..b179696723 100644 --- a/users/api/metrics.go +++ b/users/api/metrics.go @@ -12,7 +12,6 @@ import ( mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-kit/kit/metrics" - "golang.org/x/oauth2" ) var _ users.Service = (*metricsMiddleware)(nil) @@ -194,11 +193,11 @@ func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (string return ms.svc.Identify(ctx, token) } -func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { - method := provider + "_oauth_callback_" + state.String() +func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, state mgoauth2.State, client mgclients.Client) (*magistrala.Token, error) { + method := "oauth_callback_" + state.String() defer func(begin time.Time) { ms.counter.With("method", method).Add(1) ms.latency.With("method", method).Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.OAuthCallback(ctx, provider, state, token, client) + return ms.svc.OAuthCallback(ctx, state, client) } diff --git a/users/clients.go b/users/clients.go index d6b3c328e4..58c2cb9cd1 100644 --- a/users/clients.go +++ b/users/clients.go @@ -9,7 +9,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/clients" mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" - "golang.org/x/oauth2" ) // Service specifies an API that must be fullfiled by the domain service @@ -78,5 +77,5 @@ type Service interface { // OAuthCallback handles the callback from any supported OAuth provider. // It processes the OAuth tokens and either signs in or signs up the user based on the provided state. - OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client clients.Client) (*magistrala.Token, error) + OAuthCallback(ctx context.Context, state mgoauth2.State, client clients.Client) (*magistrala.Token, error) } diff --git a/users/events/events.go b/users/events/events.go index 9b1692c2ff..aa5fe1cd2b 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -415,7 +415,6 @@ func (spre sendPasswordResetEvent) Encode() (map[string]interface{}, error) { type oauthCallbackEvent struct { state string - provider string clientID string } @@ -423,7 +422,6 @@ func (oce oauthCallbackEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ "operation": oauthCallback, "state": oce.state, - "provider": oce.provider, "client_id": oce.clientID, }, nil } diff --git a/users/events/streams.go b/users/events/streams.go index 1b9b801564..55c9ca1be0 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -12,7 +12,6 @@ import ( "github.com/absmach/magistrala/pkg/events/store" mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" - "golang.org/x/oauth2" ) const streamID = "magistrala.users" @@ -298,14 +297,13 @@ func (es *eventStore) SendPasswordReset(ctx context.Context, host, email, user, return es.Publish(ctx, event) } -func (es *eventStore) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { - token, err := es.svc.OAuthCallback(ctx, provider, state, oauthToken, client) +func (es *eventStore) OAuthCallback(ctx context.Context, state mgoauth2.State, client mgclients.Client) (*magistrala.Token, error) { + token, err := es.svc.OAuthCallback(ctx, state, client) if err != nil { return token, err } event := oauthCallbackEvent{ - provider: provider, state: state.String(), clientID: client.ID, } diff --git a/users/mocks/service.go b/users/mocks/service.go index 69bd46130d..f4a2d90b81 100644 --- a/users/mocks/service.go +++ b/users/mocks/service.go @@ -14,8 +14,6 @@ import ( mock "github.com/stretchr/testify/mock" oauth2 "github.com/absmach/magistrala/pkg/oauth2" - - xoauth2 "golang.org/x/oauth2" ) // Service is an autogenerated mock type for the Service type @@ -211,9 +209,9 @@ func (_m *Service) ListMembers(ctx context.Context, token string, objectKind str return r0, r1 } -// OAuthCallback provides a mock function with given fields: ctx, provider, state, token, client -func (_m *Service) OAuthCallback(ctx context.Context, provider string, state oauth2.State, token xoauth2.Token, client clients.Client) (*magistrala.Token, error) { - ret := _m.Called(ctx, provider, state, token, client) +// OAuthCallback provides a mock function with given fields: ctx, state, client +func (_m *Service) OAuthCallback(ctx context.Context, state oauth2.State, client clients.Client) (*magistrala.Token, error) { + ret := _m.Called(ctx, state, client) if len(ret) == 0 { panic("no return value specified for OAuthCallback") @@ -221,19 +219,19 @@ func (_m *Service) OAuthCallback(ctx context.Context, provider string, state oau var r0 *magistrala.Token var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) (*magistrala.Token, error)); ok { - return rf(ctx, provider, state, token, client) + if rf, ok := ret.Get(0).(func(context.Context, oauth2.State, clients.Client) (*magistrala.Token, error)); ok { + return rf(ctx, state, client) } - if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) *magistrala.Token); ok { - r0 = rf(ctx, provider, state, token, client) + if rf, ok := ret.Get(0).(func(context.Context, oauth2.State, clients.Client) *magistrala.Token); ok { + r0 = rf(ctx, state, client) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*magistrala.Token) } } - if rf, ok := ret.Get(1).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) error); ok { - r1 = rf(ctx, provider, state, token, client) + if rf, ok := ret.Get(1).(func(context.Context, oauth2.State, clients.Client) error); ok { + r1 = rf(ctx, state, client) } else { r1 = ret.Error(1) } diff --git a/users/service.go b/users/service.go index c566fff904..58a1b8b9a1 100644 --- a/users/service.go +++ b/users/service.go @@ -16,7 +16,6 @@ import ( svcerr "github.com/absmach/magistrala/pkg/errors/service" mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users/postgres" - "golang.org/x/oauth2" "golang.org/x/sync/errgroup" ) @@ -124,7 +123,7 @@ func (svc service) IssueToken(ctx context.Context, identity, secret, domainID st if domainID != "" { d = domainID } - return svc.auth.Issue(ctx, &magistrala.IssueReq{UserId: dbUser.ID, DomainId: &d, Type: 0}) + return svc.auth.Issue(ctx, &magistrala.IssueReq{UserId: dbUser.ID, DomainId: &d, Type: uint32(auth.AccessKey)}) } func (svc service) RefreshToken(ctx context.Context, refreshToken, domainID string) (*magistrala.Token, error) { @@ -577,7 +576,7 @@ func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, per return res.GetId(), nil } -func (svc service) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { +func (svc service) OAuthCallback(ctx context.Context, state mgoauth2.State, client mgclients.Client) (*magistrala.Token, error) { switch state { case mgoauth2.SignIn: rclient, err := svc.clients.RetrieveByIdentity(ctx, client.Credentials.Identity) @@ -588,11 +587,8 @@ func (svc service) OAuthCallback(ctx context.Context, provider string, state mgo return &magistrala.Token{}, err } claims := &magistrala.IssueReq{ - UserId: rclient.ID, - Type: 0, - OauthProvider: provider, - OauthAccessToken: token.AccessToken, - OauthRefreshToken: token.RefreshToken, + UserId: rclient.ID, + Type: uint32(auth.AccessKey), } return svc.auth.Issue(ctx, claims) case mgoauth2.SignUp: @@ -604,11 +600,8 @@ func (svc service) OAuthCallback(ctx context.Context, provider string, state mgo return &magistrala.Token{}, err } claims := &magistrala.IssueReq{ - UserId: rclient.ID, - Type: 0, - OauthProvider: provider, - OauthAccessToken: token.AccessToken, - OauthRefreshToken: token.RefreshToken, + UserId: rclient.ID, + Type: uint32(auth.AccessKey), } return svc.auth.Issue(ctx, claims) default: diff --git a/users/service_test.go b/users/service_test.go index 41ce243379..f96732d3c5 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "golang.org/x/oauth2" ) var ( @@ -2442,9 +2441,7 @@ func TestOAuthCallback(t *testing.T) { cases := []struct { desc string - provider string state mgoauth2.State - token oauth2.Token client mgclients.Client retrieveByIdentityResponse mgclients.Client retrieveByIdentityErr error @@ -2459,13 +2456,8 @@ func TestOAuthCallback(t *testing.T) { err error }{ { - desc: "oauth signin callback with successfully", - provider: "google", - state: mgoauth2.SignIn, - token: oauth2.Token{ - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, + desc: "oauth signin callback with successfully", + state: mgoauth2.SignIn, client: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "test@example.com", @@ -2482,13 +2474,8 @@ func TestOAuthCallback(t *testing.T) { err: nil, }, { - desc: "oauth signin callback with error", - provider: "google", - state: mgoauth2.SignIn, - token: oauth2.Token{ - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, + desc: "oauth signin callback with error", + state: mgoauth2.SignIn, client: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "test@example.com", @@ -2500,13 +2487,8 @@ func TestOAuthCallback(t *testing.T) { err: errors.New("user not signed up"), }, { - desc: "oauth signup callback with successfully", - provider: "google", - state: mgoauth2.SignUp, - token: oauth2.Token{ - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, + desc: "oauth signup callback with successfully", + state: mgoauth2.SignUp, client: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "test@example.com", @@ -2531,13 +2513,8 @@ func TestOAuthCallback(t *testing.T) { err: nil, }, { - desc: "oauth signup callback with error", - provider: "google", - state: mgoauth2.SignUp, - token: oauth2.Token{ - AccessToken: strings.Repeat("a", 10), - RefreshToken: strings.Repeat("b", 10), - }, + desc: "oauth signup callback with error", + state: mgoauth2.SignUp, client: mgclients.Client{ Credentials: mgclients.Credentials{ Identity: "test@example.com", @@ -2568,7 +2545,7 @@ func TestOAuthCallback(t *testing.T) { repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesErr) repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesErr) repoCall3 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) - token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + token, err := svc.OAuthCallback(context.Background(), tc.state, tc.client) if err == nil { assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) @@ -2581,7 +2558,7 @@ func TestOAuthCallback(t *testing.T) { case mgoauth2.SignIn: repoCall := cRepo.On("RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) - token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + token, err := svc.OAuthCallback(context.Background(), tc.state, tc.client) if err == nil { assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index 29788dbfec..24e8b7c900 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -12,7 +12,6 @@ import ( "github.com/absmach/magistrala/users" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "golang.org/x/oauth2" ) var _ users.Service = (*tracingMiddleware)(nil) @@ -196,13 +195,12 @@ func (tm *tracingMiddleware) Identify(ctx context.Context, token string) (string } // OAuthCallback traces the "OAuthCallback" operation of the wrapped clients.Service. -func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { +func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, state mgoauth2.State, client mgclients.Client) (*magistrala.Token, error) { ctx, span := tm.tracer.Start(ctx, "svc_oauth_callback", trace.WithAttributes( - attribute.String("provider", provider), attribute.String("state", state.String()), attribute.String("client_id", client.ID), )) defer span.End() - return tm.svc.OAuthCallback(ctx, provider, state, token, client) + return tm.svc.OAuthCallback(ctx, state, client) } From 6e6dfba3bfc63d2e708841062fc257dbc02686c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:53:05 +0100 Subject: [PATCH 61/71] NOISSUE - Bump github.com/jackc/pgx/v5 from 5.5.2 to 5.5.4 (#2116) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 78d3e7d33a..799e7eb445 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/ivanpirog/coloredcobra v1.0.1 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgtype v1.14.1 - github.com/jackc/pgx/v5 v5.5.2 + github.com/jackc/pgx/v5 v5.5.4 github.com/jmoiron/sqlx v1.3.5 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/mitchellh/mapstructure v1.5.0 diff --git a/go.sum b/go.sum index 738374d356..a84d835e35 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c h1:Dznn52SgVIVst9UyOT9brctYUgxs+CvVfPaC3jKrA50= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA= -github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= From 887072f97ccaed2dc30dffac4e856073dc44d115 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:32:29 +0300 Subject: [PATCH 62/71] NOISSUE - Convert SenML Payload Time To UnixNano (#2115) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: rodneyosodo --- pkg/transformers/json/transformer.go | 2 +- pkg/transformers/senml/transformer.go | 14 ++- pkg/transformers/senml/transformer_test.go | 2 +- pkg/transformers/transformer.go | 20 +++ pkg/transformers/transformer_test.go | 140 +++++++++++++++++++++ 5 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 pkg/transformers/transformer_test.go diff --git a/pkg/transformers/json/transformer.go b/pkg/transformers/json/transformer.go index fa032167df..cf26667972 100644 --- a/pkg/transformers/json/transformer.go +++ b/pkg/transformers/json/transformer.go @@ -187,7 +187,7 @@ func (ts *transformerService) transformTimeField(payload map[string]interface{}) return 0, err } - return t.UnixNano(), nil + return transformers.ToUnixNano(t.UnixNano()), nil } } diff --git a/pkg/transformers/senml/transformer.go b/pkg/transformers/senml/transformer.go index 1e253df632..cce7f31f5c 100644 --- a/pkg/transformers/senml/transformer.go +++ b/pkg/transformers/senml/transformer.go @@ -15,6 +15,8 @@ const ( JSON = "application/senml+json" // CBOR represents SenML in CBOR format content type. CBOR = "application/senml+cbor" + + maxRelativeTime = 1 << 28 ) var ( @@ -59,8 +61,16 @@ func (t transformer) Transform(msg *messaging.Message) (interface{}, error) { // Use reception timestamp if SenML messsage Time is missing t := v.Time if t == 0 { - // Convert the Unix timestamp in nanoseconds to float64 - t = float64(msg.GetCreated()) / float64(1e9) + t = float64(msg.GetCreated()) + } + + // If time is below 2**28 it is relative to the current time + // https://datatracker.ietf.org/doc/html/rfc8428#section-4.5.3 + if t >= maxRelativeTime { + t = transformers.ToUnixNano(t) + } + if v.UpdateTime >= maxRelativeTime { + v.UpdateTime = transformers.ToUnixNano(v.UpdateTime) } msgs[i] = Message{ diff --git a/pkg/transformers/senml/transformer_test.go b/pkg/transformers/senml/transformer_test.go index de57634168..defed27368 100644 --- a/pkg/transformers/senml/transformer_test.go +++ b/pkg/transformers/senml/transformer_test.go @@ -17,7 +17,7 @@ import ( func TestTransformJSON(t *testing.T) { // Following hex-encoded bytes correspond to the content of: - // [{-2: "base-name", -3: 100.0, -4: "base-unit", -1: 10, -5: 10.0, -6: 100.0, 0: "name", 1: "unit", 6: 300.0, 7: 150.0, 2: 42.0, 5: 10.0}] + // [{"bn":"base-name","bt":100,"bu":"base-unit","bver":10,"bv":10,"bs":100,"n":"name","u":"unit","t":300,"ut":150,"v":42,"s":10}] // For more details for mapping SenML labels to integers, please take a look here: https://tools.ietf.org/html/rfc8428#page-19. jsonBytes, err := hex.DecodeString("5b7b22626e223a22626173652d6e616d65222c226274223a3130302c226275223a22626173652d756e6974222c2262766572223a31302c226276223a31302c226273223a3130302c226e223a226e616d65222c2275223a22756e6974222c2274223a3330302c227574223a3135302c2276223a34322c2273223a31307d5d") assert.Nil(t, err, "Decoding JSON expected to succeed") diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go index 73ca7dd466..aa53887678 100644 --- a/pkg/transformers/transformer.go +++ b/pkg/transformers/transformer.go @@ -10,3 +10,23 @@ type Transformer interface { // Transform Magistrala message to any other format. Transform(msg *messaging.Message) (interface{}, error) } + +type number interface { + uint64 | int64 | float64 +} + +// ToUnixNano converts time to UnixNano time format. +func ToUnixNano[N number](t N) N { + switch { + case t == 0: + return 0 + case t >= 1e18: // Check if the value is in nanoseconds + return t + case t >= 1e15 && t < 1e18: // Check if the value is in milliseconds + return t * 1e3 + case t >= 1e12 && t < 1e15: // Check if the value is in microseconds + return t * 1e6 + default: // Assume it's in seconds (Unix time) + return t * 1e9 + } +} diff --git a/pkg/transformers/transformer_test.go b/pkg/transformers/transformer_test.go new file mode 100644 index 0000000000..bcaa4125b5 --- /dev/null +++ b/pkg/transformers/transformer_test.go @@ -0,0 +1,140 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package transformers_test + +import ( + "testing" + "time" + + "github.com/absmach/magistrala/pkg/transformers" +) + +var now = time.Now() + +func TestInt64ToUnixNano(t *testing.T) { + cases := []struct { + desc string + time int64 + want int64 + }{ + { + desc: "empty", + time: 0, + want: 0, + }, + { + desc: "unix", + time: now.Unix(), + want: now.Unix() * int64(time.Second), + }, + { + desc: "unix milli", + time: now.UnixMilli(), + want: now.UnixMilli() * int64(time.Millisecond), + }, + { + desc: "unix micro", + time: now.UnixMicro(), + want: now.UnixMicro() * int64(time.Microsecond), + }, + { + desc: "unix nano", + time: now.UnixNano(), + want: now.UnixNano(), + }, + { + desc: "1e9 nano", + time: time.Unix(1e9, 0).Unix(), + want: time.Unix(1e9, 0).UnixNano(), + }, + { + desc: "1e10 nano", + time: time.Unix(1e10, 0).Unix(), + want: time.Unix(1e10, 0).UnixNano(), + }, + { + desc: "1e12 nano", + time: time.UnixMilli(1e12).Unix(), + want: time.UnixMilli(1e12).UnixNano(), + }, + { + desc: "1e13 nano", + time: time.UnixMilli(1e13).Unix(), + want: time.UnixMilli(1e13).UnixNano(), + }, + { + desc: "1e15 nano", + time: time.UnixMicro(1e15).Unix(), + want: time.UnixMicro(1e15).UnixNano(), + }, + { + desc: "1e16 nano", + time: time.UnixMicro(1e16).Unix(), + want: time.UnixMicro(1e16).UnixNano(), + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got := transformers.ToUnixNano(c.time) + if got != c.want { + t.Errorf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) + } + t.Logf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) + }) + } +} + +func TestFloat64ToUnixNano(t *testing.T) { + cases := []struct { + desc string + time float64 + want float64 + }{ + { + desc: "empty", + time: 0, + want: 0, + }, + { + desc: "unix", + time: float64(now.Unix()), + want: float64(now.Unix() * int64(time.Second)), + }, + { + desc: "unix milli", + time: float64(now.UnixMilli()), + want: float64(now.UnixMilli() * int64(time.Millisecond)), + }, + { + desc: "unix micro", + time: float64(now.UnixMicro()), + want: float64(now.UnixMicro() * int64(time.Microsecond)), + }, + { + desc: "unix nano", + time: float64(now.UnixNano()), + want: float64(now.UnixNano()), + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got := transformers.ToUnixNano(c.time) + if got != c.want { + t.Errorf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) + } + t.Logf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) + }) + } +} + +func BenchmarkToUnixNano(b *testing.B) { + for i := 0; i < b.N; i++ { + transformers.ToUnixNano(now.Unix()) + transformers.ToUnixNano(now.UnixMilli()) + transformers.ToUnixNano(now.UnixMicro()) + transformers.ToUnixNano(now.UnixNano()) + } +} From 441d3a759734846dea2359e5bcb94a2147ef17bf Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:31:07 +0530 Subject: [PATCH 63/71] NOISSUE - Add Domain URL to CLI and SDK configuration (#2118) Signed-off-by: Arvindh --- cli/config.go | 79 ++++++++++++++++++++++++++++++++++++++----------- cmd/cli/main.go | 22 ++------------ config.toml | 11 +++---- 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/cli/config.go b/cli/config.go index ff641ab6d7..244f05bd6a 100644 --- a/cli/config.go +++ b/cli/config.go @@ -17,10 +17,28 @@ import ( "github.com/spf13/cobra" ) +const ( + defURL string = "http://localhost" + defUsersURL string = defURL + ":9002" + defThingsURL string = defURL + ":9000" + defReaderURL string = defURL + ":9011" + defBootstrapURL string = defURL + ":9013" + defDomainsURL string = defURL + ":8189" + defCertsURL string = defURL + ":9019" + defInvitationsURL string = defURL + ":9020" + defHTTPURL string = defURL + ":9016/http" + defTLSVerification bool = false + defOffset string = "0" + defLimit string = "10" + defTopic string = "" + defRawOutput string = "false" +) + type remotes struct { ThingsURL string `toml:"things_url"` UsersURL string `toml:"users_url"` ReaderURL string `toml:"reader_url"` + DomainsURL string `toml:"domains_url"` HTTPAdapterURL string `toml:"http_adapter_url"` BootstrapURL string `toml:"bootstrap_url"` CertsURL string `toml:"certs_url"` @@ -85,14 +103,21 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { case os.IsNotExist(err): defaultConfig := config{ Remotes: remotes{ - ThingsURL: "http://localhost:9000", - UsersURL: "http://localhost:9002", - ReaderURL: "http://localhost", - HTTPAdapterURL: "http://localhost/http:9016", - BootstrapURL: "http://localhost", - CertsURL: "http://localhost:9019", - TLSVerification: false, + ThingsURL: defThingsURL, + UsersURL: defUsersURL, + ReaderURL: defReaderURL, + DomainsURL: defDomainsURL, + HTTPAdapterURL: defHTTPURL, + BootstrapURL: defBootstrapURL, + CertsURL: defCertsURL, + TLSVerification: defTLSVerification, }, + Filter: filter{ + Offset: defOffset, + Limit: defLimit, + Topic: defTopic, + }, + RawOutput: defRawOutput, } buf, err := toml.Marshal(defaultConfig) if err != nil { @@ -110,7 +135,7 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { return sdkConf, err } - if config.Filter.Offset != "" { + if config.Filter.Offset != "" && Offset == 0 { offset, err := strconv.ParseUint(config.Filter.Offset, 10, 64) if err != nil { return sdkConf, err @@ -118,7 +143,7 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { Offset = offset } - if config.Filter.Limit != "" { + if config.Filter.Limit != "" && Limit == 0 { limit, err := strconv.ParseUint(config.Filter.Limit, 10, 64) if err != nil { return sdkConf, err @@ -126,7 +151,7 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { Limit = limit } - if config.Filter.Topic != "" { + if config.Filter.Topic != "" && Topic == "" { Topic = config.Filter.Topic } @@ -135,15 +160,35 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { if err != nil { return sdkConf, err } - RawOutput = rawOutput + // check for config file value or flag input value is true + RawOutput = rawOutput || RawOutput + } + + if sdkConf.ThingsURL == "" && config.Remotes.ThingsURL != "" { + sdkConf.ThingsURL = config.Remotes.ThingsURL + } + + if sdkConf.UsersURL == "" && config.Remotes.UsersURL != "" { + sdkConf.UsersURL = config.Remotes.UsersURL + } + + if sdkConf.ReaderURL == "" && config.Remotes.ReaderURL != "" { + sdkConf.ReaderURL = config.Remotes.ReaderURL + } + + if sdkConf.HTTPAdapterURL == "" && config.Remotes.HTTPAdapterURL != "" { + sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL + } + + if sdkConf.BootstrapURL == "" && config.Remotes.BootstrapURL != "" { + sdkConf.BootstrapURL = config.Remotes.BootstrapURL + } + + if sdkConf.CertsURL == "" && config.Remotes.CertsURL != "" { + sdkConf.CertsURL = config.Remotes.CertsURL } - sdkConf.ThingsURL = config.Remotes.ThingsURL - sdkConf.UsersURL = config.Remotes.UsersURL - sdkConf.ReaderURL = config.Remotes.ReaderURL - sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL - sdkConf.BootstrapURL = config.Remotes.BootstrapURL - sdkConf.CertsURL = config.Remotes.CertsURL + sdkConf.TLSVerification = config.Remotes.TLSVerification || sdkConf.TLSVerification return sdkConf, nil } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 8edf912988..38764c817d 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -5,7 +5,6 @@ package main import ( - "fmt" "log" "github.com/absmach/magistrala/cli" @@ -14,29 +13,14 @@ import ( ) const ( - defURL string = "http://localhost" - defUsersURL string = defURL + ":9002" - defThingsURL string = defURL + ":9000" - defBootstrapURL string = defURL + ":9013" - defDomainsURL string = defURL + ":8189" - defCertsURL string = defURL + ":9019" - defInvitationsURL string = defURL + ":9020" + defURL string = "http://localhost" ) func main() { msgContentType := string(sdk.CTJSONSenML) sdkConf := sdk.Config{ - ThingsURL: defThingsURL, - UsersURL: defUsersURL, - ReaderURL: defURL, - HTTPAdapterURL: fmt.Sprintf("%s/http", defURL), - BootstrapURL: defBootstrapURL, - CertsURL: defCertsURL, - DomainsURL: defDomainsURL, - InvitationsURL: defInvitationsURL, - MsgContentType: sdk.ContentType(msgContentType), - TLSVerification: false, - HostURL: defURL, + MsgContentType: sdk.ContentType(msgContentType), + HostURL: defURL, } // Root diff --git a/config.toml b/config.toml index e7a21fee98..2c724e571d 100644 --- a/config.toml +++ b/config.toml @@ -1,19 +1,20 @@ # Copyright (c) Abstract Machines # SPDX-License-Identifier: Apache-2.0 -raw_output = "" +raw_output = "false" user_token = "" [filter] - limit = "" - offset = "" + limit = "10" + offset = "0" topic = "" [remotes] bootstrap_url = "http://localhost:9013" certs_url = "http://localhost:9019" - http_adapter_url = "http://localhost/http" - reader_url = "http://localhost:9009" + domains_url = "http://localhost:8189" + http_adapter_url = "http://localhost:9016/http" + reader_url = "http://localhost:9011" things_url = "http://localhost:9000" tls_verification = false users_url = "http://localhost:9002" From f5d0c4126b3710ebf942df086f7edef82608e2f2 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:16:07 +0300 Subject: [PATCH 64/71] NOISSUE - Add property based testing to users service (#2087) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/api-tests.yml | 58 ++++++ .gitignore | 3 + Makefile | 16 +- api/openapi/users.yml | 287 ++++++++++++++++++++++++++++- auth/postgres/domains.go | 20 +- auth/service.go | 31 +--- internal/api/common.go | 27 ++- internal/api/common_test.go | 4 +- internal/apiutil/errors.go | 6 +- internal/groups/api/responses.go | 4 +- internal/groups/postgres/groups.go | 16 +- internal/groups/service.go | 14 +- internal/groups/service_test.go | 5 +- internal/postgres/common.go | 33 ---- invitations/api/endpoint_test.go | 2 +- invitations/api/requests_test.go | 11 +- invitations/invitations.go | 11 +- invitations/invitations_test.go | 11 +- invitations/service_test.go | 4 +- pkg/clients/postgres/clients.go | 12 +- pkg/clients/status.go | 4 - pkg/errors/repository/types.go | 3 + pkg/errors/service/types.go | 18 ++ pkg/errors/types.go | 3 + pkg/groups/errors.go | 3 - pkg/sdk/go/channels_test.go | 8 +- pkg/sdk/go/groups_test.go | 8 +- pkg/sdk/go/things_test.go | 8 +- pkg/sdk/go/users_test.go | 13 +- things/service.go | 2 +- things/service_test.go | 8 +- users/api/clients.go | 32 ++-- users/api/endpoint_test.go | 57 +----- users/api/responses.go | 4 +- users/postgres/clients.go | 12 +- users/postgres/clients_test.go | 3 +- users/service.go | 21 +-- users/service_test.go | 6 +- 38 files changed, 523 insertions(+), 265 deletions(-) create mode 100644 .github/workflows/api-tests.yml diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml new file mode 100644 index 0000000000..d380cf9e9c --- /dev/null +++ b/.github/workflows/api-tests.yml @@ -0,0 +1,58 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +name: Property Based Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + TOKENS_URL: http://localhost:9002/users/tokens/issue + DOMAINS_URL: http://localhost:8189/domains + USER_IDENTITY: admin@example.com + USER_SECRET: 12345678 + DOMAIN_NAME: demo-test + +jobs: + api-test: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + cache-dependency-path: "go.sum" + + - name: Build images + run: make all -j $(nproc) && make dockers_dev -j $(nproc) + + - name: Start containers + run: make run up args="-d" && sleep 10 + + - name: Set access token + run: | + export USER_TOKEN=$(curl -sSX POST $TOKENS_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\"}" | jq -r .access_token) + export DOMAIN_ID=$(curl -sSX POST $DOMAINS_URL -H "Content-Type: application/json" -H "Authorization: Bearer $USER_TOKEN" -d "{\"name\":\"$DOMAIN_NAME\",\"alias\":\"$DOMAIN_NAME\"}" | jq -r .id) + export USER_TOKEN=$(curl -sSX POST $TOKENS_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\",\"domain_id\": \"$DOMAIN_ID\"}" | jq -r .access_token) + echo "USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV + + - name: Run Users API tests + uses: schemathesis/action@v1 + with: + schema: api/openapi/users.yml + base-url: http://localhost:9002 + checks: all + report: false + args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' + + - name: Stop containers + if: always() + run: make run down args="-v" diff --git a/.gitignore b/.gitignore index 8d9ae5778f..60286b5319 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ tools/provision/mgconn.toml # coverage files coverage + +# Schemathesis +.hypothesis diff --git a/Makefile b/Makefile index d1de472928..58eb2cf3bb 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ FILTERED_SERVICES = $(filter-out $(RUN_ADDON_ARGS), $(SERVICES)) all: $(SERVICES) -.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs +.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs test_api clean: rm -rf ${BUILD_DIR} @@ -129,6 +129,20 @@ test: mocks done go test -v --race -count 1 -tags test -coverprofile=coverage/coverage.out $$(go list ./... | grep -v 'consumers\|readers\|postgres\|internal\|opcua\|cmd') +test_api: +ifeq ($(USER_TOKEN),) + @echo "env variable USER_TOKEN is empty" + exit 1 +endif + @which st > /dev/null || (echo "schemathesis not found, please install it from https://github.com/schemathesis/schemathesis#getting-started" && exit 1) + st run api/openapi/users.yml \ + --checks all \ + --base-url http://localhost:9002 \ + --header "Authorization: Bearer $(USER_TOKEN)" \ + --contrib-openapi-formats-uuid \ + --hypothesis-suppress-health-check=filter_too_much \ + --stateful=links + proto: protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./*.proto diff --git a/api/openapi/users.yml b/api/openapi/users.yml index e4dd97b3b6..3c2bfb5dcb 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -34,6 +34,7 @@ tags: paths: /users: post: + operationId: createUser tags: - Users summary: Registers user account @@ -49,14 +50,19 @@ paths: description: Failed due to malformed JSON. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "409": description: Failed due to using an existing identity. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" get: + operationId: listUsers tags: - Users summary: List users @@ -93,6 +99,7 @@ paths: /users/profile: get: + operationId: getProfile summary: Gets info on currently logged in user. description: | Gets info on currently logged in user. Info is obtained using @@ -113,6 +120,7 @@ paths: /users/{userID}: get: + operationId: getUser summary: Retrieves a user description: | Retrieves a specific user that is identifier by the user ID. @@ -129,6 +137,8 @@ paths: 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": @@ -137,6 +147,7 @@ paths: $ref: "#/components/responses/ServiceError" patch: + operationId: updateUser summary: Updates name and metadata of the user. description: | Updates name and metadata of the user with provided ID. Name and metadata @@ -154,15 +165,24 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/tags: patch: + operationId: updateUserTags summary: Updates tags the user. description: | Updates tags of the user with provided ID. Tags is updated using @@ -180,15 +200,22 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/identity: patch: + operationId: updateUserIdentity summary: Updates Identity of the user. description: | Updates identity of the user with provided ID. Identity is @@ -206,15 +233,24 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/role: patch: + operationId: updateUserRole summary: Updates the user role. description: | Updates role for the user with provided ID. @@ -231,15 +267,22 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/disable: post: + operationId: disableUser summary: Disables a user description: | Disables a specific user that is identifier by the user ID. @@ -256,8 +299,14 @@ paths: 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. + "409": + description: Failed due to already disabled user. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -265,6 +314,7 @@ paths: /users/{userID}/enable: post: + operationId: enableUser summary: Enables a user description: | Enables a specific user that is identifier by the user ID. @@ -281,8 +331,14 @@ paths: 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. + "409": + description: Failed due to already enabled user. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -290,6 +346,7 @@ paths: /users/secret: patch: + operationId: updateUserSecret summary: Updates Secret of currently logged in user. description: | Updates secret of currently logged in user. Secret is updated using @@ -305,15 +362,20 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. - "404": - description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "404": + description: Failed due to non existing user. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /password/reset-request: post: + operationId: requestPasswordReset summary: User password reset request description: | Generates a reset token and sends and @@ -331,11 +393,14 @@ paths: description: Failed due to malformed JSON. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /password/reset: put: + operationId: resetPassword summary: User password reset endpoint description: | When user gets reset token, after he submitted @@ -350,13 +415,18 @@ paths: description: User link . "400": description: Failed due to malformed JSON. + "401": + description: Missing or invalid access token provided. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/users: get: + operationId: listUsersInGroup tags: - Users summary: List users in a group @@ -383,6 +453,8 @@ paths: description: | Missing or invalid access token provided. This endpoint is available only for administrators. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. "422": @@ -392,6 +464,7 @@ paths: /channels/{channelID}/users: get: + operationId: listUsersInChannel tags: - Users summary: List users in a channel @@ -418,6 +491,8 @@ paths: description: | Missing or invalid access token provided. This endpoint is available only for administrators. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. "422": @@ -427,6 +502,7 @@ paths: /users/tokens/issue: post: + operationId: issueToken summary: Issue Token description: | Issue Access and Refresh Token used for authenticating into the system. @@ -437,8 +513,12 @@ paths: responses: "200": $ref: "#/components/responses/TokenRes" + "400": + description: Failed due to malformed JSON. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -446,6 +526,7 @@ paths: /users/tokens/refresh: post: + operationId: refreshToken summary: Refresh Token description: | Refreshes Access and Refresh Token used for authenticating into the system. @@ -456,8 +537,12 @@ paths: responses: "200": $ref: "#/components/responses/TokenRes" + "400": + description: Failed due to malformed JSON. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -465,6 +550,7 @@ paths: /groups: post: + operationId: createGroup tags: - Groups summary: Creates new group @@ -482,14 +568,19 @@ paths: description: Failed due to malformed JSON. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "409": description: Failed due to using an existing identity. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" get: + operationId: listGroups summary: Lists groups. description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -515,13 +606,18 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}: get: + operationId: getGroup summary: Gets group info. description: | Gets info on a group specified by id. @@ -538,12 +634,17 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" put: + operationId: updateGroup summary: Updates group data. description: | Updates Name, Description or Metadata of a group. @@ -562,8 +663,16 @@ paths: 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: Group does not exist. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" delete: @@ -589,6 +698,7 @@ paths: /groups/{groupID}/children: get: + operationId: listChildren summary: List children of a certain group description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -615,13 +725,18 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/parents: get: + operationId: listParents summary: List parents of a certain group description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -648,13 +763,18 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/enable: post: + operationId: enableGroup summary: Enables a group description: | Enables a specific group that is identifier by the group ID. @@ -671,8 +791,14 @@ paths: 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. + "409": + description: Failed due to already enabled group. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -680,6 +806,7 @@ paths: /groups/{groupID}/disable: post: + operationId: disableGroup summary: Disables a group description: | Disables a specific group that is identifier by the group ID. @@ -696,8 +823,14 @@ paths: 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. + "409": + description: Failed due to already disabled group. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -705,6 +838,7 @@ paths: /groups/{groupID}/users/assign: post: + operationId: assignUser summary: Assigns a user to a group description: | Assigns a specific user to a group that is identifier by the group ID. @@ -723,8 +857,12 @@ paths: description: Failed due to malformed group's ID. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -732,6 +870,7 @@ paths: /groups/{groupID}/users/unassign: post: + operationId: unassignUser summary: Unassigns a user to a group description: | Unassigns a specific user to a group that is identifier by the group ID. @@ -750,8 +889,12 @@ paths: description: Failed due to malformed group's ID. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -759,6 +902,7 @@ paths: /channels/{memberID}/groups: get: + operationId: listGroupsInChannel summary: Get group associated with the member description: | Gets groups associated with the channel member specified by id. @@ -780,13 +924,18 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{memberID}/groups: get: + operationId: listGroupsByUser summary: Get group associated with the member description: | Gets groups associated with the user member specified by id. @@ -808,8 +957,12 @@ paths: 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: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /domains/{domainID}/users: @@ -845,6 +998,7 @@ paths: $ref: "#/components/responses/ServiceError" /health: get: + operationId: health summary: Retrieves service health check info. tags: - health @@ -1128,7 +1282,7 @@ components: required: - groups - total - - level + - offset MembersPage: type: object @@ -1212,7 +1366,7 @@ components: properties: role: type: string - enum: ["admin","user"] + enum: ["admin", "user"] example: user description: User role example. required: @@ -1351,6 +1505,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1369,6 +1526,7 @@ components: in: query schema: type: string + pattern: "^[^\u0000-\u001F]*$" required: false example: "admin@example.com" @@ -1429,6 +1587,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1439,6 +1600,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1449,6 +1613,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1667,6 +1834,43 @@ components: application/json: schema: $ref: "#/components/schemas/User" + links: + get: + operationId: getUser + parameters: + userID: $response.body#/id + get_groups: + operationId: listUsersInGroup + parameters: + groupID: $response.body#/id + get_channels: + operationId: listUsersInChannel + parameters: + channelID: $response.body#/id + update: + operationId: updateUser + parameters: + userID: $response.body#/id + update_tags: + operationId: updateUserTags + parameters: + userID: $response.body#/id + update_identity: + operationId: updateUserIdentity + parameters: + userID: $response.body#/id + update_role: + operationId: updateUserRole + parameters: + userID: $response.body#/id + disable: + operationId: disableUser + parameters: + userID: $response.body#/id + enable: + operationId: enableUser + parameters: + userID: $response.body#/id UserRes: description: Data retrieved. @@ -1674,6 +1878,15 @@ components: application/json: schema: $ref: "#/components/schemas/User" + links: + get_groups: + operationId: listUsersInGroup + parameters: + groupID: $response.body#/id + get_channels: + operationId: listUsersInChannel + parameters: + channelID: $response.body#/id UserPageRes: description: Data retrieved. @@ -1694,6 +1907,47 @@ components: application/json: schema: $ref: "#/components/schemas/Group" + links: + get: + operationId: getGroup + parameters: + groupID: $response.body#/id + get_children: + operationId: listChildren + parameters: + groupID: $response.body#/id + get_parent: + operationId: listParents + parameters: + groupID: $response.body#/id + get_channels: + operationId: listGroupsInChannel + parameters: + memberID: $response.body#/id + get_users: + operationId: listGroupsByUser + parameters: + memberID: $response.body#/id + update: + operationId: updateGroup + parameters: + groupID: $response.body#/id + disable: + operationId: disableGroup + parameters: + groupID: $response.body#/id + enable: + operationId: enableGroup + parameters: + groupID: $response.body#/id + assign: + operationId: assignUser + parameters: + groupID: $response.body#/id + unassign: + operationId: unassignUser + parameters: + groupID: $response.body#/id GroupRes: description: Data retrieved. @@ -1701,6 +1955,31 @@ components: application/json: schema: $ref: "#/components/schemas/Group" + links: + get_children: + operationId: listChildren + parameters: + groupID: $response.body#/id + get_parent: + operationId: listParents + parameters: + groupID: $response.body#/id + get_channels: + operationId: listGroupsInChannel + parameters: + memberID: $response.body#/id + get_users: + operationId: listGroupsByUser + parameters: + memberID: $response.body#/id + assign: + operationId: assignUser + parameters: + groupID: $response.body#/id + unassign: + operationId: unassignUser + parameters: + groupID: $response.body#/id GroupPageRes: description: Data retrieved. diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index e622e7ca16..6144c3712b 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -107,13 +107,13 @@ func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id stri rows, err := repo.db.QueryxContext(ctx, q, id, subject) if err != nil { - return []string{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return []string{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } permissions := []string{} @@ -142,18 +142,18 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM domains d" @@ -163,7 +163,7 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } pm.Total = total @@ -199,18 +199,18 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM domains d JOIN policies pc ON pc.object_id = d.id" @@ -220,7 +220,7 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } pm.Total = total diff --git a/auth/service.go b/auth/service.go index 5e96ac7b9f..ba2a4781a4 100644 --- a/auth/service.go +++ b/auth/service.go @@ -18,27 +18,6 @@ import ( const recoveryDuration = 5 * time.Minute var ( - errRollbackPolicy = errors.New("failed to rollback policy") - errRemoveLocalPolicy = errors.New("failed to remove from local policy copy") - errRemovePolicyEngine = errors.New("failed to remove from policy engine") -) - -var ( - // ErrFailedToRetrieveMembers failed to retrieve group members. - ErrFailedToRetrieveMembers = errors.New("failed to retrieve group members") - - // ErrFailedToRetrieveMembership failed to retrieve memberships. - ErrFailedToRetrieveMembership = errors.New("failed to retrieve memberships") - - // ErrFailedToRetrieveAll failed to retrieve groups. - ErrFailedToRetrieveAll = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveParents failed to retrieve groups. - ErrFailedToRetrieveParents = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveChildren failed to retrieve groups. - ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups") - // ErrExpiry indicates that the token is expired. ErrExpiry = errors.New("token is expired") @@ -51,6 +30,9 @@ var ( errCreateDomainPolicy = errors.New("failed to create domain policy") errAddPolicies = errors.New("failed to add policies") errRemovePolicies = errors.New("failed to remove the policies") + errRollbackPolicy = errors.New("failed to rollback policy") + errRemoveLocalPolicy = errors.New("failed to remove from local policy copy") + errRemovePolicyEngine = errors.New("failed to remove from policy engine") ) // Authn specifies an API that must be fullfiled by the domain service @@ -358,7 +340,12 @@ func (svc service) CountSubjects(ctx context.Context, pr PolicyReq) (int, error) } func (svc service) ListPermissions(ctx context.Context, pr PolicyReq, filterPermisions []string) (Permissions, error) { - return svc.agent.RetrievePermissions(ctx, pr, filterPermisions) + pers, err := svc.agent.RetrievePermissions(ctx, pr, filterPermisions) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + return pers, nil } func (svc service) tmpKey(duration time.Duration, key Key) (Token, error) { diff --git a/internal/api/common.go b/internal/api/common.go index 998402ad1c..e4b23396a2 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -10,7 +10,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/apiutil" - "github.com/absmach/magistrala/internal/postgres" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -115,25 +114,32 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrLimitSize), errors.Contains(err, apiutil.ErrBearerKey), errors.Contains(err, apiutil.ErrNameSize), - errors.Contains(err, svcerr.ErrInvalidStatus), errors.Contains(err, apiutil.ErrInvalidIDFormat), - errors.Contains(err, apiutil.ErrInvalidQueryParams), errors.Contains(err, apiutil.ErrInvalidStatus), - errors.Contains(err, apiutil.ErrMissingRelation), - errors.Contains(err, apiutil.ErrValidation), + errors.Contains(err, svcerr.ErrInvalidStatus), + errors.Contains(err, apiutil.ErrInvitationState), + errors.Contains(err, apiutil.ErrInvalidRole), + errors.Contains(err, apiutil.ErrMissingEmail), + errors.Contains(err, apiutil.ErrMissingHost), errors.Contains(err, apiutil.ErrMissingIdentity), errors.Contains(err, apiutil.ErrMissingSecret), errors.Contains(err, apiutil.ErrMissingPass), errors.Contains(err, apiutil.ErrMissingConfPass), - errors.Contains(err, apiutil.ErrPasswordFormat): + errors.Contains(err, apiutil.ErrInvalidResetPass), + errors.Contains(err, apiutil.ErrMissingRelation), + errors.Contains(err, svcerr.ErrPasswordFormat), + errors.Contains(err, apiutil.ErrInvalidLevel), + errors.Contains(err, apiutil.ErrInvalidQueryParams), + errors.Contains(err, apiutil.ErrValidation): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), + errors.Contains(err, svcerr.ErrLogin), errors.Contains(err, apiutil.ErrBearerToken): w.WriteHeader(http.StatusUnauthorized) case errors.Contains(err, svcerr.ErrNotFound): w.WriteHeader(http.StatusNotFound) - case errors.Contains(err, postgres.ErrMemberAlreadyAssigned), - errors.Contains(err, svcerr.ErrConflict): + case errors.Contains(err, svcerr.ErrConflict), + errors.Contains(err, errors.ErrStatusAlreadyAssigned): w.WriteHeader(http.StatusConflict) case errors.Contains(err, svcerr.ErrAuthorization), errors.Contains(err, svcerr.ErrDomainAuthorization): @@ -142,9 +148,12 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(http.StatusUnsupportedMediaType) case errors.Contains(err, svcerr.ErrCreateEntity), errors.Contains(err, svcerr.ErrUpdateEntity), + errors.Contains(err, svcerr.ErrFailedUpdateRole), errors.Contains(err, svcerr.ErrViewEntity), + errors.Contains(err, svcerr.ErrAddPolicies), + errors.Contains(err, svcerr.ErrDeletePolicies), errors.Contains(err, svcerr.ErrRemoveEntity): - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusUnprocessableEntity) default: w.WriteHeader(http.StatusInternalServerError) } diff --git a/internal/api/common_test.go b/internal/api/common_test.go index ac1adf40c4..82d654a7f0 100644 --- a/internal/api/common_test.go +++ b/internal/api/common_test.go @@ -294,14 +294,14 @@ func TestEncodeError(t *testing.T) { code: http.StatusUnsupportedMediaType, }, { - desc: "InternalServerError", + desc: "StatusUnprocessableEntity", errs: []error{ svcerr.ErrCreateEntity, svcerr.ErrUpdateEntity, svcerr.ErrViewEntity, svcerr.ErrRemoveEntity, }, - code: http.StatusInternalServerError, + code: http.StatusUnprocessableEntity, }, { desc: "InternalServerError", diff --git a/internal/apiutil/errors.go b/internal/apiutil/errors.go index 2f2261fa03..dabe5881d2 100644 --- a/internal/apiutil/errors.go +++ b/internal/apiutil/errors.go @@ -114,12 +114,12 @@ var ( // ErrMissingRelation indicates missing relation. ErrMissingRelation = errors.New("missing relation") + // ErrInvalidRelation indicates an invalid relation. + ErrInvalidRelation = errors.New("invalid relation") + // ErrInvalidAPIKey indicates an invalid API key type. ErrInvalidAPIKey = errors.New("invalid api key type") - // ErrMaxLevelExceeded indicates an invalid group level. - ErrMaxLevelExceeded = errors.New("invalid group level (should be lower than 5)") - // ErrBootstrapState indicates an invalid bootstrap state. ErrBootstrapState = errors.New("invalid bootstrap state") diff --git a/internal/groups/api/responses.go b/internal/groups/api/responses.go index 08b4b314ce..a9fbcc5efd 100644 --- a/internal/groups/api/responses.go +++ b/internal/groups/api/responses.go @@ -86,9 +86,9 @@ type groupPageRes struct { } type pageRes struct { - Limit uint64 `json:"limit"` + Limit uint64 `json:"limit,omitempty"` Offset uint64 `json:"offset"` - Total uint64 `json:"total,omitempty"` + Total uint64 `json:"total"` Level uint64 `json:"level,omitempty"` } diff --git a/internal/groups/postgres/groups.go b/internal/groups/postgres/groups.go index f0f332bff3..d2564cf67f 100644 --- a/internal/groups/postgres/groups.go +++ b/internal/groups/postgres/groups.go @@ -160,17 +160,17 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( dbPage, err := toDBGroupPage(gm) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() items, err := repo.processRows(rows) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM groups g" @@ -180,7 +180,7 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } page := gm @@ -208,17 +208,17 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, dbPage, err := toDBGroupPage(gm) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() items, err := repo.processRows(rows) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM groups g" @@ -228,7 +228,7 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } page := gm diff --git a/internal/groups/service.go b/internal/groups/service.go index 819889f90c..d802caeba3 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -22,8 +22,6 @@ import ( var ( errParentUnAuthz = errors.New("failed to authorize parent group") errMemberKind = errors.New("invalid member kind") - errAddPolicies = errors.New("failed to add policies") - errDeletePolicies = errors.New("failed to delete policies") errRetrieveGroups = errors.New("failed to retrieve groups") errGroupIDs = errors.New("invalid group ids") ) @@ -286,7 +284,7 @@ func (svc service) checkSuperAdmin(ctx context.Context, userID string) error { Object: auth.MagistralaObject, }) if err != nil { - return err + return errors.Wrap(svcerr.ErrAuthorization, err) } if !res.Authorized { return svcerr.ErrAuthorization @@ -447,7 +445,7 @@ func (svc service) Assign(ctx context.Context, token, groupID, relation, memberK } if _, err := svc.auth.AddPolicies(ctx, &policies); err != nil { - return errors.Wrap(errAddPolicies, err) + return errors.Wrap(svcerr.ErrAddPolicies, err) } return nil @@ -598,7 +596,7 @@ func (svc service) Unassign(ctx context.Context, token, groupID, relation, membe } if _, err := svc.auth.DeletePolicies(ctx, &policies); err != nil { - return errors.Wrap(errDeletePolicies, err) + return errors.Wrap(svcerr.ErrDeletePolicies, err) } return nil } @@ -696,7 +694,7 @@ func (svc service) changeGroupStatus(ctx context.Context, token string, group gr return groups.Group{}, err } if dbGroup.Status == group.Status { - return groups.Group{}, mgclients.ErrStatusAlreadyAssigned + return groups.Group{}, errors.ErrStatusAlreadyAssigned } group.UpdatedBy = id @@ -725,7 +723,7 @@ func (svc service) authorizeToken(ctx context.Context, subjectType, subject, per } res, err := svc.auth.Authorize(ctx, req) if err != nil { - return "", err + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { return "", svcerr.ErrAuthorization @@ -745,7 +743,7 @@ func (svc service) authorizeKind(ctx context.Context, domainID, subjectType, sub } res, err := svc.auth.Authorize(ctx, req) if err != nil { - return "", err + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { return "", svcerr.ErrAuthorization diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index bfc4a17822..d7ef70d437 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -17,7 +17,6 @@ import ( "github.com/absmach/magistrala/internal/groups" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/clients" - mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -587,7 +586,7 @@ func TestEnableGroup(t *testing.T) { retrieveResp: mggroups.Group{ Status: clients.Status(groups.EnabledStatus), }, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "with retrieve error", @@ -687,7 +686,7 @@ func TestDisableGroup(t *testing.T) { retrieveResp: mggroups.Group{ Status: clients.Status(groups.DisabledStatus), }, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "with retrieve error", diff --git a/internal/postgres/common.go b/internal/postgres/common.go index 1c5b183712..c34ac9fb41 100644 --- a/internal/postgres/common.go +++ b/internal/postgres/common.go @@ -6,42 +6,9 @@ package postgres import ( "context" "encoding/json" - "errors" "fmt" ) -var ( - // ErrAssignToGroup indicates failure to assign member to a group. - ErrAssignToGroup = errors.New("failed to assign member to a group") - - // ErrUnassignFromGroup indicates failure to unassign member from a group. - ErrUnassignFromGroup = errors.New("failed to unassign member from a group") - - // ErrMissingParent indicates that parent can't be found. - ErrMissingParent = errors.New("failed to retrieve parent") - - // ErrGroupNotEmpty indicates group is not empty, can't be deleted. - ErrGroupNotEmpty = errors.New("group is not empty") - - // ErrMemberAlreadyAssigned indicates that members is already assigned. - ErrMemberAlreadyAssigned = errors.New("member is already assigned") - - // ErrFailedToRetrieveMembers failed to retrieve group members. - ErrFailedToRetrieveMembers = errors.New("failed to retrieve group members") - - // ErrFailedToRetrieveMembership failed to retrieve memberships. - ErrFailedToRetrieveMembership = errors.New("failed to retrieve memberships") - - // ErrFailedToRetrieveAll failed to retrieve groups. - ErrFailedToRetrieveAll = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveParents failed to retrieve groups. - ErrFailedToRetrieveParents = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveChildren failed to retrieve groups. - ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups") -) - func CreateMetadataQuery(entity string, um map[string]interface{}) (string, []byte, error) { if len(um) == 0 { return "", nil, nil diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go index dc39fb9fac..7810eb6915 100644 --- a/invitations/api/endpoint_test.go +++ b/invitations/api/endpoint_test.go @@ -266,7 +266,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid state", token: validToken, query: "state=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, diff --git a/invitations/api/requests_test.go b/invitations/api/requests_test.go index 1f1192a92d..a2e22660a6 100644 --- a/invitations/api/requests_test.go +++ b/invitations/api/requests_test.go @@ -4,7 +4,6 @@ package api import ( - "errors" "fmt" "testing" @@ -14,11 +13,7 @@ import ( "github.com/stretchr/testify/assert" ) -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") - valid = "valid" -) +var valid = "valid" func TestSendInvitationReqValidation(t *testing.T) { cases := []struct { @@ -79,7 +74,7 @@ func TestSendInvitationReqValidation(t *testing.T) { Relation: "", Resend: true, }, - err: errMissingRelation, + err: apiutil.ErrMissingRelation, }, { desc: "invalid relation", @@ -90,7 +85,7 @@ func TestSendInvitationReqValidation(t *testing.T) { Relation: "invalid", Resend: true, }, - err: errInvalidRelation, + err: apiutil.ErrInvalidRelation, }, } diff --git a/invitations/invitations.go b/invitations/invitations.go index 354dd3108c..0cf6c5a2a7 100644 --- a/invitations/invitations.go +++ b/invitations/invitations.go @@ -6,15 +6,10 @@ package invitations import ( "context" "encoding/json" - "errors" "time" "github.com/absmach/magistrala/auth" -) - -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") + "github.com/absmach/magistrala/internal/apiutil" ) // Invitation is an invitation to join a domain. @@ -122,7 +117,7 @@ type Repository interface { // It returns an error if the relation is empty or invalid. func CheckRelation(relation string) error { if relation == "" { - return errMissingRelation + return apiutil.ErrMissingRelation } if relation != auth.AdministratorRelation && relation != auth.EditorRelation && @@ -133,7 +128,7 @@ func CheckRelation(relation string) error { relation != auth.RoleGroupRelation && relation != auth.GroupRelation && relation != auth.PlatformRelation { - return errInvalidRelation + return apiutil.ErrInvalidRelation } return nil diff --git a/invitations/invitations_test.go b/invitations/invitations_test.go index 942eaec769..2c367d7a77 100644 --- a/invitations/invitations_test.go +++ b/invitations/invitations_test.go @@ -4,19 +4,14 @@ package invitations_test import ( - "errors" "fmt" "testing" + "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/invitations" "github.com/stretchr/testify/assert" ) -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") -) - func TestInvitation_MarshalJSON(t *testing.T) { cases := []struct { desc string @@ -60,8 +55,8 @@ func TestCheckRelation(t *testing.T) { relation string err error }{ - {"", errMissingRelation}, - {"admin", errInvalidRelation}, + {"", apiutil.ErrMissingRelation}, + {"admin", apiutil.ErrInvalidRelation}, {"editor", nil}, {"viewer", nil}, {"member", nil}, diff --git a/invitations/service_test.go b/invitations/service_test.go index 87fda2ef7e..58b7593e7b 100644 --- a/invitations/service_test.go +++ b/invitations/service_test.go @@ -5,7 +5,6 @@ package invitations_test import ( "context" - "errors" "math/rand" "testing" "time" @@ -13,6 +12,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/invitations/mocks" @@ -79,7 +79,7 @@ func TestSendInvitation(t *testing.T) { token: validToken, tokenUserID: testsutil.GenerateUUID(t), req: invitations.Invitation{Relation: "invalid"}, - err: errors.New("invalid relation"), + err: apiutil.ErrInvalidRelation, authNErr: nil, domainErr: nil, adminErr: nil, diff --git a/pkg/clients/postgres/clients.go b/pkg/clients/postgres/clients.go index 6ca5255d1e..1ada83f485 100644 --- a/pkg/clients/postgres/clients.go +++ b/pkg/clients/postgres/clients.go @@ -149,11 +149,11 @@ func (repo *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clien dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() @@ -197,12 +197,12 @@ func (repo *Repository) RetrieveAllBasicInfo(ctx context.Context, pm clients.Pag dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() @@ -255,11 +255,11 @@ func (repo *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) ( dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() diff --git a/pkg/clients/status.go b/pkg/clients/status.go index 9d4c59e2fd..82a716d511 100644 --- a/pkg/clients/status.go +++ b/pkg/clients/status.go @@ -5,7 +5,6 @@ package clients import ( "encoding/json" - "errors" "strings" "github.com/absmach/magistrala/internal/apiutil" @@ -36,9 +35,6 @@ const ( Unknown = "unknown" ) -// ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. -var ErrStatusAlreadyAssigned = errors.New("status already assigned") - // String converts client/group status to string literal. func (s Status) String() string { switch s { diff --git a/pkg/errors/repository/types.go b/pkg/errors/repository/types.go index 2637d79b36..c27124343d 100644 --- a/pkg/errors/repository/types.go +++ b/pkg/errors/repository/types.go @@ -48,4 +48,7 @@ var ( // ErrInvalidSecret indicates invalid secret. ErrInvalidSecret = errors.New("missing secret") + + // ErrFailedToRetrieveAllGroups failed to retrieve groups. + ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") ) diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index 771fe2159a..28071fb3a5 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -48,4 +48,22 @@ var ( // ErrInvalidPolicy indicates that an invalid policy. ErrInvalidPolicy = errors.New("invalid policy") + + // ErrRecoveryToken indicates error in generating password recovery token. + ErrRecoveryToken = errors.New("failed to generate password recovery token") + + // ErrFailedPolicyUpdate indicates a failure to update user policy. + ErrFailedPolicyUpdate = errors.New("failed to update user policy") + + // ErrAddPolicies indicates failed to add policies. + ErrAddPolicies = errors.New("failed to add policies") + + // ErrDeletePolicies indicates failed to delete policies. + ErrDeletePolicies = errors.New("failed to delete policies") + + // ErrPasswordFormat indicates weak password. + ErrPasswordFormat = errors.New("password does not meet the requirements") + + // ErrFailedUpdateRole indicates a failure to update user role. + ErrFailedUpdateRole = errors.New("failed to update user role") ) diff --git a/pkg/errors/types.go b/pkg/errors/types.go index b75a43e313..70275f68da 100644 --- a/pkg/errors/types.go +++ b/pkg/errors/types.go @@ -17,4 +17,7 @@ var ( // ErrEmptyPath indicates empty file path. ErrEmptyPath = errors.New("empty file path") + + // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. + ErrStatusAlreadyAssigned = errors.New("status already assigned") ) diff --git a/pkg/groups/errors.go b/pkg/groups/errors.go index 24b1a29d99..b6665fa0bc 100644 --- a/pkg/groups/errors.go +++ b/pkg/groups/errors.go @@ -14,7 +14,4 @@ var ( // ErrDisableGroup indicates error in disabling group. ErrDisableGroup = errors.New("failed to disable group") - - // ErrStatusAlreadyAssigned indicated that the group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") ) diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index 8df439d6b8..4dba870a8a 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -110,7 +110,7 @@ func TestCreateChannel(t *testing.T) { Status: mgclients.EnabledStatus.String(), }, token: token, - err: errors.NewSDKErrorWithStatus(repoerr.ErrCreateEntity, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(repoerr.ErrCreateEntity, http.StatusUnprocessableEntity), }, { desc: "create channel with missing name", @@ -464,7 +464,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel description with invalid token", @@ -474,7 +474,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel metadata with invalid token", @@ -486,7 +486,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel that can't be marshalled", diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 5b82db9a92..459fe826ef 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -98,7 +98,7 @@ func TestCreateGroup(t *testing.T) { ParentID: wrongID, Status: clients.EnabledStatus.String(), }, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), }, { desc: "create group with missing name", @@ -721,7 +721,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update group description with invalid token", @@ -731,7 +731,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update group metadata with invalid token", @@ -743,7 +743,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update a group that can't be marshalled", diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 613b57084f..8c8e8f795d 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -97,7 +97,7 @@ func TestCreateThing(t *testing.T) { response: sdk.Thing{}, token: token, repoErr: sdk.ErrFailedCreation, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, repoerr.ErrCreateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, repoerr.ErrCreateEntity), http.StatusUnprocessableEntity), }, { desc: "register empty thing", @@ -246,7 +246,7 @@ func TestCreateThings(t *testing.T) { things: thingsList, response: []sdk.Thing{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusUnprocessableEntity), }, { desc: "register empty things", @@ -725,7 +725,7 @@ func TestUpdateThing(t *testing.T) { thing: thing2, response: sdk.Thing{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update thing that can't be marshalled", @@ -810,7 +810,7 @@ func TestUpdateThingTags(t *testing.T) { thing: thing2, response: sdk.Thing{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update thing that can't be marshalled", diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 54b0f93f87..a591eeb091 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -89,7 +89,7 @@ func TestCreateClient(t *testing.T) { client: user, response: sdk.User{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusUnprocessableEntity), }, { desc: "register empty user", @@ -350,7 +350,6 @@ func TestListClients(t *testing.T) { for _, tc := range cases { pm := sdk.PageMetadata{ Status: tc.status, - Total: total, Offset: tc.offset, Limit: tc.limit, Name: tc.name, @@ -552,7 +551,7 @@ func TestUpdateClient(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -643,7 +642,7 @@ func TestUpdateClientTags(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -733,7 +732,7 @@ func TestUpdateClientIdentity(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -749,7 +748,7 @@ func TestUpdateClientIdentity(t *testing.T) { }, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, } @@ -901,7 +900,7 @@ func TestUpdateClientRole(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(users.ErrFailedUpdateRole, users.ErrFailedUpdateRole), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrFailedUpdateRole, svcerr.ErrFailedUpdateRole), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", diff --git a/things/service.go b/things/service.go index 32987f8b84..853e2d0e66 100644 --- a/things/service.go +++ b/things/service.go @@ -507,7 +507,7 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) } if dbClient.Status == client.Status { - return mgclients.Client{}, mgclients.ErrStatusAlreadyAssigned + return mgclients.Client{}, errors.ErrStatusAlreadyAssigned } client.UpdatedBy = userID diff --git a/things/service_test.go b/things/service_test.go index 23f6881b25..9dec8d5807 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -1079,8 +1079,8 @@ func TestEnableClient(t *testing.T) { changeStatusResponse: enabledClient1, retrieveByIDResponse: enabledClient1, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - changeStatusErr: mgclients.ErrStatusAlreadyAssigned, - err: mgclients.ErrStatusAlreadyAssigned, + changeStatusErr: errors.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "enable non-existing client", @@ -1236,8 +1236,8 @@ func TestDisableClient(t *testing.T) { changeStatusResponse: mgclients.Client{}, retrieveByIDResponse: disabledClient1, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, - changeStatusErr: mgclients.ErrStatusAlreadyAssigned, - err: mgclients.ErrStatusAlreadyAssigned, + changeStatusErr: errors.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "disable non-existing client", diff --git a/users/api/clients.go b/users/api/clients.go index e0b295d996..380190a01a 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -32,6 +32,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, pr *rege opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } + r.Route("/users", func(r chi.Router) { r.Post("/", otelhttp.NewHandler(kithttp.NewServer( registrationEndpoint(svc), @@ -89,20 +90,6 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, pr *rege opts..., ), "update_client_identity").ServeHTTP) - r.Post("/password/reset-request", otelhttp.NewHandler(kithttp.NewServer( - passwordResetRequestEndpoint(svc), - decodePasswordResetRequest, - api.EncodeResponse, - opts..., - ), "password_reset_req").ServeHTTP) - - r.Put("/password/reset", otelhttp.NewHandler(kithttp.NewServer( - passwordResetEndpoint(svc), - decodePasswordReset, - api.EncodeResponse, - opts..., - ), "password_reset").ServeHTTP) - r.Patch("/{id}/role", otelhttp.NewHandler(kithttp.NewServer( updateClientRoleEndpoint(svc), decodeUpdateClientRole, @@ -139,6 +126,22 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, pr *rege ), "disable_client").ServeHTTP) }) + r.Route("/password", func(r chi.Router) { + r.Post("/reset-request", otelhttp.NewHandler(kithttp.NewServer( + passwordResetRequestEndpoint(svc), + decodePasswordResetRequest, + api.EncodeResponse, + opts..., + ), "password_reset_req").ServeHTTP) + + r.Put("/reset", otelhttp.NewHandler(kithttp.NewServer( + passwordResetEndpoint(svc), + decodePasswordReset, + api.EncodeResponse, + opts..., + ), "password_reset").ServeHTTP) + }) + // Ideal location: users service, groups endpoint. // Reason for placing here : // SpiceDB provides list of user ids in given user_group_id @@ -228,7 +231,6 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index dc1dcb4a0c..765ba219bb 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -456,13 +456,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid name", - token: validToken, - query: "name=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate name", token: validToken, @@ -510,13 +503,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid tags", - token: validToken, - query: "tag=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate tags", token: validToken, @@ -564,13 +550,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid permissions", - token: validToken, - query: "permission=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate permissions", token: validToken, @@ -591,13 +570,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid list perms", - token: validToken, - query: "list_perms=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate list perms", token: validToken, @@ -620,13 +592,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid identity", - token: validToken, - query: "identity=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate identity", token: validToken, @@ -649,13 +614,6 @@ func TestListClients(t *testing.T) { status: http.StatusOK, err: nil, }, - { - desc: "list users with invalid order", - token: validToken, - query: "order=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate order", token: validToken, @@ -663,13 +621,6 @@ func TestListClients(t *testing.T) { status: http.StatusBadRequest, err: apiutil.ErrInvalidQueryParams, }, - { - desc: "list users with invalid order direction", - token: validToken, - query: "dir=invalid", - status: http.StatusInternalServerError, - err: apiutil.ErrValidation, - }, { desc: "list users with duplicate order direction", token: validToken, @@ -1082,7 +1033,7 @@ func TestPasswordResetRequest(t *testing.T) { desc: "password reset request with empty email", data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "", testhost), contentType: contentType, - status: http.StatusInternalServerError, + status: http.StatusBadRequest, err: apiutil.ErrValidation, }, { @@ -1119,7 +1070,7 @@ func TestPasswordResetRequest(t *testing.T) { req := testRequest{ client: us.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/users/password/reset-request", us.URL), + url: fmt.Sprintf("%s/password/reset-request", us.URL), contentType: tc.contentType, body: strings.NewReader(tc.data), } @@ -1207,7 +1158,7 @@ func TestPasswordReset(t *testing.T) { req := testRequest{ client: us.Client(), method: http.MethodPut, - url: fmt.Sprintf("%s/users/password/reset", us.URL), + url: fmt.Sprintf("%s/password/reset", us.URL), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), @@ -1276,7 +1227,7 @@ func TestUpdateClientRole(t *testing.T) { clientID: client.ID, token: validToken, contentType: contentType, - status: http.StatusInternalServerError, + status: http.StatusBadRequest, err: svcerr.ErrInvalidRole, }, { diff --git a/users/api/responses.go b/users/api/responses.go index 716f5a9121..fb05de493b 100644 --- a/users/api/responses.go +++ b/users/api/responses.go @@ -25,8 +25,8 @@ var ( type pageRes struct { Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total,omitempty"` + Offset uint64 `json:"offset"` + Total uint64 `json:"total"` } type createClientRes struct { diff --git a/users/postgres/clients.go b/users/postgres/clients.go index defb5fedcf..8ce544eb0d 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -12,7 +12,6 @@ import ( pgclients "github.com/absmach/magistrala/pkg/clients/postgres" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - svcerr "github.com/absmach/magistrala/pkg/errors/service" ) var _ mgclients.Repository = (*clientRepo)(nil) @@ -79,17 +78,18 @@ func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) erro q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2" rows, err := repo.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole) if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) + return postgres.HandleError(repoerr.ErrViewEntity, err) } defer rows.Close() if rows.Next() { if err := rows.Err(); err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) + return postgres.HandleError(repoerr.ErrViewEntity, err) } return nil } - return svcerr.ErrAuthorization + + return repoerr.ErrNotFound } func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.Client, error) { @@ -134,11 +134,11 @@ func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgcl dbPage, err := pgclients.ToDBClientsPage(pm) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() diff --git a/users/postgres/clients_test.go b/users/postgres/clients_test.go index 418f22476e..dd2a79a57d 100644 --- a/users/postgres/clients_test.go +++ b/users/postgres/clients_test.go @@ -14,7 +14,6 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - svcerr "github.com/absmach/magistrala/pkg/errors/service" cpostgres "github.com/absmach/magistrala/users/postgres" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -232,7 +231,7 @@ func TestIsPlatformAdmin(t *testing.T) { Status: mgclients.EnabledStatus, Role: mgclients.UserRole, }, - err: svcerr.ErrAuthorization, + err: repoerr.ErrNotFound, }, } diff --git a/users/service.go b/users/service.go index 58a1b8b9a1..dc50085327 100644 --- a/users/service.go +++ b/users/service.go @@ -20,22 +20,13 @@ import ( ) var ( - // ErrRecoveryToken indicates error in generating password recovery token. - ErrRecoveryToken = errors.New("failed to generate password recovery token") - - // ErrFailedPolicyUpdate indicates a failure to update user policy. - ErrFailedPolicyUpdate = errors.New("failed to update user policy") - - // ErrFailedUpdateRole indicates a failure to update user role. - ErrFailedUpdateRole = errors.New("failed to update user role") - // ErrAddPolicies indictaed a failre to add policies. errAddPolicies = errors.New("failed to add policies") // ErrIssueToken indicates a failure to issue token. ErrIssueToken = errors.New("failed to issue token") - // ErrAddPolicies indictaed a failre to add policies. + // errDeletePolicies indictaed a failre to add policies. errDeletePolicies = errors.New("failed to delete policies") ) @@ -288,7 +279,7 @@ func (svc service) GenerateResetToken(ctx context.Context, email, host string) e } token, err := svc.auth.Issue(ctx, issueReq) if err != nil { - return errors.Wrap(ErrRecoveryToken, err) + return errors.Wrap(svcerr.ErrRecoveryToken, err) } return svc.SendPasswordReset(ctx, host, email, client.Name, token.AccessToken) @@ -375,7 +366,7 @@ func (svc service) UpdateClientRole(ctx context.Context, token string, cli mgcli } if err := svc.updateClientPolicy(ctx, cli.ID, cli.Role); err != nil { - return mgclients.Client{}, errors.Wrap(ErrFailedPolicyUpdate, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrFailedPolicyUpdate, err) } client, err = svc.clients.UpdateRole(ctx, client) if err != nil { @@ -383,7 +374,7 @@ func (svc service) UpdateClientRole(ctx context.Context, token string, cli mgcli if errRollback := svc.updateClientPolicy(ctx, cli.ID, mgclients.UserRole); errRollback != nil { return mgclients.Client{}, errors.Wrap(err, errors.Wrap(repoerr.ErrRollbackTx, errRollback)) } - return mgclients.Client{}, errors.Wrap(ErrFailedUpdateRole, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrFailedUpdateRole, err) } return client, nil } @@ -429,7 +420,7 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) } if dbClient.Status == client.Status { - return mgclients.Client{}, mgclients.ErrStatusAlreadyAssigned + return mgclients.Client{}, errors.ErrStatusAlreadyAssigned } client.UpdatedBy = tokenUserID @@ -571,7 +562,7 @@ func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, per } if !res.GetAuthorized() { - return "", errors.Wrap(svcerr.ErrAuthorization, err) + return "", svcerr.ErrAuthorization } return res.GetId(), nil } diff --git a/users/service_test.go b/users/service_test.go index f96732d3c5..08c0b1b82c 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -1328,7 +1328,7 @@ func TestEnableClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: enabledClient1.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, retrieveByIDResponse: enabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "enable disabled client with failed to change status", @@ -1515,7 +1515,7 @@ func TestDisableClient(t *testing.T) { identifyResponse: &magistrala.IdentityRes{UserId: disabledClient1.ID}, authorizeResponse: &magistrala.AuthorizeRes{Authorized: true}, retrieveByIDResponse: disabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "disable enabled client with failed to change status", @@ -2257,7 +2257,7 @@ func TestGenerateResetToken(t *testing.T) { retrieveByIdentityResponse: client, issueResponse: &magistrala.Token{}, issueErr: svcerr.ErrAuthorization, - err: users.ErrRecoveryToken, + err: svcerr.ErrRecoveryToken, }, } From 698d213ac33c16c47ce7a71e8f5fcebd7237ff38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:22:57 +0100 Subject: [PATCH 65/71] NOISSUE - Bump github.com/docker/docker from 24.0.7+incompatible to 24.0.9+incompatible (#2123) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 799e7eb445..ec3b1a9e03 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 github.com/caarlos0/env/v10 v10.0.0 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/docker/docker v24.0.7+incompatible + github.com/docker/docker v24.0.9+incompatible github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/fatih/color v1.16.0 github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba diff --git a/go.sum b/go.sum index a84d835e35..a94c2cf8aa 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From 0f4a709e23d0799297d7cad1332d0c1af50daf70 Mon Sep 17 00:00:00 2001 From: Ian Ngethe Muchiri <100555904+ianmuchyri@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:24:09 +0300 Subject: [PATCH 66/71] NOISSUE - Fix Timescale Reader query (#2120) Signed-off-by: ianmuchyri --- readers/timescale/messages.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readers/timescale/messages.go b/readers/timescale/messages.go index aa1589d033..3aec80dd6b 100644 --- a/readers/timescale/messages.go +++ b/readers/timescale/messages.go @@ -42,8 +42,8 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( // If aggregation is provided, add time_bucket and aggregation to the query if rpm.Aggregation != "" { - q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) *1000 AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) - totalQuery = fmt.Sprintf(`SELECT COUNT(*) FROM (SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1) AS subquery;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) + q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000000))) *1000000 AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) + totalQuery = fmt.Sprintf(`SELECT COUNT(*) FROM (SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/1000000))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1) AS subquery;`, rpm.Interval, rpm.Aggregation, format, fmtCondition(rpm)) } params := map[string]interface{}{ From 097b547634c4775092f6c9d2b2a3365c0a84be9c Mon Sep 17 00:00:00 2001 From: Washington Kigani Kamadi Date: Mon, 1 Apr 2024 13:38:12 +0300 Subject: [PATCH 67/71] NOISSUE - Fix OPC-UA adapter (#2114) Signed-off-by: WashingtonKK --- internal/groups/events/events.go | 34 +++++++++ internal/groups/events/streams.go | 27 ++++++- opcua/adapter.go | 117 ++++++++++++++++++++++-------- opcua/api/endpoint.go | 2 +- opcua/api/logging.go | 17 +++-- opcua/api/metrics.go | 12 +-- opcua/api/requests.go | 7 +- opcua/api/transport.go | 25 ++++--- opcua/events/events.go | 4 +- opcua/events/streams.go | 73 +++++++++++++------ 10 files changed, 233 insertions(+), 85 deletions(-) diff --git a/internal/groups/events/events.go b/internal/groups/events/events.go index 880c7eddf3..d15a2a1caa 100644 --- a/internal/groups/events/events.go +++ b/internal/groups/events/events.go @@ -21,9 +21,13 @@ const ( groupList = groupPrefix + "list" groupListMemberships = groupPrefix + "list_by_user" groupRemove = groupPrefix + "remove" + groupAssign = groupPrefix + "assign" + groupUnassign = groupPrefix + "unassign" ) var ( + _ events.Event = (*assignEvent)(nil) + _ events.Event = (*unassignEvent)(nil) _ events.Event = (*createGroupEvent)(nil) _ events.Event = (*updateGroupEvent)(nil) _ events.Event = (*changeStatusGroupEvent)(nil) @@ -34,6 +38,36 @@ var ( _ events.Event = (*listGroupMembershipEvent)(nil) ) +type assignEvent struct { + memberIDs []string + groupID string +} + +func (cge assignEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": groupAssign, + "member_ids": cge.memberIDs, + "group_id": cge.groupID, + } + + return val, nil +} + +type unassignEvent struct { + memberIDs []string + groupID string +} + +func (cge unassignEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": groupUnassign, + "member_ids": cge.memberIDs, + "group_id": cge.groupID, + } + + return val, nil +} + type createGroupEvent struct { groups.Group } diff --git a/internal/groups/events/streams.go b/internal/groups/events/streams.go index 0f5bc23110..10610d6686 100644 --- a/internal/groups/events/streams.go +++ b/internal/groups/events/streams.go @@ -140,10 +140,35 @@ func (es eventStore) EnableGroup(ctx context.Context, token, id string) (groups. } func (es eventStore) Assign(ctx context.Context, token, groupID, relation, memberKind string, memberIDs ...string) error { - return es.svc.Assign(ctx, token, groupID, relation, memberKind, memberIDs...) + if err := es.svc.Assign(ctx, token, groupID, relation, memberKind, memberIDs...); err != nil { + return err + } + + event := assignEvent{ + groupID: groupID, + memberIDs: memberIDs, + } + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil } func (es eventStore) Unassign(ctx context.Context, token, groupID, relation, memberKind string, memberIDs ...string) error { + if err := es.svc.Unassign(ctx, token, groupID, relation, memberKind, memberIDs...); err != nil { + return err + } + + event := unassignEvent{ + groupID: groupID, + memberIDs: memberIDs, + } + + if err := es.Publish(ctx, event); err != nil { + return err + } return es.svc.Unassign(ctx, token, groupID, relation, memberKind, memberIDs...) } diff --git a/opcua/adapter.go b/opcua/adapter.go index b07801dfbd..0b0a8a149d 100644 --- a/opcua/adapter.go +++ b/opcua/adapter.go @@ -5,8 +5,11 @@ package opcua import ( "context" + "encoding/base64" "fmt" "log/slog" + "regexp" + "strconv" "github.com/absmach/magistrala/opcua/db" ) @@ -33,27 +36,30 @@ type Service interface { RemoveChannel(ctx context.Context, chanID string) error // ConnectThing creates thingID:channelID route-map - ConnectThing(ctx context.Context, chanID, thingID string) error + ConnectThing(ctx context.Context, chanID string, thingIDs []string) error // DisconnectThing removes thingID:channelID route-map - DisconnectThing(ctx context.Context, chanID, thingID string) error + DisconnectThing(ctx context.Context, chanID string, thingIDs []string) error // Browse browses available nodes for a given OPC-UA Server URI and NodeID - Browse(ctx context.Context, serverURI, namespace, identifier string) ([]BrowsedNode, error) + Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]BrowsedNode, error) } // Config OPC-UA Server. type Config struct { ServerURI string NodeID string - Interval string `env:"MG_OPCUA_ADAPTER_INTERVAL_MS" envDefault:"1000"` - Policy string `env:"MG_OPCUA_ADAPTER_POLICY" envDefault:""` - Mode string `env:"MG_OPCUA_ADAPTER_MODE" envDefault:""` - CertFile string `env:"MG_OPCUA_ADAPTER_CERT_FILE" envDefault:""` - KeyFile string `env:"MG_OPCUA_ADAPTER_KEY_FILE" envDefault:""` + Interval string `env:"MG_OPCUA_ADAPTER_INTERVAL_MS" envDefault:"1000"` + Policy string `env:"MG_OPCUA_ADAPTER_POLICY" envDefault:""` + Mode string `env:"MG_OPCUA_ADAPTER_MODE" envDefault:""` + CertFile string `env:"MG_OPCUA_ADAPTER_CERT_FILE" envDefault:""` + KeyFile string `env:"MG_OPCUA_ADAPTER_KEY_FILE" envDefault:""` } -var _ Service = (*adapterService)(nil) +var ( + _ Service = (*adapterService)(nil) + guidRegex = regexp.MustCompile(`^\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\}?$`) +) type adapterService struct { subscriber Subscriber @@ -102,38 +108,80 @@ func (as *adapterService) RemoveChannel(ctx context.Context, chanID string) erro return as.channelsRM.Remove(ctx, chanID) } -func (as *adapterService) ConnectThing(ctx context.Context, chanID, thingID string) error { +func (as *adapterService) ConnectThing(ctx context.Context, chanID string, thingIDs []string) error { serverURI, err := as.channelsRM.Get(ctx, chanID) if err != nil { return err } - nodeID, err := as.thingsRM.Get(ctx, thingID) - if err != nil { - return err - } + for _, thingID := range thingIDs { + nodeID, err := as.thingsRM.Get(ctx, thingID) + if err != nil { + return err + } - as.cfg.NodeID = nodeID - as.cfg.ServerURI = serverURI + as.cfg.NodeID = nodeID + as.cfg.ServerURI = serverURI - c := fmt.Sprintf("%s:%s", chanID, thingID) - if err := as.connectRM.Save(ctx, c, c); err != nil { - return err - } + c := fmt.Sprintf("%s:%s", chanID, thingID) + if err := as.connectRM.Save(ctx, c, c); err != nil { + return err + } + + go func() { + if err := as.subscriber.Subscribe(ctx, as.cfg); err != nil { + as.logger.Warn("subscription failed", slog.Any("error", err)) + } + }() - go func() { - if err := as.subscriber.Subscribe(ctx, as.cfg); err != nil { - as.logger.Warn(fmt.Sprintf("subscription failed: %s", err)) + // Store subscription details + if err := db.Save(serverURI, nodeID); err != nil { + return err } - }() + } - // Store subscription details - return db.Save(serverURI, nodeID) + return nil } -func (as *adapterService) Browse(ctx context.Context, serverURI, namespace, identifier string) ([]BrowsedNode, error) { - nodeID := fmt.Sprintf("%s;%s", namespace, identifier) - +func (as *adapterService) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]BrowsedNode, error) { + idFormat := "s" + switch identifierType { + case "string": + break + case "numeric": + if _, err := strconv.Atoi(identifier); err != nil { + args := []any{ + slog.String("namespace", namespace), + slog.String("identifier", identifier), + slog.Any("error", err), + } + as.logger.Warn("failed to parse numeric identifier", args...) + break + } + idFormat = "i" + case "guid": + if !guidRegex.MatchString(identifier) { + args := []any{ + slog.String("namespace", namespace), + slog.String("identifier", identifier), + } + as.logger.Warn("GUID identifier has invalid format", args...) + break + } + idFormat = "g" + case "opaque": + if _, err := base64.StdEncoding.DecodeString(identifier); err != nil { + args := []any{ + slog.String("namespace", namespace), + slog.String("identifier", identifier), + slog.Any("error", err), + } + as.logger.Warn("opaque identifier has invalid base64 format", args...) + break + } + idFormat = "b" + } + nodeID := fmt.Sprintf("ns=%s;%s=%s", namespace, idFormat, identifier) nodes, err := as.browser.Browse(serverURI, nodeID) if err != nil { return nil, err @@ -141,7 +189,12 @@ func (as *adapterService) Browse(ctx context.Context, serverURI, namespace, iden return nodes, nil } -func (as *adapterService) DisconnectThing(ctx context.Context, chanID, thingID string) error { - c := fmt.Sprintf("%s:%s", chanID, thingID) - return as.connectRM.Remove(ctx, c) +func (as *adapterService) DisconnectThing(ctx context.Context, chanID string, thingIDs []string) error { + for _, thingID := range thingIDs { + c := fmt.Sprintf("%s:%s", chanID, thingID) + if err := as.connectRM.Remove(ctx, c); err != nil { + return err + } + } + return nil } diff --git a/opcua/api/endpoint.go b/opcua/api/endpoint.go index b0dbc4d4d4..b71149ce91 100644 --- a/opcua/api/endpoint.go +++ b/opcua/api/endpoint.go @@ -20,7 +20,7 @@ func browseEndpoint(svc opcua.Service) endpoint.Endpoint { return nil, errors.Wrap(apiutil.ErrValidation, err) } - nodes, err := svc.Browse(ctx, req.ServerURI, req.Namespace, req.Identifier) + nodes, err := svc.Browse(ctx, req.ServerURI, req.Namespace, req.Identifier, req.IdentifierType) if err != nil { return nil, err } diff --git a/opcua/api/logging.go b/opcua/api/logging.go index e58e97abba..fc728bc908 100644 --- a/opcua/api/logging.go +++ b/opcua/api/logging.go @@ -134,12 +134,12 @@ func (lm loggingMiddleware) RemoveChannel(ctx context.Context, mgxChanID string) return lm.svc.RemoveChannel(ctx, mgxChanID) } -func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID, mgxThingID string) (err error) { +func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("channel_id", mgxChanID), - slog.String("thing_id", mgxThingID), + slog.Any("thing_ids", mgxThingIDs), } if err != nil { args = append(args, slog.Any("error", err)) @@ -149,15 +149,15 @@ func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID, mgxThin lm.logger.Info("Connect thing to channel completed successfully", args...) }(time.Now()) - return lm.svc.ConnectThing(ctx, mgxChanID, mgxThingID) + return lm.svc.ConnectThing(ctx, mgxChanID, mgxThingIDs) } -func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID, mgxThingID string) (err error) { +func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("channel_id", mgxChanID), - slog.String("thing_id", mgxThingID), + slog.Any("thing_ids", mgxThingIDs), } if err != nil { args = append(args, slog.Any("error", err)) @@ -167,16 +167,17 @@ func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID, mgxT lm.logger.Info("Disconnect thing from channel completed successfully", args...) }(time.Now()) - return lm.svc.DisconnectThing(ctx, mgxChanID, mgxThingID) + return lm.svc.DisconnectThing(ctx, mgxChanID, mgxThingIDs) } -func (lm loggingMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier string) (nodes []opcua.BrowsedNode, err error) { +func (lm loggingMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) (nodes []opcua.BrowsedNode, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("server_uri", serverURI), slog.String("namespace", namespace), slog.String("identifier", identifier), + slog.String("identifier_type", identifierType), } if err != nil { args = append(args, slog.Any("error", err)) @@ -186,5 +187,5 @@ func (lm loggingMiddleware) Browse(ctx context.Context, serverURI, namespace, id lm.logger.Info("Browse available nodes completed successfully", args...) }(time.Now()) - return lm.svc.Browse(ctx, serverURI, namespace, identifier) + return lm.svc.Browse(ctx, serverURI, namespace, identifier, identifierType) } diff --git a/opcua/api/metrics.go b/opcua/api/metrics.go index 22cdb7718c..18d072f8db 100644 --- a/opcua/api/metrics.go +++ b/opcua/api/metrics.go @@ -84,29 +84,29 @@ func (mm *metricsMiddleware) RemoveChannel(ctx context.Context, mgxChanID string return mm.svc.RemoveChannel(ctx, mgxChanID) } -func (mm *metricsMiddleware) ConnectThing(ctx context.Context, mgxChanID, mgxThingID string) error { +func (mm *metricsMiddleware) ConnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) error { defer func(begin time.Time) { mm.counter.With("method", "connect_thing").Add(1) mm.latency.With("method", "connect_thing").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.ConnectThing(ctx, mgxChanID, mgxThingID) + return mm.svc.ConnectThing(ctx, mgxChanID, mgxThingIDs) } -func (mm *metricsMiddleware) DisconnectThing(ctx context.Context, mgxChanID, mgxThingID string) error { +func (mm *metricsMiddleware) DisconnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) error { defer func(begin time.Time) { mm.counter.With("method", "disconnect_thing").Add(1) mm.latency.With("method", "disconnect_thing").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.DisconnectThing(ctx, mgxChanID, mgxThingID) + return mm.svc.DisconnectThing(ctx, mgxChanID, mgxThingIDs) } -func (mm *metricsMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier string) ([]opcua.BrowsedNode, error) { +func (mm *metricsMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]opcua.BrowsedNode, error) { defer func(begin time.Time) { mm.counter.With("method", "browse").Add(1) mm.latency.With("method", "browse").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.Browse(ctx, serverURI, namespace, identifier) + return mm.svc.Browse(ctx, serverURI, namespace, identifier, identifierType) } diff --git a/opcua/api/requests.go b/opcua/api/requests.go index 7288f8a144..73197c69c8 100644 --- a/opcua/api/requests.go +++ b/opcua/api/requests.go @@ -6,9 +6,10 @@ package api import "github.com/absmach/magistrala/internal/apiutil" type browseReq struct { - ServerURI string - Namespace string - Identifier string + ServerURI string + Namespace string + Identifier string + IdentifierType string } func (req *browseReq) validate() error { diff --git a/opcua/api/transport.go b/opcua/api/transport.go index ec9ece461b..cb24cdc844 100644 --- a/opcua/api/transport.go +++ b/opcua/api/transport.go @@ -19,12 +19,13 @@ import ( ) const ( - contentType = "application/json" - serverParam = "server" - namespaceParam = "namespace" - identifierParam = "identifier" - defNamespace = "ns=0" // Standard root namespace - defIdentifier = "i=84" // Standard root identifier + contentType = "application/json" + serverParam = "server" + namespaceParam = "namespace" + identifierParam = "identifier" + identifierTypeParam = "identifierType" + defNamespace = "ns=0" // Standard root namespace + defIdentifier = "i=84" // Standard root identifier ) // MakeHandler returns a HTTP handler for API endpoints. @@ -64,15 +65,21 @@ func decodeBrowse(_ context.Context, r *http.Request) (interface{}, error) { return nil, errors.Wrap(apiutil.ErrValidation, err) } + iType, err := apiutil.ReadStringQuery(r, identifierTypeParam, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if n == "" || i == "" { n = defNamespace i = defIdentifier } req := browseReq{ - ServerURI: s, - Namespace: n, - Identifier: i, + ServerURI: s, + Namespace: n, + Identifier: i, + IdentifierType: iType, } return req, nil diff --git a/opcua/events/events.go b/opcua/events/events.go index 30cea2f4b7..d778420b53 100644 --- a/opcua/events/events.go +++ b/opcua/events/events.go @@ -13,8 +13,8 @@ type removeThingEvent struct { } type connectThingEvent struct { - chanID string - thingID string + chanID string + thingIDs []string } type createChannelEvent struct { diff --git a/opcua/events/streams.go b/opcua/events/streams.go index 91d1750546..ccee65e733 100644 --- a/opcua/events/streams.go +++ b/opcua/events/streams.go @@ -5,6 +5,7 @@ package events import ( "context" + "encoding/base64" "encoding/json" "errors" @@ -13,21 +14,21 @@ import ( ) const ( - keyType = "opcua" - keyNodeID = "node_id" - keyServerURI = "server_uri" + keyType = "opcua" + keyNodeID = "node_id" + keyServerURI = "server_uri" + channelPrefix = "group." + thingPrefix = "thing." - thingPrefix = "thing." - thingCreate = thingPrefix + "create" - thingUpdate = thingPrefix + "update" - thingRemove = thingPrefix + "remove" - thingConnect = thingPrefix + "connect" - thingDisconnect = thingPrefix + "disconnect" + thingCreate = thingPrefix + "create" + thingUpdate = thingPrefix + "update" + thingRemove = thingPrefix + "remove" - channelPrefix = "group." - channelCreate = channelPrefix + "create" - channelUpdate = channelPrefix + "update" - channelRemove = channelPrefix + "remove" + channelCreate = channelPrefix + "create" + channelUpdate = channelPrefix + "update" + channelRemove = channelPrefix + "remove" + channelConnect = channelPrefix + "assign" + channelDisconnect = channelPrefix + "unassign" ) var ( @@ -92,12 +93,12 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { case channelRemove: rce := decodeRemoveChannel(msg) err = es.svc.RemoveChannel(ctx, rce.id) - case thingConnect: + case channelConnect: rce := decodeConnectThing(msg) - err = es.svc.ConnectThing(ctx, rce.chanID, rce.thingID) - case thingDisconnect: + err = es.svc.ConnectThing(ctx, rce.chanID, rce.thingIDs) + case channelDisconnect: rce := decodeDisconnectThing(msg) - err = es.svc.DisconnectThing(ctx, rce.chanID, rce.thingID) + err = es.svc.DisconnectThing(ctx, rce.chanID, rce.thingIDs) } if err != nil && err != errMetadataType { return err @@ -108,8 +109,14 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { func decodeCreateThing(event map[string]interface{}) (createThingEvent, error) { strmeta := read(event, "metadata", "{}") + + // Metadata is base64 encoded since it is marshalled as []byte. + meta, err := base64.StdEncoding.DecodeString(strmeta) + if err != nil { + return createThingEvent{}, err + } var metadata map[string]interface{} - if err := json.Unmarshal([]byte(strmeta), &metadata); err != nil { + if err := json.Unmarshal(meta, &metadata); err != nil { return createThingEvent{}, err } @@ -144,8 +151,12 @@ func decodeRemoveThing(event map[string]interface{}) removeThingEvent { func decodeCreateChannel(event map[string]interface{}) (createChannelEvent, error) { strmeta := read(event, "metadata", "{}") + meta, err := base64.StdEncoding.DecodeString(strmeta) + if err != nil { + return createChannelEvent{}, err + } var metadata map[string]interface{} - if err := json.Unmarshal([]byte(strmeta), &metadata); err != nil { + if err := json.Unmarshal(meta, &metadata); err != nil { return createChannelEvent{}, err } @@ -180,15 +191,15 @@ func decodeRemoveChannel(event map[string]interface{}) removeChannelEvent { func decodeConnectThing(event map[string]interface{}) connectThingEvent { return connectThingEvent{ - chanID: read(event, "chan_id", ""), - thingID: read(event, "thing_id", ""), + chanID: read(event, "group_id", ""), + thingIDs: readMemberIDs(event, "member_ids"), } } func decodeDisconnectThing(event map[string]interface{}) connectThingEvent { return connectThingEvent{ - chanID: read(event, "chan_id", ""), - thingID: read(event, "thing_id", ""), + chanID: read(event, "chan_id", ""), + thingIDs: readMemberIDs(event, "member_ids"), } } @@ -200,3 +211,19 @@ func read(event map[string]interface{}, key, def string) string { return val } + +func readMemberIDs(event map[string]interface{}, key string) []string { + var memberIDs []string + val, ok := event[key].([]interface{}) + if !ok { + return memberIDs + } + + for _, v := range val { + if str, ok := v.(string); ok { + memberIDs = append(memberIDs, str) + } + } + + return memberIDs +} From a5c7d8ec0c59dc34e0b00be9d0fbc8f009eb9690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Borov=C4=8Danin?= Date: Wed, 3 Apr 2024 11:55:06 +0200 Subject: [PATCH 68/71] NOISSUE - Fix links in README.md file (#2129) Signed-off-by: Dusan Borovcanin --- .github/ISSUE_TEMPLATE/config.yml | 4 +- .github/workflows/swagger-ui.yaml | 2 +- README.md | 21 +-- api/asyncapi/mqtt.yml | 2 +- api/asyncapi/websocket.yml | 2 +- api/openapi/README.md | 2 +- api/openapi/auth.yml | 2 +- api/openapi/bootstrap.yml | 2 +- api/openapi/certs.yml | 2 +- api/openapi/consumers-notifiers.yml | 2 +- api/openapi/http.yml | 2 +- api/openapi/invitations.yml | 2 +- api/openapi/provision.yml | 2 +- api/openapi/readers.yml | 2 +- api/openapi/things.yml | 2 +- api/openapi/twins.yml | 2 +- api/openapi/users.yml | 2 +- auth/README.md | 4 +- auth/tracing/doc.go | 2 +- bootstrap/README.md | 2 +- bootstrap/tracing/doc.go | 2 +- certs/README.md | 2 +- certs/tracing/doc.go | 2 +- coap/tracing/doc.go | 2 +- consumers/README.md | 4 +- consumers/notifiers/README.md | 2 +- consumers/notifiers/smpp/README.md | 3 +- consumers/notifiers/smtp/README.md | 2 +- consumers/notifiers/tracing/doc.go | 2 +- consumers/writers/README.md | 2 +- consumers/writers/cassandra/README.md | 2 +- consumers/writers/influxdb/README.md | 2 +- go.mod | 98 +++++----- go.sum | 234 ++++++++++++------------ http/README.md | 2 +- internal/groups/postgres/groups_test.go | 2 +- internal/groups/service_test.go | 2 +- internal/groups/tracing/doc.go | 2 +- invitations/README.md | 2 +- lora/README.md | 2 +- mqtt/tracing/doc.go | 2 +- opcua/README.md | 2 +- pkg/clients/postgres/clients_test.go | 14 +- pkg/messaging/nats/tracing/doc.go | 2 +- pkg/messaging/rabbitmq/tracing/doc.go | 2 +- pkg/messaging/tracing/doc.go | 2 +- provision/README.md | 6 +- readers/README.md | 2 +- readers/cassandra/README.md | 4 +- readers/influxdb/README.md | 4 +- readers/mongodb/README.md | 4 +- readers/postgres/README.md | 2 +- readers/timescale/README.md | 2 +- things/README.md | 4 +- things/api/http/endpoints_test.go | 2 +- things/postgres/clients_test.go | 2 +- things/tracing/doc.go | 2 +- tools/e2e/README.md | 8 +- tools/e2e/cmd/main.go | 2 +- tools/e2e/e2e.go | 2 +- tools/mqtt-bench/README.md | 2 +- tools/mqtt-bench/cmd/main.go | 2 +- tools/provision/README.md | 2 +- tools/provision/cmd/main.go | 2 +- twins/README.md | 4 +- users/README.md | 4 +- users/postgres/clients_test.go | 2 +- users/tracing/doc.go | 2 +- ws/README.md | 2 +- ws/doc.go | 2 +- ws/tracing/doc.go | 2 +- 71 files changed, 267 insertions(+), 263 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ee76485f5d..2fb1e5667e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -5,7 +5,7 @@ blank_issues_enabled: false contact_links: - name: Google group url: https://groups.google.com/forum/#!forum/mainflux - about: Join the Mainflux community on Google group. + about: Join the Magistrala community on Google group. - name: Gitter url: https://gitter.im/mainflux/mainflux - about: Join the Mainflux community on Gitter. + about: Join the Magistrala community on Gitter. diff --git a/.github/workflows/swagger-ui.yaml b/.github/workflows/swagger-ui.yaml index 5e380375e8..403f5cbb9a 100644 --- a/.github/workflows/swagger-ui.yaml +++ b/.github/workflows/swagger-ui.yaml @@ -28,4 +28,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: swagger-ui - cname: api.mainflux.io + cname: docs.api.magistrala.abstractmachines.fr diff --git a/README.md b/README.md index 499b1669b3..a1c80ea5d3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Check that `.env` file contains: MG_RELEASE_TAG= ``` -> `docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://docs.mainflux.io/kubernetes). +> `docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://docs.magistrala.abstractmachines.fr/kubernetes). ## Usage @@ -87,11 +87,11 @@ make cli ./build/cli version ``` -Additional details on using the CLI can be found in the [CLI documentation](https://docs.mainflux.io/cli). +Additional details on using the CLI can be found in the [CLI documentation](https://docs.magistrala.abstractmachines.fr/cli). ## Documentation -Official documentation is hosted at [Magistrala official docs page][docs]. Documentation is auto-generated, checkout the instructions on [official docs repository](https://github.com/mainflux/docs): +Official documentation is hosted at [Magistrala official docs page][docs]. Documentation is auto-generated, checkout the instructions on [official docs repository](https://github.com/absmach/magistrala-docs): If you spot an error or a need for corrections, please let us know - or even better: send us a PR. @@ -137,8 +137,7 @@ You like Magistrala and you would like to make it your day job? We're always loo [Apache-2.0](LICENSE) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux?ref=badge_large) - +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala.svg?type=large&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala?ref=badge_large&issueType=license) ## Data Collection for Magistrala Magistrala is committed to continuously improving its services and ensuring a seamless experience for its users. To achieve this, we collect certain data from your deployments. Rest assured, this data is collected solely for the purpose of enhancing Magistrala and is not used with any malicious intent. The deployment summary can be found on our [website][callhome]. @@ -157,8 +156,8 @@ Data collection is on by default and can be disabled by setting the env variable By utilizing Magistrala, you actively contribute to its improvement. Together, we can build a more robust and efficient IoT platform. Thank you for your trust in Magistrala! -[banner]: https://github.com/mainflux/docs/blob/master/docs/img/gopherBanner.jpg -[docs]: https://docs.mainflux.io +[banner]: https://github.com/absmach/magistrala-docs/blob/main/docs/img/gopherBanner.jpg +[docs]: https://docs.magistrala.abstractmachines.fr [docker]: https://www.docker.com [forum]: https://groups.google.com/forum/#!forum/mainflux [gitter]: https://gitter.im/absmach/magistrala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge @@ -168,11 +167,11 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w [cov-badge]: https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=SEMDAO3L09 [cov-url]: https://codecov.io/gh/absmach/magistrala [license]: https://img.shields.io/badge/license-Apache%20v2.0-blue.svg -[twitter]: https://twitter.com/mainflux +[twitter]: https://twitter.com/absmach [lora]: https://lora-alliance.org/ [opcua]: https://opcfoundation.org/about/opc-technologies/opc-ua/ -[agent]: https://github.com/mainflux/agent -[export]: https://github.com/mainflux/export +[agent]: https://github.com/absmach/agent +[export]: https://github.com/absmach/export [kubernetes]: https://kubernetes.io/ [releases]: https://github.com/absmach/magistrala/releases [drasko]: https://github.com/drasko @@ -190,4 +189,4 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w [dusanm]: https://github.com/malidukica [mirko]: https://github.com/mteodor [rodneyosodo]: https://github.com/rodneyosodo -[callhome]: https://deployments.mainflux.io +[callhome]: https://deployments.magistrala.abstractmachines.fr/ diff --git a/api/asyncapi/mqtt.yml b/api/asyncapi/mqtt.yml index 7f7ba0afc3..540250c3ba 100644 --- a/api/asyncapi/mqtt.yml +++ b/api/asyncapi/mqtt.yml @@ -9,7 +9,7 @@ info: contact: name: Magistrala Team url: 'https://github.com/absmach/magistrala' - email: info@mainflux.com + email: info@abstractmachines.fr description: | MQTT adapter provides an MQTT API for sending messages through the platform. MQTT adapter uses [mProxy](https://github.com/absmach/mproxy) for proxying traffic between client and MQTT broker. Additionally, the MQTT adapter and the message broker are replicating the traffic between brokers. diff --git a/api/asyncapi/websocket.yml b/api/asyncapi/websocket.yml index 5f75a4501c..d77db0bca9 100644 --- a/api/asyncapi/websocket.yml +++ b/api/asyncapi/websocket.yml @@ -10,7 +10,7 @@ info: contact: name: Magistrala Team url: 'https://github.com/absmach/magistrala' - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: 'https://github.com/absmach/magistrala/blob/master/LICENSE' diff --git a/api/openapi/README.md b/api/openapi/README.md index 1d635234ab..09dbcfc030 100644 --- a/api/openapi/README.md +++ b/api/openapi/README.md @@ -2,4 +2,4 @@ This folder contains an OpenAPI specifications for Magistrala API. -View specification in Swagger UI at [api.mainflux.io](https://api.mainflux.io) \ No newline at end of file +View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.magistrala.abstractmachines.fr) \ No newline at end of file diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml index 5d798276fd..15dffb967a 100644 --- a/api/openapi/auth.yml +++ b/api/openapi/auth.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/bootstrap.yml b/api/openapi/bootstrap.yml index 2a8177a54f..fc8f98b5a0 100644 --- a/api/openapi/bootstrap.yml +++ b/api/openapi/bootstrap.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/certs.yml b/api/openapi/certs.yml index e8a7e88f4d..9bfb2e471e 100644 --- a/api/openapi/certs.yml +++ b/api/openapi/certs.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/consumers-notifiers.yml b/api/openapi/consumers-notifiers.yml index 067d8f96bf..9134944426 100644 --- a/api/openapi/consumers-notifiers.yml +++ b/api/openapi/consumers-notifiers.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/http.yml b/api/openapi/http.yml index 5a5a985f09..18d69bffc3 100644 --- a/api/openapi/http.yml +++ b/api/openapi/http.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/invitations.yml b/api/openapi/invitations.yml index 0dd3c3fc8d..acaefd6456 100644 --- a/api/openapi/invitations.yml +++ b/api/openapi/invitations.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/provision.yml b/api/openapi/provision.yml index e7d57dae24..83ed48f4cb 100644 --- a/api/openapi/provision.yml +++ b/api/openapi/provision.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstracmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/readers.yml b/api/openapi/readers.yml index 2391060e27..3ea4846615 100644 --- a/api/openapi/readers.yml +++ b/api/openapi/readers.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/things.yml b/api/openapi/things.yml index 4ed91b8cdf..74843303cb 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/twins.yml b/api/openapi/twins.yml index fd826ca319..6f85e0ce05 100644 --- a/api/openapi/twins.yml +++ b/api/openapi/twins.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 3c2bfb5dcb..44b98b42b5 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -9,7 +9,7 @@ info: Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: - email: info@mainflux.com + email: info@abstractmachines.fr license: name: Apache 2.0 url: https://github.com/absmach/magistrala/blob/master/LICENSE diff --git a/auth/README.md b/auth/README.md index e3b1bc34fb..9950b7c5a7 100644 --- a/auth/README.md +++ b/auth/README.md @@ -154,6 +154,6 @@ Setting `MG_AUTH_GRPC_SERVER_CERT` and `MG_AUTH_GRPC_SERVER_KEY` will enable TLS ## Usage -For more information about service capabilities and its usage, please check out the [API documentation](https://api.mainflux.io/?urls.primaryName=auth.yml). +For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=auth.yml). -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/auth/tracing/doc.go b/auth/tracing/doc.go index 2b8a06f366..5aa1b44b98 100644 --- a/auth/tracing/doc.go +++ b/auth/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala Users service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/bootstrap/README.md b/bootstrap/README.md index ce44cb381d..32c2299aee 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -119,4 +119,4 @@ Setting `MG_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS ## Usage -For more information about service capabilities and its usage, please check out the [API documentation](https://api.mainflux.io/?urls.primaryName=bootstrap.yml). +For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=bootstrap.yml). diff --git a/bootstrap/tracing/doc.go b/bootstrap/tracing/doc.go index 2b8a06f366..5aa1b44b98 100644 --- a/bootstrap/tracing/doc.go +++ b/bootstrap/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala Users service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/certs/README.md b/certs/README.md index a0e727dba7..1b921db23d 100644 --- a/certs/README.md +++ b/certs/README.md @@ -127,4 +127,4 @@ Setting `MG_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS ## Usage -For more information about service capabilities and its usage, please check out the [Certs section](https://docs.mainflux.io/certs/). +For more information about service capabilities and its usage, please check out the [Certs section](https://docs.magistrala.abstractmachines.fr/certs/). diff --git a/certs/tracing/doc.go b/certs/tracing/doc.go index 50bde7ad88..6a419f3b5c 100644 --- a/certs/tracing/doc.go +++ b/certs/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala Users Groups service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/coap/tracing/doc.go b/coap/tracing/doc.go index 653346469d..2d65dbe4e1 100644 --- a/coap/tracing/doc.go +++ b/coap/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala WebSocket adapter service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/consumers/README.md b/consumers/README.md index 6a55a08315..f4e2f28bba 100644 --- a/consumers/README.md +++ b/consumers/README.md @@ -13,6 +13,6 @@ For an in-depth explanation of the usage of `consumers`, as well as thorough understanding of Magistrala, please check out the [official documentation][doc]. For more information about service capabilities and its usage, please check out -the [API documentation](https://api.mainflux.io/?urls.primaryName=consumers-notifiers-openapi.yml). +the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yml). -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/consumers/notifiers/README.md b/consumers/notifiers/README.md index 4efb05fc3e..1866719671 100644 --- a/consumers/notifiers/README.md +++ b/consumers/notifiers/README.md @@ -20,4 +20,4 @@ default values. Subscriptions service will start consuming messages and sending notifications when a message is received. -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/consumers/notifiers/smpp/README.md b/consumers/notifiers/smpp/README.md index 6de3842d9c..784763dee7 100644 --- a/consumers/notifiers/smpp/README.md +++ b/consumers/notifiers/smpp/README.md @@ -47,4 +47,5 @@ default values. Starting service will start consuming messages and sending SMS when a message is received. -[doc]: http://mainflux.readthedocs.io +[doc]: https://docs.magistrala.abstractmachines.fr + diff --git a/consumers/notifiers/smtp/README.md b/consumers/notifiers/smtp/README.md index 80ec830ae7..ce0c64f39f 100644 --- a/consumers/notifiers/smtp/README.md +++ b/consumers/notifiers/smtp/README.md @@ -48,4 +48,4 @@ default values. Starting service will start consuming messages and sending emails when a message is received. -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/consumers/notifiers/tracing/doc.go b/consumers/notifiers/tracing/doc.go index 653346469d..2d65dbe4e1 100644 --- a/consumers/notifiers/tracing/doc.go +++ b/consumers/notifiers/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala WebSocket adapter service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/consumers/writers/README.md b/consumers/writers/README.md index 2bb3663dbc..3bfd0e6b6e 100644 --- a/consumers/writers/README.md +++ b/consumers/writers/README.md @@ -12,5 +12,5 @@ the [Docker Compose][compose] file. For an in-depth explanation of the usage of `writers`, as well as thorough understanding of Magistrala, please check out the [official documentation][doc]. -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr [compose]: ../docker/docker-compose.yml diff --git a/consumers/writers/cassandra/README.md b/consumers/writers/cassandra/README.md index 4ee5ce3813..a9bb2140b8 100644 --- a/consumers/writers/cassandra/README.md +++ b/consumers/writers/cassandra/README.md @@ -78,4 +78,4 @@ execute following command: Starting service will start consuming normalized messages in SenML format. -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/consumers/writers/influxdb/README.md b/consumers/writers/influxdb/README.md index 128883fdb9..8aa28e8dbc 100644 --- a/consumers/writers/influxdb/README.md +++ b/consumers/writers/influxdb/README.md @@ -102,4 +102,4 @@ _Please note that you need to start core services before the additional ones._ Starting service will start consuming normalized messages in SenML format. -Official docs can be found [here](https://docs.mainflux.io). +Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). diff --git a/go.mod b/go.mod index ec3b1a9e03..def75f38b1 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,19 @@ go 1.21.5 toolchain go1.21.6 require ( - github.com/0x6flab/namegenerator v1.2.0 + github.com/0x6flab/namegenerator v1.3.1 github.com/absmach/callhome v0.14.0 github.com/absmach/mproxy v0.4.2 github.com/absmach/senml v1.0.5 - github.com/authzed/authzed-go v0.10.1 - github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 + github.com/authzed/authzed-go v0.11.1 + github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b github.com/caarlos0/env/v10 v10.0.0 - github.com/cenkalti/backoff/v4 v4.2.1 - github.com/docker/docker v24.0.9+incompatible + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/docker/docker v26.0.0+incompatible github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/fatih/color v1.16.0 github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba - github.com/go-chi/chi/v5 v5.0.11 + github.com/go-chi/chi/v5 v5.0.12 github.com/go-kit/kit v0.13.0 github.com/go-redis/redis/v8 v8.11.5 github.com/gocql/gocql v1.6.0 @@ -25,50 +25,51 @@ require ( github.com/gookit/color v1.5.4 github.com/gopcua/opcua v0.1.6 github.com/gorilla/websocket v1.5.1 - github.com/hashicorp/vault/api v1.12.0 + github.com/hashicorp/vault/api v1.12.2 github.com/hashicorp/vault/api/auth/approle v0.6.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/ivanpirog/coloredcobra v1.0.1 - github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa - github.com/jackc/pgtype v1.14.1 - github.com/jackc/pgx/v5 v5.5.4 + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 + github.com/jackc/pgtype v1.14.3 + github.com/jackc/pgx/v5 v5.5.5 github.com/jmoiron/sqlx v1.3.5 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/mitchellh/mapstructure v1.5.0 - github.com/nats-io/nats.go v1.32.0 + github.com/nats-io/nats.go v1.34.0 github.com/oklog/ulid/v2 v2.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/pelletier/go-toml v1.9.5 github.com/plgd-dev/go-coap/v2 v2.6.0 - github.com/prometheus/client_golang v1.18.0 + github.com/prometheus/client_golang v1.19.0 github.com/rabbitmq/amqp091-go v1.9.0 github.com/rubenv/sql-migrate v1.6.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 - go.mongodb.org/mongo-driver v1.13.1 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 - go.opentelemetry.io/otel v1.22.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 - go.opentelemetry.io/otel/sdk v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 + go.mongodb.org/mongo-driver v1.14.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 golang.org/x/crypto v0.21.0 - golang.org/x/net v0.21.0 - golang.org/x/oauth2 v0.17.0 + golang.org/x/net v0.22.0 + golang.org/x/oauth2 v0.18.0 golang.org/x/sync v0.6.0 - gonum.org/v1/gonum v0.14.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac - google.golang.org/grpc v1.60.1 + gonum.org/v1/gonum v0.15.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda + google.golang.org/grpc v1.62.1 google.golang.org/protobuf v1.33.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) require ( - cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -80,30 +81,31 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/cli v24.0.7+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/cli v26.0.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/golib/memfile v1.0.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.5.0 // indirect + github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -114,7 +116,6 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -122,7 +123,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jzelinskie/stringz v0.0.3 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect @@ -133,28 +134,32 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect + github.com/onsi/gomega v1.31.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.12 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/pion/dtls/v2 v2.2.9 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/pion/dtls/v2 v2.2.10 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.46.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/procfs v0.13.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/samber/lo v1.39.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -172,19 +177,18 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index a94c2cf8aa..0e95bfef17 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -github.com/0x6flab/namegenerator v1.2.0 h1:RuHRO7NDGQpZ9ajRFP5ALcl66cyi0hqjs1fXDV+3pZE= -github.com/0x6flab/namegenerator v1.2.0/go.mod h1:h28wadnJ13Q7PxpUzAHckVj9ToyAEdx5T186Zj1kp+k= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/0x6flab/namegenerator v1.3.1 h1:sa4ti8oiw6cWhvobLx+P68oeLzJK8A+n20BRkwRA8as= +github.com/0x6flab/namegenerator v1.3.1/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gBdD5oAU8hd0SyY= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -26,10 +28,10 @@ github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/authzed/authzed-go v0.10.1 h1:0aX2Ox9PPPknID92kLs/FnmhCmfl6Ni16v3ZTLsds5M= -github.com/authzed/authzed-go v0.10.1/go.mod h1:ZsaFPCiMjwT0jLW0gCyYzh3elHqhKDDGGRySyykXwqc= -github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 h1:bQeIwWWRI9bl93poTqpix4sYHi+gnXUPK7N6bMtXzBE= -github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= +github.com/authzed/authzed-go v0.11.1 h1:N5CoDgF3Y28oESncviWEa7quGcrpXwe2KbYR4WCeg0M= +github.com/authzed/authzed-go v0.11.1/go.mod h1:w3Q8IbTR2raCDGIWCj2UHXxhQuhmpRPYNRutZjgUkXM= +github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw= +github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -46,8 +48,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -55,8 +57,6 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= @@ -72,17 +72,17 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I= +github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= +github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dsnet/golib/memfile v0.0.0-20190531212259-571cdbcff553/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64= @@ -112,11 +112,11 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= +github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= -github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= -github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= @@ -141,8 +141,9 @@ github.com/go-ocf/go-coap/v2 v2.0.4-0.20200728125043-f38b86f047a7/go.mod h1:X9wV github.com/go-ocf/kit v0.0.0-20200728130040-4aebdb6982bc/go.mod h1:TIsoMT/iB7t9P6ahkcOnsmvS83SIJsv9qXRfz/yLf6M= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -171,9 +172,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -182,7 +182,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -191,8 +190,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gopcua/opcua v0.1.6 h1:B9SVRKQGzcWcwP2QPYN93Uku32+3wL+v5cgzBxE6V5I= @@ -203,8 +202,8 @@ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZH github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -236,14 +235,13 @@ github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEy github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= +github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= github.com/hashicorp/vault/api/auth/approle v0.6.0/go.mod h1:CCoIl1xBC3lAWpd1HV+0ovk76Z8b8Mdepyk21h3pGk0= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -264,10 +262,10 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -283,27 +281,31 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.1 h1:LyDar7M2K0tShCWqzJ/ctzF1QC3Wzc9c8a6cHE0PFdc= -github.com/jackc/pgtype v1.14.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= +github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c h1:Dznn52SgVIVst9UyOT9brctYUgxs+CvVfPaC3jKrA50= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -318,9 +320,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -383,13 +384,14 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/nats-io/nats.go v1.32.0 h1:Bx9BZS+aXYlxW08k8Gd3yR2s73pV5XSoAQUyp1Kwvp0= -github.com/nats-io/nats.go v1.32.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -402,12 +404,12 @@ github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -420,18 +422,17 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pion/dtls/v2 v2.0.1-0.20200503085337-8e86b3a7d585/go.mod h1:/GahSOC8ZY/+17zkaGJIG4OUkSGAcZu/N/g3roBOCkM= github.com/pion/dtls/v2 v2.0.10-0.20210502094952-3dc563b9aede/go.mod h1:86wv5dgx2J/z871nUR+5fTTY9tISLUlo+C5Gm86r1Hs= -github.com/pion/dtls/v2 v2.2.9 h1:K+D/aVf9/REahQvqk6G5JavdrD8W1PWDKC11UlwN7ts= -github.com/pion/dtls/v2 v2.2.9/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA= +github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= @@ -451,20 +452,20 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= +github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -479,6 +480,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= @@ -518,7 +521,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -547,31 +550,30 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= -go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -612,17 +614,17 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -633,8 +635,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -653,19 +655,19 @@ golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210502030024-e5908800b52b/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -707,11 +709,11 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -719,11 +721,11 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -763,16 +765,16 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= +gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -781,19 +783,17 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/http/README.md b/http/README.md index 4433f2d8f4..39f586fdf9 100644 --- a/http/README.md +++ b/http/README.md @@ -68,4 +68,4 @@ Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` w ## Usage -HTTP Authorization request header contains the credentials to authenticate a Thing. The authorization header can be a plain Thing key or a Thing key encoded as a password for Basic Authentication. In case the Basic Authentication schema is used, the username is ignored. For more information about service capabilities and its usage, please check out the [API documentation](https://api.mainflux.io/?urls.primaryName=http.yml). +HTTP Authorization request header contains the credentials to authenticate a Thing. The authorization header can be a plain Thing key or a Thing key encoded as a password for Basic Authentication. In case the Basic Authentication schema is used, the username is ignored. For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=http.yml). diff --git a/internal/groups/postgres/groups_test.go b/internal/groups/postgres/groups_test.go index 7ade7e219e..6d0f781bf4 100644 --- a/internal/groups/postgres/groups_test.go +++ b/internal/groups/postgres/groups_test.go @@ -22,7 +22,7 @@ import ( ) var ( - namegen = namegenerator.NewNameGenerator() + namegen = namegenerator.NewGenerator() invalidID = strings.Repeat("a", 37) validGroup = mggroups.Group{ ID: testsutil.GenerateUUID(&testing.T{}), diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index d7ef70d437..27748e62fd 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -30,7 +30,7 @@ import ( var ( idProvider = uuid.New() token = "token" - namegen = namegenerator.NewNameGenerator() + namegen = namegenerator.NewGenerator() validGroup = mggroups.Group{ Name: namegen.Generate(), Description: namegen.Generate(), diff --git a/internal/groups/tracing/doc.go b/internal/groups/tracing/doc.go index 50bde7ad88..6a419f3b5c 100644 --- a/internal/groups/tracing/doc.go +++ b/internal/groups/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala Users Groups service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/invitations/README.md b/invitations/README.md index bdc6483510..e522a357de 100644 --- a/invitations/README.md +++ b/invitations/README.md @@ -77,4 +77,4 @@ $GOBIN/magistrala-invitation ## Usage -For more information about service capabilities and its usage, please check out the [API documentation](https://api.mainflux.io/?urls.primaryName=invitations.yml). +For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=invitations.yml). diff --git a/lora/README.md b/lora/README.md index 271c5db5c5..8a07724763 100644 --- a/lora/README.md +++ b/lora/README.md @@ -84,4 +84,4 @@ docker-compose -f docker/addons/lora-adapter/docker-compose.yml up -d ## Usage -For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.mainflux.io/lora). +For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.magistrala.abstractmachines.fr/lora). diff --git a/mqtt/tracing/doc.go b/mqtt/tracing/doc.go index 4f7e50ab8a..88ed02e7f0 100644 --- a/mqtt/tracing/doc.go +++ b/mqtt/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala MQTT adapter service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/opcua/README.md b/opcua/README.md index 8562be727c..c1d2cde2e1 100644 --- a/opcua/README.md +++ b/opcua/README.md @@ -74,4 +74,4 @@ docker-compose -f docker/addons/opcua-adapter/docker-compose.yml up -d ## Usage -For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.mainflux.io/opcua). +For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.magistrala.abstractmachines.fr/opcua). diff --git a/pkg/clients/postgres/clients_test.go b/pkg/clients/postgres/clients_test.go index c87ee944a8..aeb855b001 100644 --- a/pkg/clients/postgres/clients_test.go +++ b/pkg/clients/postgres/clients_test.go @@ -27,7 +27,7 @@ import ( var ( password = "$tr0ngPassw0rd" emailSuffix = "@example.com" - namegen = namegenerator.NewNameGenerator() + namegen = namegenerator.NewGenerator() ) func TestRetrieveByID(t *testing.T) { @@ -150,7 +150,7 @@ func TestRetrieveAll(t *testing.T) { Identity: namegen.Generate() + emailSuffix, Secret: password, }, - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), Metadata: mgclients.Metadata{ "department": namegen.Generate(), }, @@ -683,7 +683,7 @@ func TestRetrieveByIDs(t *testing.T) { Identity: name + emailSuffix, Secret: password, }, - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), Metadata: map[string]interface{}{"name": name}, CreatedAt: time.Now().UTC().Truncate(time.Millisecond), Status: clients.EnabledStatus, @@ -1501,7 +1501,7 @@ func TestUpdateTags(t *testing.T) { desc: "for enabled client", client: mgclients.Client{ ID: client1.ID, - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), }, err: nil, }, @@ -1509,7 +1509,7 @@ func TestUpdateTags(t *testing.T) { desc: "for disabled client", client: mgclients.Client{ ID: client2.ID, - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), }, err: repoerr.ErrNotFound, }, @@ -1517,7 +1517,7 @@ func TestUpdateTags(t *testing.T) { desc: "for invalid client", client: mgclients.Client{ ID: testsutil.GenerateUUID(t), - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), }, err: repoerr.ErrNotFound, }, @@ -1825,7 +1825,7 @@ func generateClient(t *testing.T, status mgclients.Status, role mgclients.Role, Identity: namegen.Generate() + emailSuffix, Secret: password, }, - Tags: namegen.GenerateNames(5), + Tags: namegen.GenerateMultiple(5), Metadata: mgclients.Metadata{ "name": namegen.Generate(), }, diff --git a/pkg/messaging/nats/tracing/doc.go b/pkg/messaging/nats/tracing/doc.go index 6ea3f2db4e..5f8df0d9e0 100644 --- a/pkg/messaging/nats/tracing/doc.go +++ b/pkg/messaging/nats/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala things policies service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/pkg/messaging/rabbitmq/tracing/doc.go b/pkg/messaging/rabbitmq/tracing/doc.go index 6ea3f2db4e..5f8df0d9e0 100644 --- a/pkg/messaging/rabbitmq/tracing/doc.go +++ b/pkg/messaging/rabbitmq/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala things policies service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/pkg/messaging/tracing/doc.go b/pkg/messaging/tracing/doc.go index 6ea3f2db4e..5f8df0d9e0 100644 --- a/pkg/messaging/tracing/doc.go +++ b/pkg/messaging/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala things policies service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/provision/README.md b/provision/README.md index 3446efae31..d790dac402 100644 --- a/provision/README.md +++ b/provision/README.md @@ -7,7 +7,7 @@ For gateways to communicate with [Magistrala][magistrala] configuration is requi To create bootstrap configuration you can use [Bootstrap][bootstrap] or `Provision` service. [Magistrala UI][mgxui] uses [Bootstrap][bootstrap] service for creating gateway configurations. `Provision` service should provide an easy way of provisioning your gateways i.e creating bootstrap configuration and as many things and channels that your setup requires. -Also you may use provision service to create certificates for each thing. Each service running on gateway may require more than one thing and channel for communication. Let's say that you are using services [Agent][agent] and [Export](https://github.com/mainflux/export) on a gateway you will need two channels for `Agent` (`data` and `control`) and one for `Export` and one thing. Additionally if you enabled mtls each service will need its own thing and certificate for access to [Magistrala][magistrala]. Your setup could require any number of things and channels this kind of setup we can call `provision layout`. +Also you may use provision service to create certificates for each thing. Each service running on gateway may require more than one thing and channel for communication. Let's say that you are using services [Agent][agent] and [Export][export] on a gateway you will need two channels for `Agent` (`data` and `control`) and one for `Export` and one thing. Additionally if you enabled mtls each service will need its own thing and certificate for access to [Magistrala][magistrala]. Your setup could require any number of things and channels this kind of setup we can call `provision layout`. Provision service provides a way of specifying this `provision layout` and creating a setup according to that layout by serving requests on `/mapping` endpoint. Provision layout is configured in [config.toml](configs/config.toml). @@ -189,6 +189,6 @@ curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer "tiv" | | lt | Return values that are superstrings of the query and not equal to the query | lt["active"] -> "active" and "tiv" | -Official docs can be found [here](https://docs.mainflux.io). +Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). diff --git a/readers/mongodb/README.md b/readers/mongodb/README.md index c269825c4a..9a53a6d132 100644 --- a/readers/mongodb/README.md +++ b/readers/mongodb/README.md @@ -87,10 +87,10 @@ docker-compose -f docker/addons/mongodb-reader/docker-compose.yml up -d ## Usage -Service exposes [HTTP API](https://api.mainflux.io/?urls.primaryName=readers-openapi.yml) for fetching messages. +Service exposes [HTTP API](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=readers-openapi.yml) for fetching messages. ``` Note: MongoDB Reader doesn't support searching substrings from string_value, due to inefficient searching as the current data model is not suitable for this type of queries. ``` -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/readers/postgres/README.md b/readers/postgres/README.md index b2ecf9467e..16384533f2 100644 --- a/readers/postgres/README.md +++ b/readers/postgres/README.md @@ -98,4 +98,4 @@ Comparator Usage Guide: | le | Return values that are superstrings of the query | le["active"] -> "tiv" | | lt | Return values that are superstrings of the query and not equal to the query | lt["active"] -> "active" and "tiv" | -Official docs can be found [here](https://docs.mainflux.io). +Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). diff --git a/readers/timescale/README.md b/readers/timescale/README.md index 040af025ba..ab29c0ce1b 100644 --- a/readers/timescale/README.md +++ b/readers/timescale/README.md @@ -96,4 +96,4 @@ Comparator Usage Guide: | le | Return values that are superstrings of the query | le["active"] -> "tiv" | | lt | Return values that are superstrings of the query and not equal to the query | lt["active"] -> "active" and "tiv" | -Official docs can be found [here](https://docs.mainflux.io). +Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). diff --git a/things/README.md b/things/README.md index 10336a8c38..88add98de1 100644 --- a/things/README.md +++ b/things/README.md @@ -117,6 +117,6 @@ To run service in a standalone mode, set `MG_THINGS_STANDALONE_EMAIL` and `MG_TH ## Usage For more information about service capabilities and its usage, please check out -the [API documentation](https://api.mainflux.io/?urls.primaryName=things-openapi.yml). +the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=things-openapi.yml). -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/things/api/http/endpoints_test.go b/things/api/http/endpoints_test.go index 677725bd0a..4ad60a07cd 100644 --- a/things/api/http/endpoints_test.go +++ b/things/api/http/endpoints_test.go @@ -48,7 +48,7 @@ var ( inValidToken = "invalid" inValid = "invalid" validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - namesgen = namegenerator.NewNameGenerator() + namesgen = namegenerator.NewGenerator() ) const contentType = "application/json" diff --git a/things/postgres/clients_test.go b/things/postgres/clients_test.go index fb9edd4dcf..18f4bae136 100644 --- a/things/postgres/clients_test.go +++ b/things/postgres/clients_test.go @@ -27,7 +27,7 @@ var ( clientName = "client name" invalidClientID = "invalidClientID" invalidDomainID = strings.Repeat("m", maxNameSize+10) - namesgen = namegenerator.NewNameGenerator() + namesgen = namegenerator.NewGenerator() ) func TestClientsSave(t *testing.T) { diff --git a/things/tracing/doc.go b/things/tracing/doc.go index df8ba65b36..1d803beca5 100644 --- a/things/tracing/doc.go +++ b/things/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala things clients service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/tools/e2e/README.md b/tools/e2e/README.md index 8e8c2a4a9d..6e35845144 100644 --- a/tools/e2e/README.md +++ b/tools/e2e/README.md @@ -13,11 +13,11 @@ make ```bash ./e2e --help -Tool for testing end-to-end flow of mainflux by doing a couple of operations namely: +Tool for testing end-to-end flow of Magistrala by doing a couple of operations namely: 1. Creating, viewing, updating and changing status of users, groups, things and channels. 2. Connecting users and groups to each other and things and channels to each other. 3. Sending messages from things to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT). -Complete documentation is available at https://docs.mainflux.io +Complete documentation is available at https://docs.magistrala.abstractmachines.fr Usage: @@ -28,7 +28,7 @@ Usage: Examples: Here is a simple example of using e2e tool. -Use the following commands from the root mainflux directory: +Use the following commands from the root Magistrala directory: go run tools/e2e/cmd/main.go go run tools/e2e/cmd/main.go --host 142.93.118.47 @@ -38,7 +38,7 @@ go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --p Flags: -h, --help help for e2e - -H, --host string address for a running mainflux instance (default "localhost") + -H, --host string address for a running Magistrala instance (default "localhost") -n, --num uint number of users, groups, channels and things to create and connect (default 10) -N, --num_of_messages uint number of messages to send (default 10) -p, --prefix string name prefix for users, groups, things and channels diff --git a/tools/e2e/cmd/main.go b/tools/e2e/cmd/main.go index d145dd318b..5574382a65 100644 --- a/tools/e2e/cmd/main.go +++ b/tools/e2e/cmd/main.go @@ -24,7 +24,7 @@ func main() { "1. Creating, viewing, updating and changing status of users, groups, things and channels.\n" + "2. Connecting users and groups to each other and things and channels to each other.\n" + "3. Sending messages from things to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT).\n" + - "Complete documentation is available at https://docs.mainflux.io", + "Complete documentation is available at https://docs.magistrala.abstractmachines.fr", Example: "Here is a simple example of using e2e tool.\n" + "Use the following commands from the root magistrala directory:\n\n" + "go run tools/e2e/cmd/main.go\n" + diff --git a/tools/e2e/e2e.go b/tools/e2e/e2e.go index 5fa4cfd2c4..cc89757d9d 100644 --- a/tools/e2e/e2e.go +++ b/tools/e2e/e2e.go @@ -31,7 +31,7 @@ const ( ) var ( - namesgenerator = namegenerator.NewNameGenerator() + namesgenerator = namegenerator.NewGenerator() msgFormat = `[{"bn":"demo", "bu":"V", "t": %d, "bver":5, "n":"voltage", "u":"V", "v":%d}]` ) diff --git a/tools/mqtt-bench/README.md b/tools/mqtt-bench/README.md index 35399e0047..f94eb4d216 100644 --- a/tools/mqtt-bench/README.md +++ b/tools/mqtt-bench/README.md @@ -21,7 +21,7 @@ The tool supports multiple concurrent clients, publishers and subscribers config ``` ./mqtt-bench --help Tool for extensive load and benchmarking of MQTT brokers used within Magistrala platform. -Complete documentation is available at https://docs.mainflux.io +Complete documentation is available at https://docs.magistrala.abstractmachines.fr Usage: mqtt-bench [flags] diff --git a/tools/mqtt-bench/cmd/main.go b/tools/mqtt-bench/cmd/main.go index 41da3ed064..f3edf7d369 100644 --- a/tools/mqtt-bench/cmd/main.go +++ b/tools/mqtt-bench/cmd/main.go @@ -21,7 +21,7 @@ func main() { Use: "mqtt-bench", Short: "mqtt-bench is MQTT benchmark tool for Magistrala", Long: `Tool for exctensive load and benchmarking of MQTT brokers used within the Magistrala platform. -Complete documentation is available at https://docs.mainflux.io`, +Complete documentation is available at https://docs.magistrala.abstractmachines.fr`, Run: func(cmd *cobra.Command, args []string) { if confFile != "" { viper.SetConfigFile(confFile) diff --git a/tools/provision/README.md b/tools/provision/README.md index 11b1d08593..77d7068372 100644 --- a/tools/provision/README.md +++ b/tools/provision/README.md @@ -15,7 +15,7 @@ make ``` ./provision --help Tool for provisioning series of Magistrala channels and things and connecting them together. -Complete documentation is available at https://docs.mainflux.io +Complete documentation is available at https://docs.magistrala.abstractmachines.fr Usage: provision [flags] diff --git a/tools/provision/cmd/main.go b/tools/provision/cmd/main.go index 4fd275ed74..1b7461e14d 100644 --- a/tools/provision/cmd/main.go +++ b/tools/provision/cmd/main.go @@ -18,7 +18,7 @@ func main() { Use: "provision", Short: "provision is provisioning tool for Magistrala", Long: `Tool for provisioning series of Magistrala channels and things and connecting them together. -Complete documentation is available at https://docs.mainflux.io`, +Complete documentation is available at https://docs.magistrala.abstractmachines.fr`, Run: func(_ *cobra.Command, _ []string) { if err := provision.Provision(pconf); err != nil { log.Fatal(err) diff --git a/twins/README.md b/twins/README.md index 559457fe81..dee0bfec91 100644 --- a/twins/README.md +++ b/twins/README.md @@ -98,6 +98,6 @@ magistrala natively, than do the same thing in the corresponding console environment. For more information about service capabilities and its usage, please check out -the [API documentation](https://api.mainflux.io/?urls.primaryName=twins-openapi.yml). +the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=twins-openapi.yml). -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/users/README.md b/users/README.md index aa0039e8e8..3b1583b07c 100644 --- a/users/README.md +++ b/users/README.md @@ -119,6 +119,6 @@ Setting `MG_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS ## Usage -For more information about service capabilities and its usage, please check out the [API documentation](https://api.mainflux.io/?urls.primaryName=users-openapi.yml). +For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=users-openapi.yml). -[doc]: https://docs.mainflux.io +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/users/postgres/clients_test.go b/users/postgres/clients_test.go index dd2a79a57d..dd86ca887e 100644 --- a/users/postgres/clients_test.go +++ b/users/postgres/clients_test.go @@ -24,7 +24,7 @@ const maxNameSize = 254 var ( invalidName = strings.Repeat("m", maxNameSize+10) password = "$tr0ngPassw0rd" - namesgen = namegenerator.NewNameGenerator() + namesgen = namegenerator.NewGenerator() ) func TestClientsSave(t *testing.T) { diff --git a/users/tracing/doc.go b/users/tracing/doc.go index 2b8a06f366..5aa1b44b98 100644 --- a/users/tracing/doc.go +++ b/users/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala Users service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing diff --git a/ws/README.md b/ws/README.md index 405924c17c..339604406c 100644 --- a/ws/README.md +++ b/ws/README.md @@ -68,4 +68,4 @@ Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` w ## Usage -For more information about service capabilities and its usage, please check out the [WebSocket section](https://docs.mainflux.io/messaging/#websocket). +For more information about service capabilities and its usage, please check out the [WebSocket section](https://docs.magistrala.abstractmachines.fr/messaging/#websocket). diff --git a/ws/doc.go b/ws/doc.go index 778b580ba6..67c9b3caf5 100644 --- a/ws/doc.go +++ b/ws/doc.go @@ -11,5 +11,5 @@ // clients and servers. // // For more details about Magistrala messaging and WebSocket adapter service, -// please refer to the documentation at https://docs.mainflux.io/messaging/#websocket. +// please refer to the documentation at https://docs.magistrala.abstractmachines.fr/messaging/#websocket. package ws diff --git a/ws/tracing/doc.go b/ws/tracing/doc.go index 653346469d..2d65dbe4e1 100644 --- a/ws/tracing/doc.go +++ b/ws/tracing/doc.go @@ -8,5 +8,5 @@ // Magistrala WebSocket adapter service. // // For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.mainflux.io/tracing/. +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. package tracing From 4c4217d94795ee6458371fecd6404d59f5296410 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:01:42 +0200 Subject: [PATCH 69/71] NOISSUE - Fix Failing Users Property Based Tests (#2134) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- api/openapi/users.yml | 18 ++++++++++---- auth/service.go | 3 +-- internal/groups/service.go | 35 ++++++++++++++------------- pkg/errors/service/types.go | 3 +++ pkg/sdk/go/channels_test.go | 10 ++++---- pkg/sdk/go/groups_test.go | 12 +++++----- pkg/sdk/go/things_test.go | 2 +- pkg/sdk/go/tokens_test.go | 4 ++-- pkg/sdk/go/users_test.go | 10 ++++---- things/service.go | 4 ++-- users/service.go | 47 ++++++++++++++----------------------- users/service_test.go | 28 ++++++++++------------ 12 files changed, 86 insertions(+), 90 deletions(-) diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 44b98b42b5..63d7643e8b 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -383,7 +383,7 @@ paths: tags: - Users parameters: - - $ref: "#/components/parameters/Referrer" + - $ref: "#/components/parameters/Referer" requestBody: $ref: "#/components/requestBodies/RequestPasswordReset" responses: @@ -391,6 +391,8 @@ paths: description: Users link for resetting password. "400": description: Failed due to malformed JSON. + "404": + description: A non-existent entity request. "415": description: Missing or invalid content type. "422": @@ -883,8 +885,8 @@ paths: security: - bearerAuth: [] responses: - "200": - description: Member assigned. + "204": + description: Member unassigned. "400": description: Failed due to malformed group's ID. "401": @@ -1211,6 +1213,7 @@ components: secret: type: string example: password + minimum: 8 description: User secret password. metadata: type: object @@ -1352,10 +1355,12 @@ components: old_secret: type: string example: oldpassword + minimum: 8 description: Old user secret password. new_secret: type: string example: newpassword + minimum: 8 description: New user secret password. required: - old_secret @@ -1451,6 +1456,7 @@ components: secret: type: string example: password + minimum: 8 description: User secret password. required: - identity @@ -1490,8 +1496,8 @@ components: example: 1970-01-01_00:00:00 parameters: - Referrer: - name: Referrer + Referer: + name: Referer description: Host being sent by browser. in: header schema: @@ -1815,9 +1821,11 @@ components: password: type: string format: password + minimum: 8 description: New password. old_password: type: string + minimum: 8 format: password description: Old password. diff --git a/auth/service.go b/auth/service.go index ba2a4781a4..83b34c6f1e 100644 --- a/auth/service.go +++ b/auth/service.go @@ -10,7 +10,6 @@ import ( "time" "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/postgres" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" ) @@ -670,7 +669,7 @@ func (svc service) ListDomains(ctx context.Context, token string, p Page) (Domai } dp, err := svc.domains.ListDomains(ctx, p) if err != nil { - return DomainsPage{}, postgres.HandleError(svcerr.ErrViewEntity, err) + return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } if p.SubjectID == "" { for i := range dp.Domains { diff --git a/internal/groups/service.go b/internal/groups/service.go index d802caeba3..e3e3133cc4 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -20,10 +20,9 @@ import ( ) var ( - errParentUnAuthz = errors.New("failed to authorize parent group") - errMemberKind = errors.New("invalid member kind") - errRetrieveGroups = errors.New("failed to retrieve groups") - errGroupIDs = errors.New("invalid group ids") + errParentUnAuthz = errors.New("failed to authorize parent group") + errMemberKind = errors.New("invalid member kind") + errGroupIDs = errors.New("invalid group ids") ) type service struct { @@ -70,7 +69,7 @@ func (svc service) CreateGroup(ctx context.Context, token, kind string, g groups g, err = svc.groups.Save(ctx, g) if err != nil { - return groups.Group{}, err + return groups.Group{}, errors.Wrap(svcerr.ErrCreateEntity, err) } // IMPROVEMENT NOTE: Add defer function , if return err is not nil, then delete group @@ -104,7 +103,7 @@ func (svc service) CreateGroup(ctx context.Context, token, kind string, g groups }) } if _, err := svc.auth.AddPolicies(ctx, &policies); err != nil { - return g, err + return g, errors.Wrap(svcerr.ErrAddPolicies, err) } return g, nil @@ -228,7 +227,7 @@ func (svc service) ListGroups(ctx context.Context, token, memberKind, memberID s gp, err := svc.groups.RetrieveByIDs(ctx, gm, ids...) if err != nil { - return groups.Page{}, err + return groups.Page{}, errors.Wrap(svcerr.ErrViewEntity, err) } if gm.ListPerms && len(gp.Groups) > 0 { @@ -454,7 +453,7 @@ func (svc service) Assign(ctx context.Context, token, groupID, relation, memberK func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) (err error) { groupsPage, err := svc.groups.RetrieveByIDs(ctx, groups.Page{PageMeta: groups.PageMeta{Limit: 1<<63 - 1}}, groupIDs...) if err != nil { - return errors.Wrap(errRetrieveGroups, err) + return errors.Wrap(svcerr.ErrViewEntity, err) } if len(groupsPage.Groups) == 0 { return errGroupIDs @@ -484,7 +483,7 @@ func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID } if _, err := svc.auth.AddPolicies(ctx, &addPolicies); err != nil { - return err + return errors.Wrap(svcerr.ErrAddPolicies, err) } defer func() { if err != nil { @@ -500,7 +499,7 @@ func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) (err error) { groupsPage, err := svc.groups.RetrieveByIDs(ctx, groups.Page{PageMeta: groups.PageMeta{Limit: 1<<63 - 1}}, groupIDs...) if err != nil { - return errors.Wrap(errRetrieveGroups, err) + return errors.Wrap(svcerr.ErrViewEntity, err) } if len(groupsPage.Groups) == 0 { return errGroupIDs @@ -530,7 +529,7 @@ func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupI } if _, err := svc.auth.DeletePolicies(ctx, &deletePolicies); err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } defer func() { if err != nil { @@ -616,7 +615,7 @@ func (svc service) DeleteGroup(ctx context.Context, token, groupID string) error Subject: groupID, ObjectType: auth.GroupType, }); err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } // Remove policy of things @@ -625,7 +624,7 @@ func (svc service) DeleteGroup(ctx context.Context, token, groupID string) error Subject: groupID, ObjectType: auth.ThingType, }); err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } // Remove policy from domain @@ -634,12 +633,12 @@ func (svc service) DeleteGroup(ctx context.Context, token, groupID string) error Object: groupID, ObjectType: auth.GroupType, }); err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } // Remove group from database if err := svc.groups.Delete(ctx, groupID); err != nil { - return err + return errors.Wrap(svcerr.ErrRemoveEntity, err) } // Remove policy of users @@ -648,7 +647,7 @@ func (svc service) DeleteGroup(ctx context.Context, token, groupID string) error Object: groupID, ObjectType: auth.GroupType, }); err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } return nil @@ -691,7 +690,7 @@ func (svc service) changeGroupStatus(ctx context.Context, token string, group gr } dbGroup, err := svc.groups.RetrieveByID(ctx, group.ID) if err != nil { - return groups.Group{}, err + return groups.Group{}, errors.Wrap(svcerr.ErrViewEntity, err) } if dbGroup.Status == group.Status { return groups.Group{}, errors.ErrStatusAlreadyAssigned @@ -704,7 +703,7 @@ func (svc service) changeGroupStatus(ctx context.Context, token string, group gr func (svc service) identify(ctx context.Context, token string) (*magistrala.IdentityRes, error) { res, err := svc.auth.Identify(ctx, &magistrala.IdentityReq{Token: token}) if err != nil { - return nil, err + return nil, errors.Wrap(svcerr.ErrAuthentication, err) } if res.GetId() == "" || res.GetDomainId() == "" { return nil, svcerr.ErrDomainAuthorization diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index 28071fb3a5..e7d04936dd 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -61,6 +61,9 @@ var ( // ErrDeletePolicies indicates failed to delete policies. ErrDeletePolicies = errors.New("failed to delete policies") + // ErrIssueToken indicates a failure to issue token. + ErrIssueToken = errors.New("failed to issue token") + // ErrPasswordFormat indicates weak password. ErrPasswordFormat = errors.New("password does not meet the requirements") diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index 4dba870a8a..f0742b3964 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -110,7 +110,7 @@ func TestCreateChannel(t *testing.T) { Status: mgclients.EnabledStatus.String(), }, token: token, - err: errors.NewSDKErrorWithStatus(repoerr.ErrCreateEntity, http.StatusUnprocessableEntity), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrCreateEntity, svcerr.ErrCreateEntity), http.StatusUnprocessableEntity), }, { desc: "create channel with missing name", @@ -203,7 +203,7 @@ func TestListChannels(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -616,7 +616,7 @@ func TestListChannelsByThing(t *testing.T) { clientID: testsutil.GenerateUUID(t), page: sdk.PageMetadata{}, response: []sdk.Channel(nil), - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), }, } @@ -659,7 +659,7 @@ func TestEnableChannel(t *testing.T) { repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableChannel("wrongID", validToken) - assert.Equal(t, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), err, fmt.Sprintf("Enable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) + assert.Equal(t, errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrNotFound), http.StatusNotFound), err, fmt.Sprintf("Enable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") assert.True(t, ok, "RetrieveByID was not called on enabling channel") repoCall.Unset() @@ -711,7 +711,7 @@ func TestDisableChannel(t *testing.T) { repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) _, err := mgsdk.DisableChannel("wrongID", validToken) - assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrNotFound), http.StatusNotFound), fmt.Sprintf("Disable channel with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") assert.True(t, ok, "Memberships was not called on disabling channel with wrong id") repoCall.Unset() diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 459fe826ef..a07993a1c8 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -98,7 +98,7 @@ func TestCreateGroup(t *testing.T) { ParentID: wrongID, Status: clients.EnabledStatus.String(), }, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrCreateEntity, svcerr.ErrCreateEntity), http.StatusUnprocessableEntity), }, { desc: "create group with missing name", @@ -203,7 +203,7 @@ func TestListGroups(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -333,7 +333,7 @@ func TestListParentGroups(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -464,7 +464,7 @@ func TestListChildrenGroups(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, svcerr.ErrAuthentication), http.StatusUnauthorized), response: nil, }, { @@ -796,7 +796,7 @@ func TestEnableGroup(t *testing.T) { repoCall1 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) repoCall2 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) _, err := mgsdk.EnableGroup("wrongID", validToken) - assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Enable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrNotFound), http.StatusNotFound), fmt.Sprintf("Enable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") assert.True(t, ok, "RetrieveByID was not called on enabling group") repoCall.Unset() @@ -849,7 +849,7 @@ func TestDisableGroup(t *testing.T) { repoCall1 := grepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) repoCall2 := grepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, repoerr.ErrNotFound) _, err := mgsdk.DisableGroup("wrongID", validToken) - assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrNotFound), http.StatusNotFound), fmt.Sprintf("Disable group with wrong id: expected %v got %v", svcerr.ErrNotFound, err)) ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") assert.True(t, ok, "Memberships was not called on disabling group with wrong id") repoCall.Unset() diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index 8c8e8f795d..ac463662ac 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -581,7 +581,7 @@ func TestListThingsByChannel(t *testing.T) { channelID: wrongID, page: sdk.PageMetadata{}, response: []sdk.Thing(nil), - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrNotFound, svcerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), }, } diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go index ad5f07a957..be448f08c6 100644 --- a/pkg/sdk/go/tokens_test.go +++ b/pkg/sdk/go/tokens_test.go @@ -11,7 +11,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -69,7 +69,7 @@ func TestIssueToken(t *testing.T) { login: sdk.Login{Identity: "invalid", Secret: "secret"}, token: &magistrala.Token{}, dbClient: wrongClient, - err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), }, } for _, tc := range cases { diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index a591eeb091..0ced676c82 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -257,7 +257,7 @@ func TestListClients(t *testing.T) { token: invalidToken, offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), response: nil, }, { @@ -265,7 +265,7 @@ func TestListClients(t *testing.T) { token: "", offset: offset, limit: limit, - err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), response: nil, }, { @@ -273,7 +273,7 @@ func TestListClients(t *testing.T) { token: token, offset: offset, limit: 0, - err: errors.NewSDKErrorWithStatus(errors.Wrap(repoerr.ErrNotFound, repoerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), response: nil, }, { @@ -411,7 +411,7 @@ func TestClient(t *testing.T) { response: sdk.User{}, token: validToken, clientID: wrongID, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrNotFound, svcerr.ErrNotFound), http.StatusNotFound), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, svcerr.ErrViewEntity), http.StatusUnprocessableEntity), }, { desc: "view client with an invalid token and invalid client id", @@ -821,7 +821,7 @@ func TestUpdateClientSecret(t *testing.T) { token: validToken, response: sdk.User{}, repoErr: apiutil.ErrMissingSecret, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrNotFound, repoerr.ErrMissingSecret), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrViewEntity, repoerr.ErrMissingSecret), http.StatusBadRequest), }, } diff --git a/things/service.go b/things/service.go index 853e2d0e66..e94a7b876a 100644 --- a/things/service.go +++ b/things/service.go @@ -202,7 +202,7 @@ func (svc service) ListClients(ctx context.Context, token, reqUserID string, pm tp, err := svc.clients.RetrieveAllByIDs(ctx, pm) if err != nil { - return mgclients.ClientsPage{}, err + return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } if pm.ListPerms && len(tp.Clients) > 0 { @@ -542,7 +542,7 @@ func (svc service) ListClientsByGroup(ctx context.Context, token, groupID string cp, err := svc.clients.RetrieveAllByIDs(ctx, pm) if err != nil { - return mgclients.MembersPage{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } if pm.ListPerms && len(cp.Clients) > 0 { diff --git a/users/service.go b/users/service.go index dc50085327..ad83914535 100644 --- a/users/service.go +++ b/users/service.go @@ -19,17 +19,6 @@ import ( "golang.org/x/sync/errgroup" ) -var ( - // ErrAddPolicies indictaed a failre to add policies. - errAddPolicies = errors.New("failed to add policies") - - // ErrIssueToken indicates a failure to issue token. - ErrIssueToken = errors.New("failed to issue token") - - // errDeletePolicies indictaed a failre to add policies. - errDeletePolicies = errors.New("failed to delete policies") -) - type service struct { clients postgres.Repository idProvider magistrala.IDProvider @@ -90,7 +79,7 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien defer func() { if err != nil { if errRollback := svc.addClientPolicyRollback(ctx, cli.ID, cli.Role); errRollback != nil { - err = errors.Wrap(err, errors.Wrap(repoerr.ErrRollbackTx, errRollback)) + err = errors.Wrap(errors.Wrap(repoerr.ErrRollbackTx, errRollback), err) } } }() @@ -104,7 +93,7 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien func (svc service) IssueToken(ctx context.Context, identity, secret, domainID string) (*magistrala.Token, error) { dbUser, err := svc.clients.RetrieveByIdentity(ctx, identity) if err != nil { - return &magistrala.Token{}, errors.Wrap(repoerr.ErrNotFound, err) + return &magistrala.Token{}, errors.Wrap(svcerr.ErrViewEntity, err) } if err := svc.hasher.Compare(secret, dbUser.Credentials.Secret); err != nil { return &magistrala.Token{}, errors.Wrap(svcerr.ErrLogin, err) @@ -139,7 +128,7 @@ func (svc service) ViewClient(ctx context.Context, token, id string) (mgclients. client, err := svc.clients.RetrieveByID(ctx, id) if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err) } client.Credentials.Secret = "" @@ -153,7 +142,7 @@ func (svc service) ViewProfile(ctx context.Context, token string) (mgclients.Cli } client, err := svc.clients.RetrieveByID(ctx, id) if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err) } client.Credentials.Secret = "" @@ -168,7 +157,7 @@ func (svc service) ListClients(ctx context.Context, token string, pm mgclients.P if err := svc.checkSuperAdmin(ctx, userID); err == nil { pg, err := svc.clients.RetrieveAll(ctx, pm) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrNotFound, err) + return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } return pg, err } @@ -183,7 +172,7 @@ func (svc service) ListClients(ctx context.Context, token string, pm mgclients.P } pg, err := svc.clients.RetrieveAll(ctx, p) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrNotFound, err) + return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } return pg, nil } @@ -292,7 +281,7 @@ func (svc service) ResetSecret(ctx context.Context, resetToken, secret string) e } c, err := svc.clients.RetrieveByID(ctx, id) if err != nil { - return errors.Wrap(repoerr.ErrNotFound, err) + return errors.Wrap(svcerr.ErrViewEntity, err) } if c.Credentials.Identity == "" { return repoerr.ErrNotFound @@ -323,10 +312,10 @@ func (svc service) UpdateClientSecret(ctx context.Context, token, oldSecret, new } dbClient, err := svc.clients.RetrieveByID(ctx, id) if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err) } if _, err := svc.IssueToken(ctx, dbClient.Credentials.Identity, oldSecret, ""); err != nil { - return mgclients.Client{}, errors.Wrap(ErrIssueToken, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrIssueToken, err) } newSecret, err = svc.hasher.Hash(newSecret) if err != nil { @@ -417,7 +406,7 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client } dbClient, err := svc.clients.RetrieveByID(ctx, client.ID) if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrNotFound, err) } if dbClient.Status == client.Status { return mgclients.Client{}, errors.ErrStatusAlreadyAssigned @@ -462,7 +451,7 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID ObjectType: objectType, }) if err != nil { - return mgclients.MembersPage{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err) } if len(duids.Policies) == 0 { return mgclients.MembersPage{ @@ -480,7 +469,7 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID cp, err := svc.clients.RetrieveAll(ctx, pm) if err != nil { - return mgclients.MembersPage{}, errors.Wrap(repoerr.ErrNotFound, err) + return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } if pm.ListPerms && len(cp.Clients) > 0 { @@ -630,7 +619,7 @@ func (svc service) addClientPolicy(ctx context.Context, userID string, role mgcl } resp, err := svc.auth.AddPolicies(ctx, &policies) if err != nil { - return err + return errors.Wrap(svcerr.ErrAddPolicies, err) } if !resp.Added { return svcerr.ErrAuthorization @@ -660,7 +649,7 @@ func (svc service) addClientPolicyRollback(ctx context.Context, userID string, r } resp, err := svc.auth.DeletePolicies(ctx, &policies) if err != nil { - return err + return errors.Wrap(svcerr.ErrDeletePolicies, err) } if !resp.Deleted { return svcerr.ErrAuthorization @@ -679,10 +668,10 @@ func (svc service) updateClientPolicy(ctx context.Context, userID string, role m Object: auth.MagistralaObject, }) if err != nil { - return errors.Wrap(errAddPolicies, err) + return errors.Wrap(svcerr.ErrAddPolicies, err) } if !resp.Added { - return errors.Wrap(svcerr.ErrAuthorization, err) + return svcerr.ErrAuthorization } return nil case mgclients.UserRole: @@ -696,10 +685,10 @@ func (svc service) updateClientPolicy(ctx context.Context, userID string, role m Object: auth.MagistralaObject, }) if err != nil { - return errors.Wrap(errDeletePolicies, err) + return errors.Wrap(svcerr.ErrDeletePolicies, err) } if !resp.Deleted { - return errors.Wrap(errDeletePolicies, err) + return svcerr.ErrAuthorization } return nil } diff --git a/users/service_test.go b/users/service_test.go index 08c0b1b82c..b419b6ddbe 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -41,13 +41,11 @@ var ( Metadata: validCMetadata, Status: mgclients.EnabledStatus, } - validToken = "token" - inValidToken = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - wrongID = testsutil.GenerateUUID(&testing.T{}) - errHashPassword = errors.New("generate hash from password failed") - errAddPolicies = errors.New("failed to add policies") - errDeletePolicies = errors.New("failed to delete policies") + validToken = "token" + inValidToken = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + wrongID = testsutil.GenerateUUID(&testing.T{}) + errHashPassword = errors.New("generate hash from password failed") ) func newService(selfRegister bool) (users.Service, *mocks.Repository, *authmocks.AuthClient, users.Emailer) { @@ -226,8 +224,8 @@ func TestRegisterClient(t *testing.T) { Role: mgclients.AdminRole, }, addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, - addPoliciesResponseErr: errAddPolicies, - err: errAddPolicies, + addPoliciesResponseErr: svcerr.ErrAddPolicies, + err: svcerr.ErrAddPolicies, }, { desc: "register a new client with failed to delete policies with err", @@ -241,9 +239,9 @@ func TestRegisterClient(t *testing.T) { }, addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, - deletePoliciesResponseErr: errDeletePolicies, + deletePoliciesResponseErr: svcerr.ErrConflict, saveErr: repoerr.ErrConflict, - err: errDeletePolicies, + err: svcerr.ErrConflict, }, { desc: "register a new client with failed to delete policies with failed to delete", @@ -258,7 +256,7 @@ func TestRegisterClient(t *testing.T) { addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: false}, saveErr: repoerr.ErrConflict, - err: svcerr.ErrAuthorization, + err: svcerr.ErrConflict, }, } @@ -1035,7 +1033,7 @@ func TestUpdateClientRole(t *testing.T) { addPolicyResponse: &magistrala.AddPolicyRes{}, addPolicyErr: errors.ErrMalformedEntity, token: validToken, - err: errAddPolicies, + err: svcerr.ErrAddPolicies, }, { desc: "update client role to user role successfully ", @@ -1054,7 +1052,7 @@ func TestUpdateClientRole(t *testing.T) { deletePolicyResponse: &magistrala.DeletePolicyRes{Deleted: false}, updateRoleResponse: mgclients.Client{}, token: validToken, - err: errDeletePolicies, + err: svcerr.ErrFailedPolicyUpdate, }, { desc: "update client role to user role with failed to delete policy with error", @@ -1064,7 +1062,7 @@ func TestUpdateClientRole(t *testing.T) { updateRoleResponse: mgclients.Client{}, token: validToken, deletePolicyErr: svcerr.ErrMalformedEntity, - err: errDeletePolicies, + err: svcerr.ErrDeletePolicies, }, { desc: "Update client with failed repo update and roll back", From 9ffbc5e8752daa8476d0d88fde6012243f02809e Mon Sep 17 00:00:00 2001 From: JMboya <44696487+JeffMboya@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:40:05 +0300 Subject: [PATCH 70/71] MG-2125 - Unable to enable thing using bootstrap (#2132) Signed-off-by: JeffMboya --- bootstrap/service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bootstrap/service.go b/bootstrap/service.go index 2f87d1ae57..aae7d1bf3b 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -325,6 +325,10 @@ func (bs bootstrapService) ChangeState(ctx context.Context, token, id string, st ThingID: cfg.ThingID, } if err := bs.sdk.Connect(conIDs, token); err != nil { + // Ignore conflict errors as they indicate the connection already exists. + if errors.Contains(err, svcerr.ErrConflict) { + continue + } return ErrThings } } From 834dd56426cd40dd8e14ede10dda768859db3f64 Mon Sep 17 00:00:00 2001 From: Veddy Date: Wed, 3 Apr 2024 23:27:23 +0800 Subject: [PATCH 71/71] Reset secret tests --- users/service_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/users/service_test.go b/users/service_test.go index b419b6ddbe..d9638810e6 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -31,7 +31,6 @@ var ( idProvider = uuid.New() phasher = hasher.New() secret = "strongsecret" - weakSecret = "secret" validCMetadata = mgclients.Metadata{"role": "client"} client = mgclients.Client{ ID: testsutil.GenerateUUID(&testing.T{}),