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.

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