diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 5a0ac709..4427ec7a 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -193,6 +193,8 @@ Special purpose documents for channling authorization_not_required : No credentials should be provided for this room. invalid_credentials : The provided credentials are incorrect. + room_join_requires_account : Server configuration requires an + authenticated user account to join this room. Welcome diff --git a/server.conf.in b/server.conf.in index 6b1c6acf..282c85da 100644 --- a/server.conf.in +++ b/server.conf.in @@ -78,6 +78,9 @@ encryptionSecret = tne-default-encryption-block-key ; all users will join this room if enabled. If it is disabled then a room join ; form will be shown instead. ;defaultRoomEnabled = true +; Whether a user account is required to create a room. This only has an effect +; if user accounts are enabled. Optional, defaults to false. +;authorizeRoomCreation = false ; Server token is a public random string which is used to enhance security of ; server generated security tokens. When the serverToken is changed all existing ; nonces become invalid. Use 32 or 64 characters (eg. 16 or 32 byte hex). diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index 7d3c516b..5099b114 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -27,6 +27,10 @@ type DataError struct { Message string } +func NewDataError(code, message string) error { + return &DataError{"Error", code, message} +} + func (err *DataError) Error() string { return err.Message } diff --git a/src/app/spreed-webrtc-server/channelling_api_test.go b/src/app/spreed-webrtc-server/channelling_api_test.go index 0f0f0241..0ee186c1 100644 --- a/src/app/spreed-webrtc-server/channelling_api_test.go +++ b/src/app/spreed-webrtc-server/channelling_api_test.go @@ -195,7 +195,7 @@ func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAWelcome(t func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) { iid := "foo" api, client, session, roomManager := NewTestChannellingAPI() - roomManager.joinError = &DataError{Type: "Error", Code: "bad_join"} + roomManager.joinError = NewDataError("bad_join", "") api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: iid, Hello: &DataHello{}}) @@ -231,7 +231,7 @@ func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpda func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) { iid, roomName := "123", "foo" api, client, session, roomManager := NewTestChannellingAPI() - roomManager.updateError = &DataError{Type: "Error", Code: "a_room_error", Message: ""} + roomManager.updateError = NewDataError("a_room_error", "") api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}}) api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) diff --git a/src/app/spreed-webrtc-server/config.go b/src/app/spreed-webrtc-server/config.go index 588c066d..2a727e3b 100644 --- a/src/app/spreed-webrtc-server/config.go +++ b/src/app/spreed-webrtc-server/config.go @@ -46,6 +46,7 @@ type Config struct { DefaultRoomEnabled bool // Flag if default room ("") is enabled Plugin string // Plugin to load globalRoomID string // Id of the global room (not exported to Javascript) + authorizeRoomCreation bool // Whether a user account is required to create rooms (not exported to Javascript) } func NewConfig(container phoenix.Container, tokens bool) *Config { @@ -98,6 +99,7 @@ func NewConfig(container phoenix.Container, tokens bool) *Config { DefaultRoomEnabled: container.GetBoolDefault("app", "defaultRoomEnabled", true), Plugin: container.GetStringDefault("app", "plugin", ""), globalRoomID: container.GetStringDefault("app", "globalRoom", ""), + authorizeRoomCreation: container.GetBoolDefault("app", "authorizeRoomCreation", false), } } diff --git a/src/app/spreed-webrtc-server/room_manager.go b/src/app/spreed-webrtc-server/room_manager.go index f14c364e..f0d40c3c 100644 --- a/src/app/spreed-webrtc-server/room_manager.go +++ b/src/app/spreed-webrtc-server/room_manager.go @@ -73,10 +73,15 @@ func (rooms *roomManager) RoomUsers(session *Session) []*DataSession { func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials, session *Session, sender Sender) (*DataRoom, error) { if id == "" && !rooms.DefaultRoomEnabled { - return nil, &DataError{Type: "Error", Code: "default_room_disabled", Message: "The default room is not enabled"} + return nil, NewDataError("default_room_disabled", "The default room is not enabled") } - return rooms.GetOrCreate(id, credentials).Join(credentials, session, sender) + roomWorker, err := rooms.GetOrCreate(id, credentials, session) + if err != nil { + return nil, err + } + + return roomWorker.Join(credentials, session, sender) } func (rooms *roomManager) LeaveRoom(session *Session) { @@ -87,7 +92,7 @@ func (rooms *roomManager) LeaveRoom(session *Session) { func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoom, error) { if !session.Hello || session.Roomid != room.Name { - return nil, &DataError{Type: "Error", Code: "not_in_room", Message: "Cannot update other rooms"} + return nil, NewDataError("not_in_room", "Cannot update other rooms") } // XXX(lcooper): We'll process and send documents without this field // correctly, however clients cannot not handle it currently. @@ -147,32 +152,38 @@ func (rooms *roomManager) Get(id string) (room RoomWorker, ok bool) { return } -func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials) RoomWorker { - room, ok := rooms.Get(id) - if !ok { - rooms.Lock() - // Need to re-check, another thread might have created the room - // while we waited for the lock. - room, ok = rooms.roomTable[id] - if !ok { - room = NewRoomWorker(rooms, id, credentials) - rooms.roomTable[id] = room - rooms.Unlock() - go func() { - // Start room, this blocks until room expired. - room.Start() - // Cleanup room when we are done. - rooms.Lock() - defer rooms.Unlock() - delete(rooms.roomTable, id) - log.Printf("Cleaned up room '%s'\n", id) - }() - } else { - rooms.Unlock() - } +func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials, session *Session) (RoomWorker, error) { + if room, ok := rooms.Get(id); ok { + return room, nil + } + + rooms.Lock() + // Need to re-check, another thread might have created the room + // while we waited for the lock. + if room, ok := rooms.roomTable[id]; ok { + rooms.Unlock() + return room, nil + } + + if rooms.UsersEnabled && rooms.authorizeRoomCreation && !session.Authenticated() { + rooms.Unlock() + return nil, NewDataError("room_join_requires_account", "Room creation requires a user account") } - return room + room := NewRoomWorker(rooms, id, credentials) + rooms.roomTable[id] = room + rooms.Unlock() + go func() { + // Start room, this blocks until room expired. + room.Start() + // Cleanup room when we are done. + rooms.Lock() + defer rooms.Unlock() + delete(rooms.roomTable, id) + log.Printf("Cleaned up room '%s'\n", id) + }() + + return room, nil } func (rooms *roomManager) GlobalUsers() []*roomUser { diff --git a/src/app/spreed-webrtc-server/room_manager_test.go b/src/app/spreed-webrtc-server/room_manager_test.go index 55ceb2e4..571097b4 100644 --- a/src/app/spreed-webrtc-server/room_manager_test.go +++ b/src/app/spreed-webrtc-server/room_manager_test.go @@ -25,26 +25,48 @@ import ( "testing" ) -func NewTestRoomManager() RoomManager { - return NewRoomManager(&Config{}, nil) +func NewTestRoomManager() (RoomManager, *Config) { + config := &Config{} + return NewRoomManager(config, nil), config +} + +func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenCreationRequiresAnAccount(t *testing.T) { + roomManager, config := NewTestRoomManager() + config.UsersEnabled = true + config.authorizeRoomCreation = true + + unauthenticatedSession := &Session{} + _, err := roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil) + assertDataError(t, err, "room_join_requires_account") + + authenticatedSession := &Session{userid: "9870457"} + _, err = roomManager.JoinRoom("foo", nil, authenticatedSession, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining room while authenticated", err) + } + + _, err = roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining room while unauthenticated", err) + } } func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) { - roomManager := NewTestRoomManager() + roomManager, _ := NewTestRoomManager() _, err := roomManager.UpdateRoom(&Session{}, nil) assertDataError(t, err, "not_in_room") } func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfUpdatingAnUnjoinedRoom(t *testing.T) { - roomManager := NewTestRoomManager() + roomManager, _ := NewTestRoomManager() session := &Session{Hello: true, Roomid: "foo"} _, err := roomManager.UpdateRoom(session, &DataRoom{Name: "bar"}) assertDataError(t, err, "not_in_room") } func Test_RoomManager_UpdateRoom_ReturnsACorrectlyTypedDocument(t *testing.T) { - roomManager := NewTestRoomManager() + roomManager, _ := NewTestRoomManager() session := &Session{Hello: true, Roomid: "foo"} room, err := roomManager.UpdateRoom(session, &DataRoom{Name: session.Roomid}) if err != nil { diff --git a/src/app/spreed-webrtc-server/roomworker.go b/src/app/spreed-webrtc-server/roomworker.go index 2e52fefa..849ac436 100644 --- a/src/app/spreed-webrtc-server/roomworker.go +++ b/src/app/spreed-webrtc-server/roomworker.go @@ -244,18 +244,18 @@ func (r *roomWorker) Join(credentials *DataRoomCredentials, session *Session, se worker := func() { r.mutex.Lock() if r.credentials == nil && credentials != nil { - results <- joinResult{nil, &DataError{"Error", "authorization_not_required", "No credentials may be provided for this room"}} + results <- joinResult{nil, NewDataError("authorization_not_required", "No credentials may be provided for this room")} r.mutex.Unlock() return } else if r.credentials != nil { if credentials == nil { - results <- joinResult{nil, &DataError{"Error", "authorization_required", "Valid credentials are required to join this room"}} + results <- joinResult{nil, NewDataError("authorization_required", "Valid credentials are required to join this room")} r.mutex.Unlock() return } if len(r.credentials.PIN) != len(credentials.PIN) || subtle.ConstantTimeCompare([]byte(r.credentials.PIN), []byte(credentials.PIN)) != 1 { - results <- joinResult{nil, &DataError{"Error", "invalid_credentials", "The provided credentials are incorrect"}} + results <- joinResult{nil, NewDataError("invalid_credentials", "The provided credentials are incorrect")} r.mutex.Unlock() return } diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 92623b16..905acd72 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -173,6 +173,13 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { } +func (s *Session) Authenticated() (authenticated bool) { + s.mutex.Lock() + authenticated = s.userid != "" + s.mutex.Unlock() + return +} + func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error { s.mutex.Lock()