You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
10 KiB

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/matrix-org/go-neb/clients"
  11. "github.com/matrix-org/go-neb/database"
  12. "maunium.net/go/mautrix/crypto"
  13. "maunium.net/go/mautrix/crypto/olm"
  14. mevt "maunium.net/go/mautrix/event"
  15. )
  16. func setupMockServer() (*http.ServeMux, *matrixTripper, *httptest.ResponseRecorder, chan string) {
  17. mux := http.NewServeMux()
  18. mxTripper := newMatrixTripper()
  19. setup(envVars{
  20. BaseURL: "http://go.neb",
  21. DatabaseType: "sqlite3",
  22. DatabaseURL: ":memory:",
  23. }, mux, &http.Client{
  24. Transport: mxTripper,
  25. })
  26. mxTripper.ClearHandlers()
  27. mockWriter := httptest.NewRecorder()
  28. reqChan := make(chan string)
  29. mxTripper.HandlePOSTFilter("@link:hyrule")
  30. mxTripper.Handle("GET", "/_matrix/client/r0/sync",
  31. func(req *http.Request) (*http.Response, error) {
  32. if _, ok := req.URL.Query()["since"]; !ok {
  33. return newResponse(200, `{"next_batch":"11_22_33_44", "rooms": {}}`), nil
  34. }
  35. reqBody := <-reqChan
  36. return newResponse(200, reqBody), nil
  37. },
  38. )
  39. return mux, mxTripper, mockWriter, reqChan
  40. }
  41. func TestConfigureClient(t *testing.T) {
  42. mux, _, mockWriter, _ := setupMockServer()
  43. mockReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureClient", bytes.NewBufferString(`
  44. {
  45. "UserID":"@link:hyrule",
  46. "HomeserverURL":"http://hyrule.loz",
  47. "AccessToken":"dangeroustogoalone",
  48. "Sync":true,
  49. "AutoJoinRooms":true
  50. }`))
  51. mux.ServeHTTP(mockWriter, mockReq)
  52. expectCode := 200
  53. if mockWriter.Code != expectCode {
  54. t.Errorf("TestConfigureClient wanted HTTP status %d, got %d", expectCode, mockWriter.Code)
  55. }
  56. }
  57. func TestRespondToEcho(t *testing.T) {
  58. mux, mxTripper, mockWriter, reqChan := setupMockServer()
  59. mxTripper.Handle("POST", "/_matrix/client/r0/keys/upload", func(req *http.Request) (*http.Response, error) {
  60. return newResponse(200, `{}`), nil
  61. })
  62. var joinedRoom string
  63. var joinedRoomBody []byte
  64. mxTripper.Handle("POST", "/_matrix/client/r0/join/*", func(req *http.Request) (*http.Response, error) {
  65. parts := strings.Split(req.URL.String(), "/")
  66. joinedRoom = parts[len(parts)-1]
  67. joinedRoomBody, _ = ioutil.ReadAll(req.Body)
  68. return newResponse(200, `{}`), nil
  69. })
  70. var roomMsgBody []byte
  71. mxTripper.Handle("PUT", "/_matrix/client/r0/rooms/!greatdekutree:hyrule/send/m.room.message/*", func(req *http.Request) (*http.Response, error) {
  72. roomMsgBody, _ = ioutil.ReadAll(req.Body)
  73. return newResponse(200, `{}`), nil
  74. })
  75. // configure the client
  76. clientConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureClient", bytes.NewBufferString(`
  77. {
  78. "UserID":"@link:hyrule",
  79. "HomeserverURL":"http://hyrule.loz",
  80. "AccessToken":"dangeroustogoalone",
  81. "Sync":true,
  82. "AutoJoinRooms":true
  83. }`))
  84. mux.ServeHTTP(mockWriter, clientConfigReq)
  85. // configure the echo service
  86. serviceConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureService", bytes.NewBufferString(`
  87. {
  88. "Type": "echo",
  89. "Id": "test_echo_service",
  90. "UserID": "@link:hyrule",
  91. "Config": {}
  92. }`))
  93. mux.ServeHTTP(mockWriter, serviceConfigReq)
  94. // send neb an invite to a room
  95. reqChan <- `{
  96. "next_batch":"11_22_33_44",
  97. "rooms": {
  98. "invite": {
  99. "!greatdekutree:hyrule": {"invite_state": {"events": [{
  100. "type": "m.room.member",
  101. "sender": "@navi:hyrule",
  102. "content": {"membership": "invite"},
  103. "state_key": "@link:hyrule",
  104. "origin_server_ts": 10000,
  105. "unsigned": {"age": 100},
  106. "event_id": "evt123"
  107. }]}}}
  108. }
  109. }`
  110. // wait for it to be processed
  111. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  112. expectedRoom := "%21greatdekutree:hyrule"
  113. if joinedRoom != expectedRoom {
  114. t.Errorf("Expected join for room %v, got %v", expectedRoom, joinedRoom)
  115. }
  116. if expectedBody := `{"inviter":"@navi:hyrule"}`; string(joinedRoomBody) != expectedBody {
  117. t.Errorf("Expected join message body to be %v, got %v", expectedBody, string(joinedRoomBody))
  118. }
  119. // send neb an !echo message
  120. reqChan <- `{
  121. "next_batch":"11_22_33_44",
  122. "rooms": {
  123. "join": {
  124. "!greatdekutree:hyrule": {"timeline": {"events": [{
  125. "type": "m.room.message",
  126. "sender": "@navi:hyrule",
  127. "content": {"body": "!echo save zelda", "msgtype": "m.text"},
  128. "origin_server_ts": 10000,
  129. "unsigned": {"age": 100},
  130. "event_id": "evt124"
  131. }]}}}
  132. }
  133. }`
  134. // wait for it to be processed
  135. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  136. if expectedEchoResp := `{"msgtype":"m.notice","body":"save zelda"}`; string(roomMsgBody) != expectedEchoResp {
  137. t.Errorf("Expected echo response to be `%v`, got `%v`", expectedEchoResp, string(roomMsgBody))
  138. }
  139. }
  140. func TestEncryptedRespondToEcho(t *testing.T) {
  141. mux, mxTripper, mockWriter, reqChan := setupMockServer()
  142. // create the two accounts, inbound and outbound sessions, both the bot and mock ones
  143. accountMock := olm.NewAccount()
  144. accountBot := olm.NewAccount()
  145. signingKeyMock, identityKeyMock := accountMock.IdentityKeys()
  146. signingKeyBot, identityKeyBot := accountBot.IdentityKeys()
  147. ogsBot := crypto.NewOutboundGroupSession("!greatdekutree:hyrule")
  148. ogsBot.Shared = true
  149. igsMock, err := crypto.NewInboundGroupSession(identityKeyBot, signingKeyBot, "!greatdekutree:hyrule", ogsBot.Internal.Key())
  150. if err != nil {
  151. t.Errorf("Error creating mock IGS: %v", err)
  152. }
  153. ogsMock := crypto.NewOutboundGroupSession("!greatdekutree:hyrule")
  154. ogsMock.Shared = true
  155. igsBot, err := crypto.NewInboundGroupSession(identityKeyMock, signingKeyMock, "!greatdekutree:hyrule", ogsMock.Internal.Key())
  156. if err != nil {
  157. t.Errorf("Error creating bot IGS: %v", err)
  158. }
  159. mxTripper.Handle("POST", "/_matrix/client/r0/keys/upload", func(req *http.Request) (*http.Response, error) {
  160. return newResponse(200, `{}`), nil
  161. })
  162. var joinedRoom string
  163. var joinedRoomBody []byte
  164. mxTripper.Handle("POST", "/_matrix/client/r0/join/*", func(req *http.Request) (*http.Response, error) {
  165. parts := strings.Split(req.URL.String(), "/")
  166. joinedRoom = parts[len(parts)-1]
  167. joinedRoomBody, _ = ioutil.ReadAll(req.Body)
  168. return newResponse(200, `{}`), nil
  169. })
  170. var decryptedMsg string
  171. mxTripper.Handle("PUT", "/_matrix/client/r0/rooms/!greatdekutree:hyrule/send/m.room.encrypted/*", func(req *http.Request) (*http.Response, error) {
  172. encryptedMsg, _ := ioutil.ReadAll(req.Body)
  173. var encryptedContent mevt.EncryptedEventContent
  174. encryptedContent.UnmarshalJSON(encryptedMsg)
  175. decryptedMsgBytes, _, err := igsMock.Internal.Decrypt(encryptedContent.MegolmCiphertext)
  176. if err != nil {
  177. t.Errorf("Error decrypting message sent by bot: %v", err)
  178. }
  179. decryptedMsg = string(decryptedMsgBytes)
  180. return newResponse(200, `{}`), nil
  181. })
  182. // configure the client
  183. clientConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureClient", bytes.NewBufferString(`
  184. {
  185. "UserID":"@link:hyrule",
  186. "DeviceID":"mastersword",
  187. "HomeserverURL":"http://hyrule.loz",
  188. "AccessToken":"dangeroustogoalone",
  189. "Sync":true,
  190. "AutoJoinRooms":true
  191. }`))
  192. mux.ServeHTTP(mockWriter, clientConfigReq)
  193. // configure the echo service
  194. serviceConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureService", bytes.NewBufferString(`
  195. {
  196. "Type": "echo",
  197. "Id": "test_echo_service",
  198. "UserID": "@link:hyrule",
  199. "Config": {}
  200. }`))
  201. mux.ServeHTTP(mockWriter, serviceConfigReq)
  202. // send neb an invite to a room
  203. reqChan <- `{
  204. "next_batch":"11_22_33_44",
  205. "rooms": {
  206. "invite": {
  207. "!greatdekutree:hyrule": {"invite_state": {"events": [{
  208. "type": "m.room.member",
  209. "sender": "@navi:hyrule",
  210. "content": {"membership": "invite"},
  211. "state_key": "@link:hyrule",
  212. "origin_server_ts": 10000,
  213. "unsigned": {"age": 100},
  214. "event_id": "evt123"
  215. }]}}}
  216. }
  217. }`
  218. // wait for it to be processed
  219. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  220. expectedRoom := "%21greatdekutree:hyrule"
  221. if joinedRoom != expectedRoom {
  222. t.Errorf("Expected join for room %v, got %v", expectedRoom, joinedRoom)
  223. }
  224. if expectedBody := `{"inviter":"@navi:hyrule"}`; string(joinedRoomBody) != expectedBody {
  225. t.Errorf("Expected join message body to be %v, got %v", expectedBody, string(joinedRoomBody))
  226. }
  227. // send neb the room state: encrypted with one member
  228. reqChan <- `{
  229. "next_batch":"11_22_33_44",
  230. "rooms": {
  231. "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{
  232. "type": "m.room.encryption",
  233. "state_key": "",
  234. "content": {
  235. "algorithm": "m.megolm.v1.aes-sha2"
  236. }
  237. }, {
  238. "type": "m.room.member",
  239. "sender": "@navi:hyrule",
  240. "content": {"membership": "join", "displayname": "Navi"},
  241. "state_key": "@navi:hyrule",
  242. "origin_server_ts": 100,
  243. "event_id": "evt124"
  244. }]}}}
  245. }
  246. }`
  247. // wait for it to be processed
  248. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  249. // DB is initialized, store the megolm sessions from before for the bot to be able to decrypt and encrypt
  250. sqlDB, dialect := database.GetServiceDB().(*database.ServiceDB).GetSQLDb()
  251. cryptoStore := crypto.NewSQLCryptoStore(sqlDB, dialect, "mastersword", []byte("masterswordpickle"), clients.CryptoMachineLogger{})
  252. if err := cryptoStore.AddOutboundGroupSession(ogsBot); err != nil {
  253. t.Errorf("Error storing bot OGS: %v", err)
  254. }
  255. if err := cryptoStore.PutGroupSession("!greatdekutree:hyrule", identityKeyMock, igsBot.ID(), igsBot); err != nil {
  256. t.Errorf("Error storing bot IGS: %v", err)
  257. }
  258. plaintext := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"body":"!echo save zelda","msgtype":"m.text"}}`
  259. ciphertext, err := ogsMock.Encrypt([]byte(plaintext))
  260. if err != nil {
  261. t.Errorf("Error encrypting bytes: %v", err)
  262. }
  263. // send neb an !echo message, encrypted with our mock OGS which it has an IGS for
  264. reqChan <- fmt.Sprintf(`{
  265. "next_batch":"11_22_33_44",
  266. "rooms": {
  267. "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{
  268. "type": "m.room.encrypted",
  269. "sender": "@navi:hyrule",
  270. "content": {
  271. "algorithm":"m.megolm.v1.aes-sha2",
  272. "sender_key":"%s",
  273. "ciphertext":"%s",
  274. "session_id":"%s"
  275. },
  276. "origin_server_ts": 10000,
  277. "unsigned": {"age": 100},
  278. "event_id": "evt125"
  279. }]}}}
  280. }
  281. }`, identityKeyMock, string(ciphertext), ogsMock.ID())
  282. // wait for it to be processed
  283. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  284. expectedDecryptedMsg := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"msgtype":"m.notice","body":"save zelda"}}`
  285. if decryptedMsg != expectedDecryptedMsg {
  286. t.Errorf("Expected decrypted message to be `%v`, got `%v`", expectedDecryptedMsg, decryptedMsg)
  287. }
  288. }