From b80260b96307858590f2483a8632049c3d835628 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Tue, 5 Dec 2023 11:31:32 +0100 Subject: [PATCH] Changed notifications & user.login as main unique id for users --- internals/handlers/notifier_handlers.go | 5 +- .../notifier/notification/notification.go | 22 +- .../notification/notification_export.go | 21 +- .../notification/notification_mock.go | 81 +++++- .../notification/notification_test.go | 232 +++++++++++++++++- internals/notifier/notifier.go | 11 +- internals/router/oidc/oidc_middleware.go | 3 +- internals/router/saml_middleware.go | 3 +- internals/security/users/user.go | 2 +- 9 files changed, 350 insertions(+), 30 deletions(-) diff --git a/internals/handlers/notifier_handlers.go b/internals/handlers/notifier_handlers.go index 34c5274c..e95f45fe 100644 --- a/internals/handlers/notifier_handlers.go +++ b/internals/handlers/notifier_handlers.go @@ -3,6 +3,7 @@ package handlers import ( "github.com/google/uuid" "github.com/myrteametrics/myrtea-engine-api/v5/internals/export" + "github.com/myrteametrics/myrtea-engine-api/v5/internals/notifier/notification" "net/http" "time" @@ -45,14 +46,14 @@ func NotificationsWSRegister(w http.ResponseWriter, r *http.Request) { zap.L().Error("Add new WS Client to manager", zap.Error(err)) return } - go func(client *notifier.WebsocketClient) { + go func(client *notifier.WebsocketClient) { // temporary for tests zap.L().Info("starting notifier") ticker := time.NewTicker(1 * time.Second) after := time.After(30 * time.Second) for { select { case <-ticker.C: - notifier.C().SendToUsers(ExportNotification{Status: export.StatusPending, Export: export.WrapperItem{Id: uuid.New().String(), FileName: "test.bla"}}, []uuid.UUID{user.ID}) + notifier.C().SendToUsers(notification.ExportNotification{Status: export.StatusPending, Export: export.WrapperItem{Id: uuid.New().String(), FileName: "test.bla"}}, []users.UserWithPermissions{user}) zap.L().Info("send notification") case <-after: return diff --git a/internals/notifier/notification/notification.go b/internals/notifier/notification/notification.go index 02354d4f..20c79ab9 100644 --- a/internals/notifier/notification/notification.go +++ b/internals/notifier/notification/notification.go @@ -8,6 +8,7 @@ import ( type Notification interface { ToBytes() ([]byte, error) NewInstance(id int64, data []byte, isRead bool) (Notification, error) + Equals(notification Notification) bool } // BaseNotification data structure represents a basic notification and her current state @@ -28,9 +29,10 @@ func (n BaseNotification) NewInstance(id int64, data []byte, isRead bool) (Notif notification.Id = id notification.IsRead = isRead notification.Notification = notification - return ¬ification, nil + return notification, nil } +// ToBytes convert a notification in a json byte slice to be sent though any required channel func (n BaseNotification) ToBytes() ([]byte, error) { b, err := json.Marshal(n) if err != nil { @@ -38,3 +40,21 @@ func (n BaseNotification) ToBytes() ([]byte, error) { } return b, nil } + +// Equals returns true if the two notifications are equals +func (n BaseNotification) Equals(notification Notification) bool { + notif, ok := notification.(BaseNotification) + if !ok { + return ok + } + if n.Id != notif.Id { + return false + } + if n.IsRead != notif.IsRead { + return false + } + if n.Type != notif.Type { + return false + } + return true +} diff --git a/internals/notifier/notification/notification_export.go b/internals/notifier/notification/notification_export.go index a451fa54..66c3ed5b 100644 --- a/internals/notifier/notification/notification_export.go +++ b/internals/notifier/notification/notification_export.go @@ -3,6 +3,7 @@ package notification import ( "encoding/json" "github.com/myrteametrics/myrtea-engine-api/v5/internals/export" + "reflect" ) type ExportNotification struct { @@ -40,5 +41,23 @@ func (e ExportNotification) NewInstance(id int64, data []byte, isRead bool) (Not notification.Id = id notification.IsRead = isRead notification.Notification = notification - return ¬ification, nil + return notification, nil +} + +// Equals returns true if the two notifications are equals +func (e ExportNotification) Equals(notification Notification) bool { + notif, ok := notification.(ExportNotification) + if !ok { + return ok + } + if !notif.BaseNotification.Equals(e.BaseNotification) { + return false + } + if !reflect.DeepEqual(notif.Export, e.Export) { + return false + } + if notif.Status != e.Status { + return false + } + return true } diff --git a/internals/notifier/notification/notification_mock.go b/internals/notifier/notification/notification_mock.go index 9e79ff93..3fa7dd25 100644 --- a/internals/notifier/notification/notification_mock.go +++ b/internals/notifier/notification/notification_mock.go @@ -8,8 +8,6 @@ import ( // MockNotification is an implementation of a notification main type type MockNotification struct { BaseNotification - ID int64 `json:"id"` - Type string `json:"type"` CreationDate time.Time `json:"creationDate"` Groups []int64 `json:"groups"` Level string `json:"level"` @@ -20,18 +18,21 @@ type MockNotification struct { } // NewMockNotification renders a new MockNotification instance -func NewMockNotification(level string, title string, subTitle string, description string, creationDate time.Time, +func NewMockNotification(id int64, level string, title string, subTitle string, description string, creationDate time.Time, groups []int64, context map[string]interface{}) *MockNotification { return &MockNotification{ - Type: "mock", + BaseNotification: BaseNotification{ + Id: id, + Type: "MockNotification", + }, CreationDate: creationDate, - // Groups: groups, - Level: level, - Title: title, - SubTitle: subTitle, - Description: description, - Context: context, + Groups: groups, + Level: level, + Title: title, + SubTitle: subTitle, + Description: description, + Context: context, } } @@ -43,3 +44,63 @@ func (n MockNotification) ToBytes() ([]byte, error) { } return b, nil } + +// NewInstance returns a new instance of a MockNotification +func (n MockNotification) NewInstance(id int64, data []byte, isRead bool) (Notification, error) { + var notification MockNotification + err := json.Unmarshal(data, ¬ification) + if err != nil { + return nil, err + } + notification.Id = id + notification.IsRead = isRead + notification.Notification = notification + return notification, nil +} + +// Equals returns true if the two notifications are equals +func (n MockNotification) Equals(notification Notification) bool { + notif, ok := notification.(MockNotification) + if !ok { + return ok + } + if !notif.BaseNotification.Equals(n.BaseNotification) { + return false + } + if notif.CreationDate != n.CreationDate { + return false + } + if notif.Level != n.Level { + return false + } + if notif.Title != n.Title { + return false + } + if notif.SubTitle != n.SubTitle { + return false + } + if notif.Description != n.Description { + return false + } + if notif.Context != nil && n.Context != nil { + if len(notif.Context) != len(n.Context) { + return false + } + for k, v := range notif.Context { + if n.Context[k] != v { + return false + } + } + } else if notif.Context != nil || n.Context != nil { + return false + } + if len(notif.Groups) != len(n.Groups) { + return false + } + for i, v := range notif.Groups { + if n.Groups[i] != v { + return false + } + } + return true +} diff --git a/internals/notifier/notification/notification_test.go b/internals/notifier/notification/notification_test.go index e2ae90be..78d6640a 100644 --- a/internals/notifier/notification/notification_test.go +++ b/internals/notifier/notification/notification_test.go @@ -4,8 +4,8 @@ import ( "github.com/google/uuid" "github.com/myrteametrics/myrtea-engine-api/v5/internals/export" "github.com/myrteametrics/myrtea-sdk/v4/expression" - "reflect" "testing" + "time" ) func TestBaseNotificationToBytes(t *testing.T) { @@ -26,30 +26,40 @@ func TestBaseNotificationToBytes(t *testing.T) { } func TestBaseNotificationNewInstance(t *testing.T) { - data := []byte(`{"Notification":null,"Id":1,"Type":"Test","IsRead":true}`) + s := BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + } + se, e := s.ToBytes() + if e == nil { + t.Log(string(se)) + } + + data := []byte(`{"id":1,"type":"Test","isRead":true}`) notification, err := BaseNotification{}.NewInstance(1, data, true) if err != nil { t.Errorf("Unexpected error: %v", err) } - expected := &BaseNotification{ + expected := BaseNotification{ Id: 1, Type: "Test", IsRead: true, } - expression.AssertEqual(t, reflect.DeepEqual(notification, expected), true) + expression.AssertEqual(t, expected.Equals(notification), true) } func TestBaseNotificationNewInstanceWithInvalidData(t *testing.T) { - data := []byte(`{"Notification":null,"Id":1,"Type":"Test","IsRead":"invalid"}`) + data := []byte(`{"id":1,"type":"Test","isRead":"invalid"}`) _, err := BaseNotification{}.NewInstance(1, data, true) if err == nil { t.Errorf("Expected error, got nil") } } -func TextExportNotification(t *testing.T) { +func TestExportNotification(t *testing.T) { // init handler ReplaceHandlerGlobals(NewHandler()) @@ -90,3 +100,213 @@ func TextExportNotification(t *testing.T) { expression.AssertEqual(t, string(bytes), string(bt)) } + +func TestBaseNotification_Equals(t *testing.T) { + notif := BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + } + + expression.AssertEqual(t, notif.Equals(BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + }), true) + + expression.AssertEqual(t, notif.Equals(BaseNotification{ + Id: 2, + Type: "Test", + IsRead: true, + }), false) + + expression.AssertEqual(t, notif.Equals(BaseNotification{ + Id: 1, + Type: "Test2", + IsRead: true, + }), false) + + expression.AssertEqual(t, notif.Equals(BaseNotification{ + Id: 1, + Type: "Test", + IsRead: false, + }), false) +} + +func TestMockNotification_Equals(t *testing.T) { + baseNotification := BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + } + now := time.Now() + notif := MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + } + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), true) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: BaseNotification{ + Id: 2, + Type: "Test", + IsRead: true, + }, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: time.Now().AddDate(1, 0, 0), + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "infos", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "titles", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitles", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "descriptions", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"tests": "test"}, + Groups: []int64{1, 2}, + }), false) + + expression.AssertEqual(t, notif.Equals(MockNotification{ + BaseNotification: baseNotification, + CreationDate: now, + Level: "info", + Title: "title", + SubTitle: "subTitle", + Description: "description", + Context: map[string]interface{}{"test": "test"}, + Groups: []int64{1, 2, 3}, + }), false) + +} + +func TestExportNotification_Equals(t *testing.T) { + id := uuid.New().String() + exportNotification := ExportNotification{ + BaseNotification: BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + }, + Export: export.WrapperItem{ + Id: id, + }, + Status: 1, + } + + expression.AssertEqual(t, exportNotification.Equals(ExportNotification{ + BaseNotification: BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + }, + Status: 1, + Export: export.WrapperItem{Id: id}, + }), true) + + expression.AssertEqual(t, exportNotification.Equals(ExportNotification{ + BaseNotification: BaseNotification{ + Id: 2, + Type: "Test", + IsRead: true, + }, + Status: 1, + Export: export.WrapperItem{Id: id}, + }), false) + + expression.AssertEqual(t, exportNotification.Equals(ExportNotification{ + BaseNotification: BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + }, + Status: 2, + Export: export.WrapperItem{Id: id}, + }), false) + + expression.AssertEqual(t, exportNotification.Equals(ExportNotification{ + BaseNotification: BaseNotification{ + Id: 1, + Type: "Test", + IsRead: true, + }, + Status: 1, + Export: export.WrapperItem{Id: uuid.New().String()}, + }), false) + +} diff --git a/internals/notifier/notifier.go b/internals/notifier/notifier.go index 19162063..00ca6f54 100644 --- a/internals/notifier/notifier.go +++ b/internals/notifier/notifier.go @@ -1,6 +1,7 @@ package notifier import ( + "github.com/myrteametrics/myrtea-engine-api/v5/internals/security/users" "sync" "time" @@ -126,10 +127,10 @@ func (notifier *Notifier) Broadcast(notif notification.Notification) { } // SendToUsers send a notification to users corresponding the input ids -func (notifier *Notifier) SendToUsers(notif notification.Notification, users []uuid.UUID) { +func (notifier *Notifier) SendToUsers(notif notification.Notification, users []users.UserWithPermissions) { if users != nil && len(users) > 0 { - for _, userID := range users { - clients := notifier.findClientsByUserID(userID) + for _, user := range users { + clients := notifier.findClientsByUserLogin(user.Login) for _, client := range clients { notifier.sendToClient(notif, client) } @@ -144,10 +145,10 @@ func (notifier *Notifier) Send(message []byte, client Client) { } } -func (notifier *Notifier) findClientsByUserID(id uuid.UUID) []Client { +func (notifier *Notifier) findClientsByUserLogin(login string) []Client { clients := make([]Client, 0) for _, client := range notifier.clientManager.GetClients() { - if client.GetUser() != nil && client.GetUser().ID == id { + if client.GetUser() != nil && client.GetUser().Login == login { clients = append(clients, client) } } diff --git a/internals/router/oidc/oidc_middleware.go b/internals/router/oidc/oidc_middleware.go index d54b193b..3fbc5627 100644 --- a/internals/router/oidc/oidc_middleware.go +++ b/internals/router/oidc/oidc_middleware.go @@ -3,7 +3,6 @@ package oidcAuth import ( "context" "errors" - "fmt" "net/http" "strings" "time" @@ -129,7 +128,7 @@ func ContextMiddleware(next http.Handler) http.Handler { loggerR := r.Context().Value(models.ContextKeyLoggerR) if loggerR != nil { - gorillacontext.Set(loggerR.(*http.Request), models.UserLogin, fmt.Sprintf("%s(%d)", up.User.Login, up.User.ID)) + gorillacontext.Set(loggerR.(*http.Request), models.UserLogin, up.User.Login) } ctx := context.WithValue(r.Context(), models.ContextKeyUser, up) diff --git a/internals/router/saml_middleware.go b/internals/router/saml_middleware.go index 5b02a707..ec94d776 100644 --- a/internals/router/saml_middleware.go +++ b/internals/router/saml_middleware.go @@ -6,7 +6,6 @@ import ( "crypto/tls" "crypto/x509" "errors" - "fmt" "net/http" "net/url" @@ -217,7 +216,7 @@ func (m *SamlSPMiddleware) ContextMiddleware(next http.Handler) http.Handler { loggerR := r.Context().Value(models.ContextKeyLoggerR) if loggerR != nil { - gorillacontext.Set(loggerR.(*http.Request), models.UserLogin, fmt.Sprintf("%s(%d)", up.User.Login, up.User.ID)) + gorillacontext.Set(loggerR.(*http.Request), models.UserLogin, up.User.Login) } ctx := context.WithValue(r.Context(), models.ContextKeyUser, up) diff --git a/internals/security/users/user.go b/internals/security/users/user.go index 2793ee61..542dca8b 100644 --- a/internals/security/users/user.go +++ b/internals/security/users/user.go @@ -13,7 +13,7 @@ import ( // User is used as the main user struct type User struct { ID uuid.UUID `json:"id"` - Login string `json:"login"` + Login string `json:"login"` // is the unique identifier of the user, through the different connection modes Created time.Time `json:"created"` LastName string `json:"lastName"` FirstName string `json:"firstName"`