Browse Source

Server side support for requiring a user account to create rooms.

Also streamlines error creations.
pull/148/head
Lance Cooper 11 years ago
parent
commit
1f09f72bd2
  1. 2
      doc/CHANNELING-API.txt
  2. 3
      server.conf.in
  3. 4
      src/app/spreed-webrtc-server/channeling.go
  4. 4
      src/app/spreed-webrtc-server/channelling_api_test.go
  5. 2
      src/app/spreed-webrtc-server/config.go
  6. 65
      src/app/spreed-webrtc-server/room_manager.go
  7. 32
      src/app/spreed-webrtc-server/room_manager_test.go
  8. 6
      src/app/spreed-webrtc-server/roomworker.go
  9. 7
      src/app/spreed-webrtc-server/session.go

2
doc/CHANNELING-API.txt

@ -193,6 +193,8 @@ Special purpose documents for channling
authorization_not_required : No credentials should be provided for this authorization_not_required : No credentials should be provided for this
room. room.
invalid_credentials : The provided credentials are incorrect. invalid_credentials : The provided credentials are incorrect.
room_join_requires_account : Server configuration requires an
authenticated user account to join this room.
Welcome Welcome

3
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 ; all users will join this room if enabled. If it is disabled then a room join
; form will be shown instead. ; form will be shown instead.
;defaultRoomEnabled = true ;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 token is a public random string which is used to enhance security of
; server generated security tokens. When the serverToken is changed all existing ; 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). ; nonces become invalid. Use 32 or 64 characters (eg. 16 or 32 byte hex).

4
src/app/spreed-webrtc-server/channeling.go

@ -27,6 +27,10 @@ type DataError struct {
Message string Message string
} }
func NewDataError(code, message string) error {
return &DataError{"Error", code, message}
}
func (err *DataError) Error() string { func (err *DataError) Error() string {
return err.Message return err.Message
} }

4
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) { func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) {
iid := "foo" iid := "foo"
api, client, session, roomManager := NewTestChannellingAPI() 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{}}) 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) { func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) {
iid, roomName := "123", "foo" iid, roomName := "123", "foo"
api, client, session, roomManager := NewTestChannellingAPI() 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: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}})
api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}})

2
src/app/spreed-webrtc-server/config.go

@ -46,6 +46,7 @@ type Config struct {
DefaultRoomEnabled bool // Flag if default room ("") is enabled DefaultRoomEnabled bool // Flag if default room ("") is enabled
Plugin string // Plugin to load Plugin string // Plugin to load
globalRoomID string // Id of the global room (not exported to Javascript) 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 { 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), DefaultRoomEnabled: container.GetBoolDefault("app", "defaultRoomEnabled", true),
Plugin: container.GetStringDefault("app", "plugin", ""), Plugin: container.GetStringDefault("app", "plugin", ""),
globalRoomID: container.GetStringDefault("app", "globalRoom", ""), globalRoomID: container.GetStringDefault("app", "globalRoom", ""),
authorizeRoomCreation: container.GetBoolDefault("app", "authorizeRoomCreation", false),
} }
} }

65
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) { func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials, session *Session, sender Sender) (*DataRoom, error) {
if id == "" && !rooms.DefaultRoomEnabled { 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) { 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) { func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoom, error) {
if !session.Hello || session.Roomid != room.Name { 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 // XXX(lcooper): We'll process and send documents without this field
// correctly, however clients cannot not handle it currently. // correctly, however clients cannot not handle it currently.
@ -147,32 +152,38 @@ func (rooms *roomManager) Get(id string) (room RoomWorker, ok bool) {
return return
} }
func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials) RoomWorker { func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials, session *Session) (RoomWorker, error) {
room, ok := rooms.Get(id) if room, ok := rooms.Get(id); ok {
if !ok { return room, nil
rooms.Lock() }
// Need to re-check, another thread might have created the room
// while we waited for the lock. rooms.Lock()
room, ok = rooms.roomTable[id] // Need to re-check, another thread might have created the room
if !ok { // while we waited for the lock.
room = NewRoomWorker(rooms, id, credentials) if room, ok := rooms.roomTable[id]; ok {
rooms.roomTable[id] = room rooms.Unlock()
rooms.Unlock() return room, nil
go func() { }
// Start room, this blocks until room expired.
room.Start() if rooms.UsersEnabled && rooms.authorizeRoomCreation && !session.Authenticated() {
// Cleanup room when we are done. rooms.Unlock()
rooms.Lock() return nil, NewDataError("room_join_requires_account", "Room creation requires a user account")
defer rooms.Unlock()
delete(rooms.roomTable, id)
log.Printf("Cleaned up room '%s'\n", id)
}()
} else {
rooms.Unlock()
}
} }
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 { func (rooms *roomManager) GlobalUsers() []*roomUser {

32
src/app/spreed-webrtc-server/room_manager_test.go

@ -25,26 +25,48 @@ import (
"testing" "testing"
) )
func NewTestRoomManager() RoomManager { func NewTestRoomManager() (RoomManager, *Config) {
return NewRoomManager(&Config{}, nil) 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) { func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) {
roomManager := NewTestRoomManager() roomManager, _ := NewTestRoomManager()
_, err := roomManager.UpdateRoom(&Session{}, nil) _, err := roomManager.UpdateRoom(&Session{}, nil)
assertDataError(t, err, "not_in_room") assertDataError(t, err, "not_in_room")
} }
func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfUpdatingAnUnjoinedRoom(t *testing.T) { func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfUpdatingAnUnjoinedRoom(t *testing.T) {
roomManager := NewTestRoomManager() roomManager, _ := NewTestRoomManager()
session := &Session{Hello: true, Roomid: "foo"} session := &Session{Hello: true, Roomid: "foo"}
_, err := roomManager.UpdateRoom(session, &DataRoom{Name: "bar"}) _, err := roomManager.UpdateRoom(session, &DataRoom{Name: "bar"})
assertDataError(t, err, "not_in_room") assertDataError(t, err, "not_in_room")
} }
func Test_RoomManager_UpdateRoom_ReturnsACorrectlyTypedDocument(t *testing.T) { func Test_RoomManager_UpdateRoom_ReturnsACorrectlyTypedDocument(t *testing.T) {
roomManager := NewTestRoomManager() roomManager, _ := NewTestRoomManager()
session := &Session{Hello: true, Roomid: "foo"} session := &Session{Hello: true, Roomid: "foo"}
room, err := roomManager.UpdateRoom(session, &DataRoom{Name: session.Roomid}) room, err := roomManager.UpdateRoom(session, &DataRoom{Name: session.Roomid})
if err != nil { if err != nil {

6
src/app/spreed-webrtc-server/roomworker.go

@ -244,18 +244,18 @@ func (r *roomWorker) Join(credentials *DataRoomCredentials, session *Session, se
worker := func() { worker := func() {
r.mutex.Lock() r.mutex.Lock()
if r.credentials == nil && credentials != nil { 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() r.mutex.Unlock()
return return
} else if r.credentials != nil { } else if r.credentials != nil {
if 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() r.mutex.Unlock()
return return
} }
if len(r.credentials.PIN) != len(credentials.PIN) || subtle.ConstantTimeCompare([]byte(r.credentials.PIN), []byte(credentials.PIN)) != 1 { 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() r.mutex.Unlock()
return return
} }

7
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 { func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error {
s.mutex.Lock() s.mutex.Lock()

Loading…
Cancel
Save