nextcloud-spreed-signaling/backend_server_test.go

1309 lines
34 KiB
Go

/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2017 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @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 <http://www.gnu.org/licenses/>.
*/
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)
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)
}
}