From 32e3c2f0b8cbaef93cac5934298816008aee626f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Espino=20Garc=C3=ADa?= Date: Wed, 2 Mar 2022 12:15:58 +0100 Subject: [PATCH] [Gekidou] Add push notification check to ping (#19610) * Add push notification check to ping * Add test type and missing details * Address feedback * Fix function name * Address feedback Co-authored-by: Mattermod --- api4/system.go | 4 ++ api4/system_test.go | 11 ++++ app/app_iface.go | 1 + app/notification_push.go | 79 +++++++++++++++++++++------- app/notification_push_test.go | 47 +++++++++++++---- app/opentracing/opentracing_layer.go | 17 ++++++ model/push_notification.go | 1 + 7 files changed, 131 insertions(+), 29 deletions(-) diff --git a/api4/system.go b/api4/system.go index 0e6e7e50cd..bc49164e7f 100644 --- a/api4/system.go +++ b/api4/system.go @@ -183,6 +183,10 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set(filestoreStatusKey, s[filestoreStatusKey]) } + if deviceID := r.FormValue("device_id"); deviceID != "" { + s["CanReceiveNotifications"] = c.App.SendTestPushNotification(deviceID) + } + if s[model.STATUS] != model.StatusOk { w.WriteHeader(http.StatusInternalServerError) } diff --git a/api4/system_test.go b/api4/system_test.go index 828b7331a2..9bf3f9e73b 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -96,6 +96,17 @@ func TestGetPing(t *testing.T) { respString = string(respBytes) require.Contains(t, respString, "testvalue") }, "ping feature flag test") + + th.TestForAllClients(t, func(t *testing.T, client *model.Client4) { + th.App.ReloadConfig() + resp, err := client.DoAPIGet("/system/ping?device_id=platform:id", "") + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + var respMap map[string]string + err = json.NewDecoder(resp.Body).Decode(&respMap) + require.NoError(t, err) + assert.Equal(t, "unknown", respMap["CanReceiveNotifications"]) // Unrecognized platform + }, "ping and test push notification") } func TestGetAudits(t *testing.T) { diff --git a/app/app_iface.go b/app/app_iface.go index c2bf38bd52..ee1a01a57d 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -977,6 +977,7 @@ type AppIface interface { SendNotifications(post *model.Post, team *model.Team, channel *model.Channel, sender *model.User, parentPostList *model.PostList, setOnline bool) ([]string, error) SendPasswordReset(email string, siteURL string) (bool, *model.AppError) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.AppError + SendTestPushNotification(deviceID string) string ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string) SessionHasPermissionTo(session model.Session, permission *model.Permission) bool SessionHasPermissionToAny(session model.Session, permissions []*model.Permission) bool diff --git a/app/notification_push.go b/app/notification_push.go index 56f1ebcb20..d7359b920d 100644 --- a/app/notification_push.go +++ b/app/notification_push.go @@ -379,6 +379,32 @@ func (s *Server) StopPushNotificationsHubWorkers() { s.PushNotificationsHub.stop() } +func (a *App) rawSendToPushProxy(msg *model.PushNotification) (model.PushResponse, error) { + msgJSON, jsonErr := json.Marshal(msg) + if jsonErr != nil { + return nil, errors.Wrap(jsonErr, "failed to encode to JSON") + } + + url := strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/") + model.APIURLSuffixV1 + "/send_push" + request, err := http.NewRequest("POST", url, bytes.NewReader(msgJSON)) + if err != nil { + return nil, err + } + + resp, err := a.Srv().pushNotificationClient.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var pushResponse model.PushResponse + if jsonErr := json.NewDecoder(resp.Body).Decode(&pushResponse); jsonErr != nil { + return nil, errors.Wrap(jsonErr, "failed to decode from JSON") + } + + return pushResponse, nil +} + func (a *App) sendToPushProxy(msg *model.PushNotification, session *model.Session) error { msg.ServerId = a.TelemetryId() @@ -390,28 +416,11 @@ func (a *App) sendToPushProxy(msg *model.PushNotification, session *model.Sessio mlog.String("status", model.PushSendPrepare), ) - msgJSON, jsonErr := json.Marshal(msg) - if jsonErr != nil { - return errors.Wrap(jsonErr, "failed to encode to JSON") - } - - url := strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/") + model.APIURLSuffixV1 + "/send_push" - request, err := http.NewRequest("POST", url, bytes.NewReader(msgJSON)) + pushResponse, err := a.rawSendToPushProxy(msg) if err != nil { return err } - resp, err := a.Srv().pushNotificationClient.Do(request) - if err != nil { - return err - } - defer resp.Body.Close() - - var pushResponse model.PushResponse - if jsonErr := json.NewDecoder(resp.Body).Decode(&pushResponse); jsonErr != nil { - return errors.Wrap(jsonErr, "failed to decode from JSON") - } - switch pushResponse[model.PushStatus] { case model.PushStatusRemove: a.AttachDeviceId(session.Id, "", session.ExpiresAt) @@ -568,6 +577,40 @@ func (a *App) BuildPushNotificationMessage(contentsConfig string, post *model.Po return msg, nil } +func (a *App) SendTestPushNotification(deviceID string) string { + msg := &model.PushNotification{ + Version: "2", + Type: model.PushTypeTest, + ServerId: a.TelemetryId(), + Badge: -1, + } + msg.SetDeviceIdAndPlatform(deviceID) + + pushResponse, err := a.rawSendToPushProxy(msg) + if err != nil { + a.NotificationsLog().Error("Notification error", + mlog.String("type", msg.Type), + mlog.String("deviceId", msg.DeviceId), + mlog.String("status", err.Error()), + ) + return "unknown" + } + + switch pushResponse[model.PushStatus] { + case model.PushStatusRemove: + return "false" + case model.PushStatusFail: + a.NotificationsLog().Error("Notification error", + mlog.String("type", msg.Type), + mlog.String("deviceId", msg.DeviceId), + mlog.String("status", pushResponse[model.PushStatusErrorMsg]), + ) + return "unknown" + } + + return "true" +} + func (a *App) buildIdLoadedPushNotificationMessage(channel *model.Channel, post *model.Post, user *model.User) *model.PushNotification { userLocale := i18n.GetUserTranslations(user.Locale) msg := &model.PushNotification{ diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 93898d24cd..867e2ef136 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -1031,9 +1031,7 @@ func (h *testPushNotificationHandler) handleReq(w http.ResponseWriter, r *http.R // Don't do any checking if it's a benchmark if _, ok := h.t.(*testing.B); ok { - resp := model.NewOkPushResponse() - jsonData, _ := json.Marshal(&resp) - fmt.Fprintln(w, jsonData) + h.printResponse(w, model.NewOkPushResponse()) return } @@ -1042,9 +1040,7 @@ func (h *testPushNotificationHandler) handleReq(w http.ResponseWriter, r *http.R var err error if r.URL.Path == "/api/v1/send_push" { if err = json.NewDecoder(r.Body).Decode(¬ification); err != nil { - resp := model.NewErrorPushResponse("fail") - jsonData, _ := json.Marshal(&resp) - fmt.Fprintln(w, jsonData) + h.printResponse(w, model.NewErrorPushResponse("fail")) return } // We verify that messages are being sent in order per-device. @@ -1057,9 +1053,7 @@ func (h *testPushNotificationHandler) handleReq(w http.ResponseWriter, r *http.R } } else { if err = json.NewDecoder(r.Body).Decode(¬ificationAck); err != nil { - resp := model.NewErrorPushResponse("fail") - jsonData, _ := json.Marshal(&resp) - fmt.Fprintln(w, jsonData) + h.printResponse(w, model.NewErrorPushResponse("fail")) return } } @@ -1086,11 +1080,15 @@ func (h *testPushNotificationHandler) handleReq(w http.ResponseWriter, r *http.R resp = model.NewRemovePushResponse() } } - jsonData, _ := json.Marshal(&resp) - fmt.Fprintln(w, jsonData) + h.printResponse(w, resp) } } +func (h *testPushNotificationHandler) printResponse(w http.ResponseWriter, resp model.PushResponse) { + jsonData, _ := json.Marshal(&resp) + fmt.Fprintln(w, string(jsonData)) +} + func (h *testPushNotificationHandler) numReqs() int { h.mut.RLock() defer h.mut.RUnlock() @@ -1239,6 +1237,33 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) { assert.Equal(t, model.PushTypeUpdateBadge, handler.notifications()[1].Type) } +func TestSendTestPushNotification(t *testing.T) { + th := Setup(t) + defer th.TearDown() + + handler := &testPushNotificationHandler{t: t} + pushServer := httptest.NewServer( + http.HandlerFunc(handler.handleReq), + ) + defer pushServer.Close() + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.EmailSettings.PushNotificationServer = pushServer.URL + }) + + // Per mock definition, first time will send remove, second time will send OK + result := th.App.SendTestPushNotification("platform:id") + assert.Equal(t, "false", result) + result = th.App.SendTestPushNotification("platform:id") + assert.Equal(t, "true", result) + + // Server side verification. + // We verify that 2 requests have been sent, and also check the message contents. + require.Equal(t, 2, handler.numReqs()) + assert.Equal(t, model.PushTypeTest, handler.notifications()[0].Type) + assert.Equal(t, model.PushTypeTest, handler.notifications()[1].Type) +} + func TestSendAckToPushProxy(t *testing.T) { th := SetupWithStoreMock(t) defer th.TearDown() diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 40a84c534e..d8e9d79ed4 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -14637,6 +14637,23 @@ func (a *OpenTracingAppLayer) SendPaymentFailedEmail(failedPayment *model.Failed return resultVar0 } +func (a *OpenTracingAppLayer) SendTestPushNotification(deviceID string) string { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendTestPushNotification") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.SendTestPushNotification(deviceID) + + return resultVar0 +} + func (a *OpenTracingAppLayer) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId string, destinationPluginId string) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ServeInterPluginRequest") diff --git a/model/push_notification.go b/model/push_notification.go index d46c24ecff..09a645ea38 100644 --- a/model/push_notification.go +++ b/model/push_notification.go @@ -17,6 +17,7 @@ const ( PushTypeClear = "clear" PushTypeUpdateBadge = "update_badge" PushTypeSession = "session" + PushTypeTest = "test" PushMessageV2 = "v2" PushSoundNone = "none"