diff --git a/go/channelling/config.go b/go/channelling/config.go index 5fcc65ea..1f1800d2 100644 --- a/go/channelling/config.go +++ b/go/channelling/config.go @@ -34,6 +34,7 @@ type Config struct { RoomTypes map[*regexp.Regexp]string `json:"-"` // Map of regular expression -> room type RoomNameCaseSensitive bool // Whether the room names are case sensitive. LockedRoomJoinableWithPIN bool // Whether locked rooms should be joinable by providing the PIN the room was locked with + PublicRoomNames *regexp.Regexp `json:"-"` // Regular expression that specifies room paths that may be created/joined without a user account. } func (config *Config) WithModule(m string) bool { diff --git a/go/channelling/room_manager.go b/go/channelling/room_manager.go index c0c5d51e..4ed4b12d 100644 --- a/go/channelling/room_manager.go +++ b/go/channelling/room_manager.go @@ -209,9 +209,17 @@ func (rooms *roomManager) Get(roomID string) (room RoomWorker, ok bool) { return } +func (rooms *roomManager) isPublicRoom(roomName string) bool { + return rooms.PublicRoomNames != nil && + rooms.PublicRoomNames.MatchString(roomName) +} + func (rooms *roomManager) GetOrCreate(roomID, roomName, roomType string, credentials *DataRoomCredentials, sessionAuthenticated bool) (RoomWorker, error) { + isPublic := false if rooms.AuthorizeRoomJoin && rooms.UsersEnabled && !sessionAuthenticated { - return nil, NewDataError("room_join_requires_account", "Room join requires a user account") + if isPublic = rooms.isPublicRoom(roomName); !isPublic { + return nil, NewDataError("room_join_requires_account", "Room join requires a user account") + } } if room, ok := rooms.Get(roomID); ok { @@ -231,8 +239,13 @@ func (rooms *roomManager) GetOrCreate(roomID, roomName, roomType string, credent } if rooms.UsersEnabled && rooms.AuthorizeRoomCreation && !sessionAuthenticated { - rooms.Unlock() - return nil, NewDataError("room_join_requires_account", "Room creation requires a user account") + // Only need to check for public room if not checked above. + if !isPublic { + if isPublic = rooms.isPublicRoom(roomName); !isPublic { + rooms.Unlock() + return nil, NewDataError("room_join_requires_account", "Room creation requires a user account") + } + } } room := NewRoomWorker(rooms, roomID, roomName, roomType, credentials) diff --git a/go/channelling/room_manager_test.go b/go/channelling/room_manager_test.go index 579b33fd..a31f69f6 100644 --- a/go/channelling/room_manager_test.go +++ b/go/channelling/room_manager_test.go @@ -22,6 +22,7 @@ package channelling import ( + "regexp" "testing" "github.com/strukturag/spreed-webrtc/go/channelling" @@ -74,6 +75,55 @@ func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenJoinR assertDataError(t, err, "room_join_requires_account") } +func Test_RoomManager_JoinPublicRoom_ForUnauthenticatedSessionsWhenCreationRequiresAnAccount(t *testing.T) { + roomManager, config := NewTestRoomManager() + config.UsersEnabled = true + config.AuthorizeRoomCreation = true + + unauthenticatedSession := &Session{} + _, err := roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + assertDataError(t, err, "room_join_requires_account") + + config.PublicRoomNames = regexp.MustCompile("^public$") + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining public room", err) + } + + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + assertDataError(t, err, "room_join_requires_account") +} + +func Test_RoomManager_JoinPublicRoom_ForUnauthenticatedSessionsWhenJoinRequiresAnAccount(t *testing.T) { + roomManager, config := NewTestRoomManager() + config.UsersEnabled = true + config.AuthorizeRoomJoin = true + + authenticatedSession := &Session{userid: "9870457"} + _, err := roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, authenticatedSession, true, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining room while authenticated", err) + } + + unauthenticatedSession := &Session{} + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + assertDataError(t, err, "room_join_requires_account") + + config.PublicRoomNames = regexp.MustCompile("^public$") + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining public room", err) + } + + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, authenticatedSession, true, nil) + if err != nil { + t.Fatalf("Unexpected error %v joining room while authenticated", err) + } + + _, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil) + assertDataError(t, err, "room_join_requires_account") +} + func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) { roomManager, _ := NewTestRoomManager() _, err := roomManager.UpdateRoom(&Session{}, nil) diff --git a/go/channelling/server/config.go b/go/channelling/server/config.go index cfe57da6..f664e079 100644 --- a/go/channelling/server/config.go +++ b/go/channelling/server/config.go @@ -119,6 +119,16 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e } } + publicRoomNamesString := container.GetStringDefault("app", "publicRooms", "") + var publicRoomNames *regexp.Regexp + if publicRoomNamesString != "" { + var err error + if publicRoomNames, err = regexp.Compile(publicRoomNamesString); err != nil { + return nil, fmt.Errorf("Invalid regular expression '%s': %s", publicRoomNamesString, err) + } + log.Printf("Allowed public rooms: %s\n", publicRoomNamesString) + } + return &channelling.Config{ Title: container.GetStringDefault("app", "title", "Spreed WebRTC"), Ver: ver, @@ -148,6 +158,7 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e RoomTypes: roomTypes, RoomNameCaseSensitive: container.GetBoolDefault("app", "caseSensitiveRooms", false), LockedRoomJoinableWithPIN: container.GetBoolDefault("app", "lockedRoomJoinableWithPIN", true), + PublicRoomNames: publicRoomNames, }, nil } diff --git a/server.conf.in b/server.conf.in index 1641ee85..98d51ca8 100644 --- a/server.conf.in +++ b/server.conf.in @@ -100,6 +100,10 @@ encryptionSecret = tne-default-encryption-block-key ; Whether locked rooms should be joinable by providing the PIN the room was ; locked with. Optional, defaults to true. ;lockedRoomJoinableWithPIN = true +; Regular expression specifying room names that can be created / joined without +; a valid user account (even if "authorizeRoomJoin" or "authorizeRoomCreation" +; is enabled). +;publicRooms = ; Whether the pipelines API should be enabled. Optional, defaults to false. ;pipelinesEnabled = false ; Server token is a public random string which is used to enhance security of