From b36259ec7d019e0c8b462c9d16ac872915eb60ee Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 09:25:13 +0100 Subject: [PATCH 1/7] Add tests for server notices --- tests/csapi/admin_test.go | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/csapi/admin_test.go b/tests/csapi/admin_test.go index 68675b7e..e6a0ecfb 100644 --- a/tests/csapi/admin_test.go +++ b/tests/csapi/admin_test.go @@ -1,9 +1,16 @@ package csapi_tests import ( + "fmt" + "net/http" + "net/url" "testing" "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/client" + "github.com/matrix-org/complement/internal/match" + "github.com/matrix-org/complement/internal/must" + "github.com/tidwall/gjson" ) // Check if this homeserver supports Synapse-style admin registration. @@ -13,3 +20,103 @@ func TestCanRegisterAdmin(t *testing.T) { defer deployment.Destroy(t) deployment.RegisterUser(t, "hs1", "admin", "adminpassword", true) } + +// Test if the implemented /_synapse/admin/v1/send_server_notice behaves as expected +func TestServerNotices(t *testing.T) { + deployment := Deploy(t, b.BlueprintAlice) + defer deployment.Destroy(t) + admin := deployment.RegisterUser(t, "hs1", "admin", "adminpassword", true) + alice := deployment.Client(t, "hs1", "@alice:hs1") + + reqBody := client.WithJSONBody(t, map[string]interface{}{ + "user_id": "@alice:hs1", + "content": map[string]interface{}{ + "msgtype": "m.text", + "body": "hello from server notices!", + }, + }) + t.Run("/send_server_notice is not allowed as normal user", func(t *testing.T) { + res := alice.DoFunc(t, "POST", []string{"_synapse", "admin", "v1", "send_server_notice"}) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: http.StatusForbidden, + JSON: []match.JSON{ + match.JSONKeyEqual("errcode", "M_FORBIDDEN"), + }, + }) + }) + t.Run("/send_server_notice as an admin is allowed", func(t *testing.T) { + sendServerNotice(t, admin, reqBody, nil) + }) + t.Run("Alice is invited to the server alert room", func(t *testing.T) { + sendServerNotice(t, admin, reqBody, nil) + syncUntilInvite(t, alice) + }) + t.Run("Alice cannot reject the invite", func(t *testing.T) { + sendServerNotice(t, admin, reqBody, nil) + roomID := syncUntilInvite(t, alice) + res := alice.DoFunc(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "leave"}) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: http.StatusForbidden, + JSON: []match.JSON{ + match.JSONKeyEqual("errcode", "M_FORBIDDEN"), + match.JSONKeyEqual("error", "You cannot reject this invite"), + }, + }) + }) + t.Run("Alice can join the alert room", func(t *testing.T) { + eventID := sendServerNotice(t, admin, reqBody, nil) + roomID := syncUntilInvite(t, alice) + alice.JoinRoom(t, roomID, []string{}) + queryParams := url.Values{} + queryParams.Set("dir", "b") + // check if we received the message + res := alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, client.WithQueries(queryParams)) + msgRes := &msgResult{} + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: http.StatusOK, + JSON: []match.JSON{ + findMessageId(eventID, msgRes), + }, + }) + if !msgRes.found { + t.Errorf("did not find expected message from server notices") + } + }) + t.Run("Sending a notice with a transactionID is idempotent", func(t *testing.T) { + txnID := "1" + eventID1 := sendServerNotice(t, admin, reqBody, &txnID) + eventID2 := sendServerNotice(t, admin, reqBody, &txnID) + if eventID1 != eventID2 { + t.Errorf("expected event IDs to be the same, but got '%s' and '%s'", eventID1, eventID2) + } + }) + +} + +func sendServerNotice(t *testing.T, admin *client.CSAPI, reqBody client.RequestOpt, txnID *string) (eventID string) { + var res *http.Response + if txnID != nil { + res = admin.MustDoFunc(t, "PUT", []string{"_synapse", "admin", "v1", "send_server_notice", *txnID}, reqBody) + } else { + res = admin.MustDoFunc(t, "POST", []string{"_synapse", "admin", "v1", "send_server_notice"}, reqBody) + } + body := must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: http.StatusOK, + JSON: []match.JSON{ + match.JSONKeyPresent("event_id"), + }, + }) + return gjson.GetBytes(body, "event_id").Str +} + +func syncUntilInvite(t *testing.T, alice *client.CSAPI) string { + var roomID string + alice.MustSyncUntil(t, client.SyncReq{}, func(userID string, res gjson.Result) error { + if res.Get("rooms.invite.*.invite_state.events.0.sender").Str == "@_server:hs1" { + roomID = res.Get("rooms.invite").Get("@keys.0").Str + return nil + } + return fmt.Errorf("invite not found") + }) + return roomID +} From 661a7908c87203f70f553fbf11f1db7e13e73800 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 09:28:50 +0100 Subject: [PATCH 2/7] goimports to make the linter happy --- tests/csapi/admin_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/csapi/admin_test.go b/tests/csapi/admin_test.go index e6a0ecfb..25d5a7df 100644 --- a/tests/csapi/admin_test.go +++ b/tests/csapi/admin_test.go @@ -6,11 +6,12 @@ import ( "net/url" "testing" + "github.com/tidwall/gjson" + "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/client" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" - "github.com/tidwall/gjson" ) // Check if this homeserver supports Synapse-style admin registration. From 8150b672fff70d7571be83b7114ea50cfd1db2e8 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 09:46:17 +0100 Subject: [PATCH 3/7] Enable server notices for synapse --- dockerfiles/synapse/homeserver.yaml | 6 ++++++ dockerfiles/synapse/workers-shared.yaml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dockerfiles/synapse/homeserver.yaml b/dockerfiles/synapse/homeserver.yaml index 9039a866..b0b3583a 100644 --- a/dockerfiles/synapse/homeserver.yaml +++ b/dockerfiles/synapse/homeserver.yaml @@ -112,3 +112,9 @@ experimental_features: msc2716_enabled: true # server-side support for partial state in /send_join msc3706_enabled: true + +server_notices: + system_mxid_localpart: _server + system_mxid_display_name: "SServer Alert" + system_mxid_avatar_url: "" + room_name: "Server Alert" \ No newline at end of file diff --git a/dockerfiles/synapse/workers-shared.yaml b/dockerfiles/synapse/workers-shared.yaml index b824557a..05ef6ceb 100644 --- a/dockerfiles/synapse/workers-shared.yaml +++ b/dockerfiles/synapse/workers-shared.yaml @@ -68,3 +68,9 @@ experimental_features: msc2716_enabled: true # Enable spaces support spaces_enabled: true + +server_notices: + system_mxid_localpart: _server + system_mxid_display_name: "SServer Alert" + system_mxid_avatar_url: "" + room_name: "Server Alert" \ No newline at end of file From 936fa2fa5a74b1ef2e22b7ef3d0d3e15bc27e7ff Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 10:12:55 +0100 Subject: [PATCH 4/7] Use correct errcode --- tests/csapi/admin_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/csapi/admin_test.go b/tests/csapi/admin_test.go index 25d5a7df..041d6322 100644 --- a/tests/csapi/admin_test.go +++ b/tests/csapi/admin_test.go @@ -47,6 +47,7 @@ func TestServerNotices(t *testing.T) { }) t.Run("/send_server_notice as an admin is allowed", func(t *testing.T) { sendServerNotice(t, admin, reqBody, nil) + }) t.Run("Alice is invited to the server alert room", func(t *testing.T) { sendServerNotice(t, admin, reqBody, nil) @@ -59,8 +60,7 @@ func TestServerNotices(t *testing.T) { must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: http.StatusForbidden, JSON: []match.JSON{ - match.JSONKeyEqual("errcode", "M_FORBIDDEN"), - match.JSONKeyEqual("error", "You cannot reject this invite"), + match.JSONKeyEqual("errcode", "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"), }, }) }) @@ -68,6 +68,8 @@ func TestServerNotices(t *testing.T) { eventID := sendServerNotice(t, admin, reqBody, nil) roomID := syncUntilInvite(t, alice) alice.JoinRoom(t, roomID, []string{}) + // cleanup room, so the next test is in a clean state + defer alice.LeaveRoom(t, roomID) queryParams := url.Values{} queryParams.Set("dir", "b") // check if we received the message @@ -83,6 +85,12 @@ func TestServerNotices(t *testing.T) { t.Errorf("did not find expected message from server notices") } }) + t.Run("Alice can leave the alert room, after joining it", func(t *testing.T) { + sendServerNotice(t, admin, reqBody, nil) + roomID := syncUntilInvite(t, alice) + alice.JoinRoom(t, roomID, []string{}) + alice.LeaveRoom(t, roomID) + }) t.Run("Sending a notice with a transactionID is idempotent", func(t *testing.T) { txnID := "1" eventID1 := sendServerNotice(t, admin, reqBody, &txnID) From a0fa8b481cc16111de3f6e2d3f45bb6c6045f1b6 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 10:22:08 +0100 Subject: [PATCH 5/7] Fix copy & paste error --- dockerfiles/synapse/homeserver.yaml | 2 +- dockerfiles/synapse/workers-shared.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfiles/synapse/homeserver.yaml b/dockerfiles/synapse/homeserver.yaml index b0b3583a..0316a3a1 100644 --- a/dockerfiles/synapse/homeserver.yaml +++ b/dockerfiles/synapse/homeserver.yaml @@ -115,6 +115,6 @@ experimental_features: server_notices: system_mxid_localpart: _server - system_mxid_display_name: "SServer Alert" + system_mxid_display_name: "Server Alert" system_mxid_avatar_url: "" room_name: "Server Alert" \ No newline at end of file diff --git a/dockerfiles/synapse/workers-shared.yaml b/dockerfiles/synapse/workers-shared.yaml index 05ef6ceb..9614f85c 100644 --- a/dockerfiles/synapse/workers-shared.yaml +++ b/dockerfiles/synapse/workers-shared.yaml @@ -71,6 +71,6 @@ experimental_features: server_notices: system_mxid_localpart: _server - system_mxid_display_name: "SServer Alert" + system_mxid_display_name: "Server Alert" system_mxid_avatar_url: "" room_name: "Server Alert" \ No newline at end of file From 95ded4f096defeabdbcb01c29abc1d45a0547d8e Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 14:13:25 +0100 Subject: [PATCH 6/7] Update tests & add comment --- tests/csapi/admin_test.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/csapi/admin_test.go b/tests/csapi/admin_test.go index 041d6322..2e9a7bf1 100644 --- a/tests/csapi/admin_test.go +++ b/tests/csapi/admin_test.go @@ -36,6 +36,10 @@ func TestServerNotices(t *testing.T) { "body": "hello from server notices!", }, }) + var ( + eventID string + roomID string + ) t.Run("/send_server_notice is not allowed as normal user", func(t *testing.T) { res := alice.DoFunc(t, "POST", []string{"_synapse", "admin", "v1", "send_server_notice"}) must.MatchResponse(t, res, match.HTTPResponse{ @@ -46,16 +50,12 @@ func TestServerNotices(t *testing.T) { }) }) t.Run("/send_server_notice as an admin is allowed", func(t *testing.T) { - sendServerNotice(t, admin, reqBody, nil) - + eventID = sendServerNotice(t, admin, reqBody, nil) }) t.Run("Alice is invited to the server alert room", func(t *testing.T) { - sendServerNotice(t, admin, reqBody, nil) - syncUntilInvite(t, alice) + roomID = syncUntilInvite(t, alice) }) t.Run("Alice cannot reject the invite", func(t *testing.T) { - sendServerNotice(t, admin, reqBody, nil) - roomID := syncUntilInvite(t, alice) res := alice.DoFunc(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "leave"}) must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: http.StatusForbidden, @@ -65,11 +65,7 @@ func TestServerNotices(t *testing.T) { }) }) t.Run("Alice can join the alert room", func(t *testing.T) { - eventID := sendServerNotice(t, admin, reqBody, nil) - roomID := syncUntilInvite(t, alice) alice.JoinRoom(t, roomID, []string{}) - // cleanup room, so the next test is in a clean state - defer alice.LeaveRoom(t, roomID) queryParams := url.Values{} queryParams.Set("dir", "b") // check if we received the message @@ -86,11 +82,15 @@ func TestServerNotices(t *testing.T) { } }) t.Run("Alice can leave the alert room, after joining it", func(t *testing.T) { - sendServerNotice(t, admin, reqBody, nil) - roomID := syncUntilInvite(t, alice) - alice.JoinRoom(t, roomID, []string{}) alice.LeaveRoom(t, roomID) }) + t.Run("After leaving the alert room, a new room is created", func(t *testing.T) { + sendServerNotice(t, admin, reqBody, nil) + newRoomID := syncUntilInvite(t, alice) + if roomID == newRoomID { + t.Errorf("expected a new room, but they are the same") + } + }) t.Run("Sending a notice with a transactionID is idempotent", func(t *testing.T) { txnID := "1" eventID1 := sendServerNotice(t, admin, reqBody, &txnID) @@ -118,6 +118,8 @@ func sendServerNotice(t *testing.T, admin *client.CSAPI, reqBody client.RequestO return gjson.GetBytes(body, "event_id").Str } +// syncUntilInvite checks if we got an invitation from the server notice sender, as the roomID is unknown. +// Returns the found roomID on success func syncUntilInvite(t *testing.T, alice *client.CSAPI) string { var roomID string alice.MustSyncUntil(t, client.SyncReq{}, func(userID string, res gjson.Result) error { From 3e8883c4d9c25ae94985b870a0210c3fb7563441 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 18 Feb 2022 15:12:52 +0100 Subject: [PATCH 7/7] Use existing room --- tests/csapi/admin_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/csapi/admin_test.go b/tests/csapi/admin_test.go index 2e9a7bf1..71a274ed 100644 --- a/tests/csapi/admin_test.go +++ b/tests/csapi/admin_test.go @@ -84,10 +84,10 @@ func TestServerNotices(t *testing.T) { t.Run("Alice can leave the alert room, after joining it", func(t *testing.T) { alice.LeaveRoom(t, roomID) }) - t.Run("After leaving the alert room, a new room is created", func(t *testing.T) { + t.Run("After leaving the alert room and on re-invitation, no new room is created", func(t *testing.T) { sendServerNotice(t, admin, reqBody, nil) newRoomID := syncUntilInvite(t, alice) - if roomID == newRoomID { + if roomID != newRoomID { t.Errorf("expected a new room, but they are the same") } })