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.

335 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. var joinedRoom string
  165. var joinedRoomBody []byte
  166. mxTripper.Handle("POST", "/_matrix/client/r0/join/*", func(req *http.Request) (*http.Response, error) {
  167. parts := strings.Split(req.URL.String(), "/")
  168. joinedRoom = parts[len(parts)-1]
  169. joinedRoomBody, _ = ioutil.ReadAll(req.Body)
  170. return newResponse(200, `{}`), nil
  171. })
  172. var decryptedMsg string
  173. mxTripper.Handle("PUT", "/_matrix/client/r0/rooms/!greatdekutree:hyrule/send/m.room.encrypted/*", func(req *http.Request) (*http.Response, error) {
  174. encryptedMsg, _ := ioutil.ReadAll(req.Body)
  175. var encryptedContent mevt.EncryptedEventContent
  176. encryptedContent.UnmarshalJSON(encryptedMsg)
  177. decryptedMsgBytes, _, err := igsMock.Internal.Decrypt(encryptedContent.MegolmCiphertext)
  178. if err != nil {
  179. t.Errorf("Error decrypting message sent by bot: %v", err)
  180. }
  181. decryptedMsg = string(decryptedMsgBytes)
  182. return newResponse(200, `{}`), nil
  183. })
  184. // configure the client
  185. clientConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureClient", bytes.NewBufferString(`
  186. {
  187. "UserID":"@link:hyrule",
  188. "DeviceID":"mastersword",
  189. "HomeserverURL":"http://hyrule.loz",
  190. "AccessToken":"dangeroustogoalone",
  191. "Sync":true,
  192. "AutoJoinRooms":true
  193. }`))
  194. mux.ServeHTTP(mockWriter, clientConfigReq)
  195. // configure the echo service
  196. serviceConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureService", bytes.NewBufferString(`
  197. {
  198. "Type": "echo",
  199. "Id": "test_echo_service",
  200. "UserID": "@link:hyrule",
  201. "Config": {}
  202. }`))
  203. mux.ServeHTTP(mockWriter, serviceConfigReq)
  204. // send neb an invite to a room
  205. reqChan <- `{
  206. "next_batch":"11_22_33_44",
  207. "rooms": {
  208. "invite": {
  209. "!greatdekutree:hyrule": {"invite_state": {"events": [{
  210. "type": "m.room.member",
  211. "sender": "@navi:hyrule",
  212. "content": {"membership": "invite"},
  213. "state_key": "@link:hyrule",
  214. "origin_server_ts": 10000,
  215. "unsigned": {"age": 100},
  216. "event_id": "evt123"
  217. }]}}}
  218. }
  219. }`
  220. // wait for it to be processed
  221. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  222. expectedRoom := "%21greatdekutree:hyrule"
  223. if joinedRoom != expectedRoom {
  224. t.Errorf("Expected join for room %v, got %v", expectedRoom, joinedRoom)
  225. }
  226. if expectedBody := `{"inviter":"@navi:hyrule"}`; string(joinedRoomBody) != expectedBody {
  227. t.Errorf("Expected join message body to be %v, got %v", expectedBody, string(joinedRoomBody))
  228. }
  229. // send neb the room state: encrypted with one member
  230. reqChan <- `{
  231. "next_batch":"11_22_33_44",
  232. "rooms": {
  233. "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{
  234. "type": "m.room.encryption",
  235. "state_key": "",
  236. "content": {
  237. "algorithm": "m.megolm.v1.aes-sha2"
  238. }
  239. }, {
  240. "type": "m.room.member",
  241. "sender": "@navi:hyrule",
  242. "content": {"membership": "join", "displayname": "Navi"},
  243. "state_key": "@navi:hyrule",
  244. "origin_server_ts": 100,
  245. "event_id": "evt124"
  246. }]}}}
  247. }
  248. }`
  249. // wait for it to be processed
  250. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  251. // DB is initialized, store the megolm sessions from before for the bot to be able to decrypt and encrypt
  252. sqlDB, dialect := database.GetServiceDB().(*database.ServiceDB).GetSQLDb()
  253. cryptoStore := crypto.NewSQLCryptoStore(sqlDB, dialect, "@link:hyrule-mastersword", "mastersword", []byte("masterswordpickle"), clients.CryptoMachineLogger{})
  254. if err := cryptoStore.AddOutboundGroupSession(ogsBot); err != nil {
  255. t.Errorf("Error storing bot OGS: %v", err)
  256. }
  257. if err := cryptoStore.PutGroupSession("!greatdekutree:hyrule", identityKeyMock, igsBot.ID(), igsBot); err != nil {
  258. t.Errorf("Error storing bot IGS: %v", err)
  259. }
  260. cryptoStore.PutDevices("@navi:hyrule", map[id.DeviceID]*crypto.DeviceIdentity{
  261. "NAVI": {
  262. UserID: "@navi:hyrule",
  263. DeviceID: "NAVI",
  264. IdentityKey: identityKeyMock,
  265. SigningKey: signingKeyMock,
  266. },
  267. })
  268. plaintext := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"body":"!echo save zelda","msgtype":"m.text"}}`
  269. ciphertext, err := ogsMock.Encrypt([]byte(plaintext))
  270. if err != nil {
  271. t.Errorf("Error encrypting bytes: %v", err)
  272. }
  273. // send neb an !echo message, encrypted with our mock OGS which it has an IGS for
  274. reqChan <- fmt.Sprintf(`{
  275. "next_batch":"11_22_33_44",
  276. "rooms": {
  277. "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{
  278. "type": "m.room.encrypted",
  279. "sender": "@navi:hyrule",
  280. "content": {
  281. "algorithm":"m.megolm.v1.aes-sha2",
  282. "sender_key":"%s",
  283. "ciphertext":"%s",
  284. "session_id":"%s",
  285. "device_id": "NAVI"
  286. },
  287. "origin_server_ts": 10000,
  288. "unsigned": {"age": 100},
  289. "event_id": "evt125"
  290. }]}}}
  291. }
  292. }`, identityKeyMock, string(ciphertext), ogsMock.ID())
  293. // wait for it to be processed
  294. reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}`
  295. expectedDecryptedMsg := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"msgtype":"m.notice","body":"save zelda"}}`
  296. if decryptedMsg != expectedDecryptedMsg {
  297. t.Errorf("Expected decrypted message to be `%v`, got `%v`", expectedDecryptedMsg, decryptedMsg)
  298. }
  299. }