/** * Standalone signaling server for the Nextcloud Spreed app. * Copyright (C) 2017 struktur AG * * @author Joachim Bauch * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package signaling import ( "bytes" "context" "crypto/hmac" "crypto/sha1" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "sync" "testing" "time" "github.com/dlintw/goconf" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/nats-io/nats.go" ) var ( turnApiKey = "TheApiKey" turnSecret = "TheTurnSecret" turnServersString = "turn:1.2.3.4:9991?transport=udp,turn:1.2.3.4:9991?transport=tcp" turnServers = strings.Split(turnServersString, ",") ) func CreateBackendServerForTest(t *testing.T) (*goconf.ConfigFile, *BackendServer, NatsClient, *Hub, *mux.Router, *httptest.Server, func()) { return CreateBackendServerForTestFromConfig(t, nil) } func CreateBackendServerForTestWithTurn(t *testing.T) (*goconf.ConfigFile, *BackendServer, NatsClient, *Hub, *mux.Router, *httptest.Server, func()) { config := goconf.NewConfigFile() config.AddOption("turn", "apikey", turnApiKey) config.AddOption("turn", "secret", turnSecret) config.AddOption("turn", "servers", turnServersString) return CreateBackendServerForTestFromConfig(t, config) } func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFile) (*goconf.ConfigFile, *BackendServer, NatsClient, *Hub, *mux.Router, *httptest.Server, func()) { r := mux.NewRouter() registerBackendHandler(t, r) server := httptest.NewServer(r) if config == nil { config = goconf.NewConfigFile() } u, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } config.AddOption("backend", "allowed", u.Host) if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } config.AddOption("backend", "secret", string(testBackendSecret)) config.AddOption("sessions", "hashkey", "12345678901234567890123456789012") config.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config.AddOption("clients", "internalsecret", string(testInternalSecret)) config.AddOption("geoip", "url", "none") nats, err := NewLoopbackNatsClient() if err != nil { t.Fatal(err) } hub, err := NewHub(config, nats, r, "no-version") if err != nil { t.Fatal(err) } b, err := NewBackendServer(config, hub, "no-version") if err != nil { t.Fatal(err) } if err := b.Start(r); err != nil { t.Fatal(err) } go hub.Run() shutdown := func() { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() WaitForHub(ctx, t, hub) (nats).(*LoopbackNatsClient).waitForSubscriptionsEmpty(ctx, t) nats.Close() server.Close() } return config, b, nats, hub, r, server, shutdown } func performBackendRequest(url string, body []byte) (*http.Response, error) { request, err := http.NewRequest("POST", url, bytes.NewReader(body)) if err != nil { return nil, err } request.Header.Set("Content-Type", "application/json") rnd := newRandomString(32) check := CalculateBackendChecksum(rnd, body, testBackendSecret) request.Header.Set("Spreed-Signaling-Random", rnd) request.Header.Set("Spreed-Signaling-Checksum", check) request.Header.Set("Spreed-Signaling-Backend", url) client := &http.Client{} return client.Do(request) } func expectRoomlistEvent(n NatsClient, ch chan *nats.Msg, subject string, msgType string) (*EventServerMessage, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() select { case message := <-ch: if message.Subject != subject { return nil, fmt.Errorf("Expected subject %s, got %s", subject, message.Subject) } var natsMsg NatsMessage if err := n.Decode(message, &natsMsg); err != nil { return nil, err } if natsMsg.Type != "message" || natsMsg.Message == nil { return nil, fmt.Errorf("Expected message type message, got %+v", natsMsg) } msg := natsMsg.Message if msg.Type != "event" || msg.Event == nil { return nil, fmt.Errorf("Expected message type event, got %+v", msg) } if msg.Event.Target != "roomlist" || msg.Event.Type != msgType { return nil, fmt.Errorf("Expected roomlist %s event, got %+v", msgType, msg.Event) } return msg.Event, nil case <-ctx.Done(): return nil, ctx.Err() } } func TestBackendServer_NoAuth(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() roomId := "the-room-id" data := []byte{'{', '}'} request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data)) if err != nil { t.Fatal(err) } request.Header.Set("Content-Type", "application/json") client := &http.Client{} res, err := client.Do(request) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != http.StatusForbidden { t.Errorf("Expected error response, got %s: %s", res.Status, string(body)) } } func TestBackendServer_InvalidAuth(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() roomId := "the-room-id" data := []byte{'{', '}'} request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data)) if err != nil { t.Fatal(err) } request.Header.Set("Content-Type", "application/json") request.Header.Set("Spreed-Signaling-Random", "hello") request.Header.Set("Spreed-Signaling-Checksum", "world") client := &http.Client{} res, err := client.Do(request) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != http.StatusForbidden { t.Errorf("Expected error response, got %s: %s", res.Status, string(body)) } } func TestBackendServer_OldCompatAuth(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() roomId := "the-room-id" userid := "the-user-id" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") msg := &BackendServerRoomRequest{ Type: "invite", Invite: &BackendRoomInviteRequest{ UserIds: []string{ userid, }, AllUserIds: []string{ userid, }, Properties: &roomProperties, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data)) if err != nil { t.Fatal(err) } request.Header.Set("Content-Type", "application/json") rnd := newRandomString(32) check := CalculateBackendChecksum(rnd, data, testBackendSecret) request.Header.Set("Spreed-Signaling-Random", rnd) request.Header.Set("Spreed-Signaling-Checksum", check) client := &http.Client{} res, err := client.Do(request) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != http.StatusOK { t.Errorf("Expected success, got %s: %s", res.Status, string(body)) } } func TestBackendServer_InvalidBody(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() roomId := "the-room-id" data := []byte{1, 2, 3, 4} // Invalid JSON res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != http.StatusBadRequest { t.Errorf("Expected error response, got %s: %s", res.Status, string(body)) } } func TestBackendServer_UnsupportedRequest(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() msg := &BackendServerRoomRequest{ Type: "lala", } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } roomId := "the-room-id" res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != http.StatusBadRequest { t.Errorf("Expected error response, got %s: %s", res.Status, string(body)) } } func TestBackendServer_RoomInvite(t *testing.T) { _, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() u, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } userid := "test-userid" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") backend := hub.backend.GetBackend(u) natsChan := make(chan *nats.Msg, 1) subject := GetSubjectForUserId(userid, backend) sub, err := n.Subscribe(subject, natsChan) if err != nil { t.Fatal(err) } defer func() { if err := sub.Unsubscribe(); err != nil { t.Error(err) } }() msg := &BackendServerRoomRequest{ Type: "invite", Invite: &BackendRoomInviteRequest{ UserIds: []string{ userid, }, AllUserIds: []string{ userid, }, Properties: &roomProperties, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } roomId := "the-room-id" res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } event, err := expectRoomlistEvent(n, natsChan, subject, "invite") if err != nil { t.Error(err) } else if event.Invite == nil { t.Errorf("Expected invite, got %+v", event) } else if event.Invite.RoomId != roomId { t.Errorf("Expected room %s, got %+v", roomId, event) } else if event.Invite.Properties == nil || !bytes.Equal(*event.Invite.Properties, roomProperties) { t.Errorf("Room properties don't match: expected %s, got %s", string(roomProperties), string(*event.Invite.Properties)) } } func TestBackendServer_RoomDisinvite(t *testing.T) { _, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() u, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } backend := hub.backend.GetBackend(u) client := NewTestClient(t, server, hub) defer client.CloseWithBye() if err := client.SendHello(testDefaultUserId); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() hello, err := client.RunUntilHello(ctx) if err != nil { t.Fatal(err) } // Join room by id. roomId := "test-room" if room, err := client.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } // Ignore "join" events. if err := client.DrainMessages(ctx); err != nil { t.Error(err) } roomProperties := json.RawMessage("{\"foo\":\"bar\"}") natsChan := make(chan *nats.Msg, 1) subject := GetSubjectForUserId(testDefaultUserId, backend) sub, err := n.Subscribe(subject, natsChan) if err != nil { t.Fatal(err) } defer func() { if err := sub.Unsubscribe(); err != nil { t.Error(err) } }() msg := &BackendServerRoomRequest{ Type: "disinvite", Disinvite: &BackendRoomDisinviteRequest{ UserIds: []string{ testDefaultUserId, }, SessionIds: []string{ roomId + "-" + hello.Hello.SessionId, }, AllUserIds: []string{}, Properties: &roomProperties, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } event, err := expectRoomlistEvent(n, natsChan, subject, "disinvite") if err != nil { t.Error(err) } else if event.Disinvite == nil { t.Errorf("Expected disinvite, got %+v", event) } else if event.Disinvite.RoomId != roomId { t.Errorf("Expected room %s, got %+v", roomId, event) } else if event.Disinvite.Properties != nil { t.Errorf("Room properties should be omitted, got %s", string(*event.Disinvite.Properties)) } else if event.Disinvite.Reason != "disinvited" { t.Errorf("Reason should be disinvited, got %s", event.Disinvite.Reason) } if message, err := client.RunUntilRoomlistDisinvite(ctx); err != nil { t.Error(err) } else if message.RoomId != roomId { t.Errorf("Expected message for room %s, got %s", roomId, message.RoomId) } if message, err := client.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { t.Errorf("Received unexpected error %s", err) } else if err == nil { t.Errorf("Server should have closed the connection, received %+v", *message) } } func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) { _, _, _, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() client1 := NewTestClient(t, server, hub) defer client1.CloseWithBye() if err := client1.SendHello(testDefaultUserId); err != nil { t.Fatal(err) } client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() if err := client2.SendHello(testDefaultUserId); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() hello1, err := client1.RunUntilHello(ctx) if err != nil { t.Fatal(err) } if _, err := client2.RunUntilHello(ctx); err != nil { t.Fatal(err) } // Join room by id. roomId1 := "test-room1" if _, err := client1.JoinRoom(ctx, roomId1); err != nil { t.Fatal(err) } roomId2 := "test-room2" if _, err := client2.JoinRoom(ctx, roomId2); err != nil { t.Fatal(err) } // Ignore "join" events. if err := client1.DrainMessages(ctx); err != nil { t.Error(err) } if err := client2.DrainMessages(ctx); err != nil { t.Error(err) } msg := &BackendServerRoomRequest{ Type: "disinvite", Disinvite: &BackendRoomDisinviteRequest{ UserIds: []string{ testDefaultUserId, }, SessionIds: []string{ roomId1 + "-" + hello1.Hello.SessionId, }, AllUserIds: []string{}, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId1, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } if message, err := client1.RunUntilRoomlistDisinvite(ctx); err != nil { t.Error(err) } else if message.RoomId != roomId1 { t.Errorf("Expected message for room %s, got %s", roomId1, message.RoomId) } if message, err := client1.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) { t.Errorf("Received unexpected error %s", err) } else if err == nil { t.Errorf("Server should have closed the connection, received %+v", *message) } if message, err := client2.RunUntilRoomlistDisinvite(ctx); err != nil { t.Error(err) } else if message.RoomId != roomId1 { t.Errorf("Expected message for room %s, got %s", roomId1, message.RoomId) } msg = &BackendServerRoomRequest{ Type: "update", Update: &BackendRoomUpdateRequest{ UserIds: []string{ testDefaultUserId, }, }, } data, err = json.Marshal(msg) if err != nil { t.Fatal(err) } res, err = performBackendRequest(server.URL+"/api/v1/room/"+roomId2, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err = ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } if message, err := client2.RunUntilRoomlistUpdate(ctx); err != nil { t.Error(err) } else if message.RoomId != roomId2 { t.Errorf("Expected message for room %s, got %s", roomId2, message.RoomId) } } func TestBackendServer_RoomUpdate(t *testing.T) { _, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() u, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } roomId := "the-room-id" emptyProperties := json.RawMessage("{}") backend := hub.backend.GetBackend(u) if backend == nil { t.Fatalf("Did not find backend") } room, err := hub.createRoom(roomId, &emptyProperties, backend) if err != nil { t.Fatalf("Could not create room: %s", err) } defer room.Close() userid := "test-userid" roomProperties := json.RawMessage("{\"foo\":\"bar\"}") natsChan := make(chan *nats.Msg, 1) subject := GetSubjectForUserId(userid, backend) sub, err := n.Subscribe(subject, natsChan) if err != nil { t.Fatal(err) } defer func() { if err := sub.Unsubscribe(); err != nil { t.Error(err) } }() msg := &BackendServerRoomRequest{ Type: "update", Update: &BackendRoomUpdateRequest{ UserIds: []string{ userid, }, Properties: &roomProperties, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } event, err := expectRoomlistEvent(n, natsChan, subject, "update") if err != nil { t.Error(err) } else if event.Update == nil { t.Errorf("Expected update, got %+v", event) } else if event.Update.RoomId != roomId { t.Errorf("Expected room %s, got %+v", roomId, event) } else if event.Update.Properties == nil || !bytes.Equal(*event.Update.Properties, roomProperties) { t.Errorf("Room properties don't match: expected %s, got %s", string(roomProperties), string(*event.Update.Properties)) } // TODO: Use event to wait for NATS messages. time.Sleep(10 * time.Millisecond) room = hub.getRoom(roomId) if room == nil { t.Fatalf("Room %s does not exist", roomId) } if string(*room.Properties()) != string(roomProperties) { t.Errorf("Expected properties %s for room %s, got %s", string(roomProperties), room.Id(), string(*room.Properties())) } } func TestBackendServer_RoomDelete(t *testing.T) { _, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() u, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } roomId := "the-room-id" emptyProperties := json.RawMessage("{}") backend := hub.backend.GetBackend(u) if backend == nil { t.Fatalf("Did not find backend") } if _, err := hub.createRoom(roomId, &emptyProperties, backend); err != nil { t.Fatalf("Could not create room: %s", err) } userid := "test-userid" natsChan := make(chan *nats.Msg, 1) subject := GetSubjectForUserId(userid, backend) sub, err := n.Subscribe(subject, natsChan) if err != nil { t.Fatal(err) } defer func() { if err := sub.Unsubscribe(); err != nil { t.Error(err) } }() msg := &BackendServerRoomRequest{ Type: "delete", Delete: &BackendRoomDeleteRequest{ UserIds: []string{ userid, }, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } // A deleted room is signalled as a "disinvite" event. event, err := expectRoomlistEvent(n, natsChan, subject, "disinvite") if err != nil { t.Error(err) } else if event.Disinvite == nil { t.Errorf("Expected disinvite, got %+v", event) } else if event.Disinvite.RoomId != roomId { t.Errorf("Expected room %s, got %+v", roomId, event) } else if event.Disinvite.Properties != nil { t.Errorf("Room properties should be omitted, got %s", string(*event.Disinvite.Properties)) } else if event.Disinvite.Reason != "deleted" { t.Errorf("Reason should be deleted, got %s", event.Disinvite.Reason) } // TODO: Use event to wait for NATS messages. time.Sleep(10 * time.Millisecond) room := hub.getRoom(roomId) if room != nil { t.Errorf("Room %s should have been deleted", roomId) } } func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) { _, _, _, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() client1 := NewTestClient(t, server, hub) defer client1.CloseWithBye() if err := client1.SendHello(testDefaultUserId + "1"); err != nil { t.Fatal(err) } client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() if err := client2.SendHello(testDefaultUserId + "2"); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() hello1, err := client1.RunUntilHello(ctx) if err != nil { t.Fatal(err) } hello2, err := client2.RunUntilHello(ctx) if err != nil { t.Fatal(err) } session1 := hub.GetSessionByPublicId(hello1.Hello.SessionId) if session1 == nil { t.Fatalf("Session %s does not exist", hello1.Hello.SessionId) } session2 := hub.GetSessionByPublicId(hello2.Hello.SessionId) if session2 == nil { t.Fatalf("Session %s does not exist", hello2.Hello.SessionId) } // Sessions have all permissions initially (fallback for old-style sessions). assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN) assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN) // Join room by id. roomId := "test-room" if room, err := client1.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } if room, err := client2.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } // Ignore "join" events. if err := client1.DrainMessages(ctx); err != nil { t.Error(err) } if err := client2.DrainMessages(ctx); err != nil { t.Error(err) } msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ Changed: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, }, { "sessionId": roomId + "-" + hello2.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, Users: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA}, }, { "sessionId": roomId + "-" + hello2.Hello.SessionId, "permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN}, }, }, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } // TODO: Use event to wait for NATS messages. time.Sleep(10 * time.Millisecond) assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasNotPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN) assertSessionHasNotPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN) } func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) { _, _, _, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() client := NewTestClient(t, server, hub) defer client.CloseWithBye() if err := client.SendHello(testDefaultUserId); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() hello, err := client.RunUntilHello(ctx) if err != nil { t.Fatal(err) } session := hub.GetSessionByPublicId(hello.Hello.SessionId) if session == nil { t.Fatalf("Session %s does not exist", hello.Hello.SessionId) } // Sessions have all permissions initially (fallback for old-style sessions). assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN) // Join room by id. roomId := "test-room" room, err := client.JoinRoom(ctx, roomId) if err != nil { t.Fatal(err) } if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } // Ignore "join" events. if err := client.DrainMessages(ctx); err != nil { t.Error(err) } // Updating with empty permissions upgrades to non-old-style and removes // all previously available permissions. msg := &BackendServerRoomRequest{ Type: "participants", Participants: &BackendRoomParticipantsRequest{ Changed: []map[string]interface{}{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, }, }, Users: []map[string]interface{}{ { "sessionId": roomId + "-" + hello.Hello.SessionId, "permissions": []Permission{}, }, }, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } // TODO: Use event to wait for NATS messages. time.Sleep(10 * time.Millisecond) assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA) assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN) } func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) { _, _, _, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() client1 := NewTestClient(t, server, hub) defer client1.CloseWithBye() if err := client1.SendHello(testDefaultUserId + "1"); err != nil { t.Fatal(err) } client2 := NewTestClient(t, server, hub) defer client2.CloseWithBye() if err := client2.SendHello(testDefaultUserId + "2"); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() hello1, err := client1.RunUntilHello(ctx) if err != nil { t.Fatal(err) } hello2, err := client2.RunUntilHello(ctx) if err != nil { t.Fatal(err) } // Join room by id. roomId := "test-room" if room, err := client1.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } // Give message processing some time. time.Sleep(10 * time.Millisecond) if room, err := client2.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() msg := &BackendServerRoomRequest{ Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), Changed: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, }, { "sessionId": "unknown-room-session-id", "inCall": 3, }, }, Users: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, }, { "sessionId": "unknown-room-session-id", "inCall": 3, }, }, }, } data, err := json.Marshal(msg) if err != nil { t.Error(err) return } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Error(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } }() // Ensure the first request is being processed. time.Sleep(100 * time.Millisecond) wg.Add(1) go func() { defer wg.Done() msg := &BackendServerRoomRequest{ Type: "incall", InCall: &BackendRoomInCallRequest{ InCall: json.RawMessage("7"), Changed: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, }, { "sessionId": roomId + "-" + hello2.Hello.SessionId, "inCall": 3, }, }, Users: []map[string]interface{}{ { "sessionId": roomId + "-" + hello1.Hello.SessionId, "inCall": 7, }, { "sessionId": roomId + "-" + hello2.Hello.SessionId, "inCall": 3, }, }, }, } data, err := json.Marshal(msg) if err != nil { t.Error(err) return } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Error(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } }() wg.Wait() if t.Failed() { return } msg1_a, err := client1.RunUntilMessage(ctx) if err != nil { t.Error(err) } if in_call_1, err := checkMessageParticipantsInCall(msg1_a); err != nil { t.Error(err) } else if len(in_call_1.Users) != 2 { msg1_b, err := client1.RunUntilMessage(ctx) if err != nil { t.Error(err) } if in_call_2, err := checkMessageParticipantsInCall(msg1_b); err != nil { t.Error(err) } else if len(in_call_2.Users) != 2 { t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users)) } } msg2_a, err := client2.RunUntilMessage(ctx) if err != nil { t.Error(err) } if in_call_1, err := checkMessageParticipantsInCall(msg2_a); err != nil { t.Error(err) } else if len(in_call_1.Users) != 2 { msg2_b, err := client2.RunUntilMessage(ctx) if err != nil { t.Error(err) } if in_call_2, err := checkMessageParticipantsInCall(msg2_b); err != nil { t.Error(err) } else if len(in_call_2.Users) != 2 { t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users)) } } ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond) defer cancel2() if msg1_c, _ := client1.RunUntilMessage(ctx2); msg1_c != nil { if in_call_2, err := checkMessageParticipantsInCall(msg1_c); err != nil { t.Error(err) } else if len(in_call_2.Users) != 2 { t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users)) } } ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond) defer cancel3() if msg2_c, _ := client2.RunUntilMessage(ctx3); msg2_c != nil { if in_call_2, err := checkMessageParticipantsInCall(msg2_c); err != nil { t.Error(err) } else if len(in_call_2.Users) != 2 { t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users)) } } } func TestBackendServer_RoomMessage(t *testing.T) { _, _, _, hub, _, server, shutdown := CreateBackendServerForTest(t) defer shutdown() client := NewTestClient(t, server, hub) defer client.CloseWithBye() if err := client.SendHello(testDefaultUserId + "1"); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() _, err := client.RunUntilHello(ctx) if err != nil { t.Fatal(err) } // Join room by id. roomId := "test-room" if room, err := client.JoinRoom(ctx, roomId); err != nil { t.Fatal(err) } else if room.Room.RoomId != roomId { t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) } // Ignore "join" events. if err := client.DrainMessages(ctx); err != nil { t.Error(err) } messageData := json.RawMessage("{\"foo\":\"bar\"}") msg := &BackendServerRoomRequest{ Type: "message", Message: &BackendRoomMessageRequest{ Data: &messageData, }, } data, err := json.Marshal(msg) if err != nil { t.Fatal(err) } res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } message, err := client.RunUntilRoomMessage(ctx) if err != nil { t.Error(err) } else if message.RoomId != roomId { t.Errorf("Expected message for room %s, got %s", roomId, message.RoomId) } else if !bytes.Equal(messageData, *message.Data) { t.Errorf("Expected message data %s, got %s", string(messageData), string(*message.Data)) } } func TestBackendServer_TurnCredentials(t *testing.T) { _, _, _, _, _, server, shutdown := CreateBackendServerForTestWithTurn(t) defer shutdown() q := make(url.Values) q.Set("service", "turn") q.Set("api", turnApiKey) request, err := http.NewRequest("GET", server.URL+"/turn/credentials?"+q.Encode(), nil) if err != nil { t.Fatal(err) } client := &http.Client{} res, err := client.Do(request) if err != nil { t.Fatal(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } if res.StatusCode != 200 { t.Errorf("Expected successful request, got %s: %s", res.Status, string(body)) } var cred TurnCredentials if err := json.Unmarshal(body, &cred); err != nil { t.Fatal(err) } m := hmac.New(sha1.New, []byte(turnSecret)) m.Write([]byte(cred.Username)) // nolint password := base64.StdEncoding.EncodeToString(m.Sum(nil)) if cred.Password != password { t.Errorf("Expected password %s, got %s", password, cred.Password) } if cred.TTL != int64((24 * time.Hour).Seconds()) { t.Errorf("Expected a TTL of %d, got %d", int64((24 * time.Hour).Seconds()), cred.TTL) } if !reflect.DeepEqual(cred.URIs, turnServers) { t.Errorf("Expected the list of servers as %s, got %s", turnServers, cred.URIs) } }