Created protocol exchanges for creating controlled rooms
This commit is contained in:
parent
da633e2883
commit
cdce17b10b
@ -417,38 +417,30 @@ class SyncplayClient(object):
|
|||||||
|
|
||||||
def createControlledRoom(self):
|
def createControlledRoom(self):
|
||||||
controlPassword = RoomPasswordGenerator.generate_password()
|
controlPassword = RoomPasswordGenerator.generate_password()
|
||||||
# TODO (Client): Send request to server; handle success and failure
|
|
||||||
# TODO (Server): Process request, send response
|
|
||||||
self.ui.showMessage("Attempting to create controlled room suffix with password '{}'...".format(controlPassword))
|
self.ui.showMessage("Attempting to create controlled room suffix with password '{}'...".format(controlPassword))
|
||||||
self._protocol.requestControlledRoom(controlPassword)
|
self._protocol.requestControlledRoom(controlPassword)
|
||||||
|
|
||||||
def controlledRoomCreated(self, controlPassword, roomName):
|
def controlledRoomCreated(self, controlPassword, roomName):
|
||||||
# NOTE (Client): Triggered by protocol to handle createControlledRoom when room is created
|
|
||||||
self.ui.showMessage("Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword))
|
self.ui.showMessage("Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword))
|
||||||
self.setRoom(roomName)
|
self.setRoom(roomName)
|
||||||
self.sendRoom()
|
self.sendRoom()
|
||||||
|
self._protocol.requestControlledRoom(controlPassword)
|
||||||
self.ui.updateRoomName(roomName)
|
self.ui.updateRoomName(roomName)
|
||||||
|
|
||||||
def controlledRoomCreationError(self, errormsg):
|
|
||||||
# NOTE (Client): Triggered by protocol to handle createControlledRoom if controlled rooms are not supported by server or if password is malformed
|
|
||||||
# NOTE (Server): Triggered by protocol to handle createControlledRoom if password is malformed
|
|
||||||
self.ui.showErrorMessage("Failed to create the controlled room suffix for the following reason: {}.".format(errormsg))
|
|
||||||
|
|
||||||
def identifyAsController(self, controlPassword):
|
def identifyAsController(self, controlPassword):
|
||||||
# TODO (Client): Send identification to server; handle success and failure
|
|
||||||
# TODO (Server): Process request, send response
|
|
||||||
self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword))
|
self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword))
|
||||||
self._protocol.requestControlledRoom(controlPassword)
|
self._protocol.requestControlledRoom(controlPassword)
|
||||||
|
|
||||||
def controllerIdentificationError(self, errormsg):
|
def controllerIdentificationError(self):
|
||||||
# NOTE (Client): Triggered by protocol handling identiedAsController, e.g. on server response or not supported error
|
self.ui.showErrorMessage("Failed to identify as a room controller.")
|
||||||
# NOTE (Server): Relevant error given in response to identifyAsController if password is wrong
|
|
||||||
self.ui.showErrorMessage("Failed to identify as a room controller for the following reason: {}.".format(errormsg))
|
|
||||||
|
|
||||||
def notControllerError(self, errormsg):
|
def controllerIdentificationSuccess(self):
|
||||||
# NOTE (Client): Trigger when client gets a "not controller" error from server (e.g. due to illegal pauses, unpauses and seeks)
|
# TODO: More UI stuff
|
||||||
# NOTE (Server): Give "not controller" error when users try to perform illegal pause, unpause or seek
|
self.ui.showErrorMessage("Authenticated as a room controller")
|
||||||
self.ui.showErrorMessage("There are currently people with 'room controller' status in this room. As such, only they can pause, unpause and seek. If you want to perform these actions then you must either identify as a controller or join a different room. See http://syncplay.pl/guide/ for more details.")
|
|
||||||
|
# TODO: A person authenticated as a room controller
|
||||||
|
# TODO: Disable UI's "Create new Controlled Room when in Controlled Room"
|
||||||
|
# TODO: Disable authenticate when authenticated
|
||||||
|
|
||||||
class _WarningManager(object):
|
class _WarningManager(object):
|
||||||
def __init__(self, player, userlist, ui):
|
def __init__(self, player, userlist, ui):
|
||||||
|
|||||||
@ -114,13 +114,21 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
self._client.userlist.modUser(username, room, file_)
|
self._client.userlist.modUser(username, room, file_)
|
||||||
|
|
||||||
def handleSet(self, settings):
|
def handleSet(self, settings):
|
||||||
for set_ in settings.iteritems():
|
for (command, values) in settings.iteritems():
|
||||||
command = set_[0]
|
|
||||||
if command == "room":
|
if command == "room":
|
||||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
roomName = values["name"] if values.has_key("name") else None
|
||||||
self._client.setRoom(roomName)
|
self._client.setRoom(roomName)
|
||||||
elif command == "user":
|
elif command == "user":
|
||||||
self._SetUser(set_[1])
|
self._SetUser(values)
|
||||||
|
elif command == "controllerAuth":
|
||||||
|
if values['success']:
|
||||||
|
self._client.controllerIdentificationSuccess()
|
||||||
|
else:
|
||||||
|
self._client.controllerIdentificationError()
|
||||||
|
elif command == "newControlledRoom":
|
||||||
|
controlPassword = values['password']
|
||||||
|
roomName = values['roomName']
|
||||||
|
self._client.controlledRoomCreated(controlPassword, roomName)
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
@ -210,8 +218,15 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||||
self.sendMessage({"State": state})
|
self.sendMessage({"State": state})
|
||||||
|
|
||||||
|
def requestControlledRoom(self, password):
|
||||||
|
self.sendSet({
|
||||||
|
"controllerAuth": {
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def handleError(self, error):
|
def handleError(self, error):
|
||||||
self.dropWithError(error["message"]) # TODO: more processing and fallbacking
|
self.dropWithError(error["message"])
|
||||||
|
|
||||||
def sendError(self, message):
|
def sendError(self, message):
|
||||||
self.sendMessage({"Error": {"message": message}})
|
self.sendMessage({"Error": {"message": message}})
|
||||||
@ -311,10 +326,28 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
self._factory.setWatcherRoom(self._watcher, roomName)
|
self._factory.setWatcherRoom(self._watcher, roomName)
|
||||||
elif command == "file":
|
elif command == "file":
|
||||||
self._watcher.setFile(set_[1])
|
self._watcher.setFile(set_[1])
|
||||||
|
elif command == "controllerAuth":
|
||||||
|
password = set_[1]["password"] if set_[1].has_key("password") else None
|
||||||
|
self._factory.authRoomController(self._watcher, password)
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
|
|
||||||
|
def sendNewControlledRoom(self, roomName, password):
|
||||||
|
self.sendSet({
|
||||||
|
"newControlledRoom": {
|
||||||
|
"password": password,
|
||||||
|
"roomName": roomName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def sendControlledRoomAuthStatus(self, success):
|
||||||
|
self.sendSet({
|
||||||
|
"controllerAuth": {
|
||||||
|
"success": success
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def sendUserSetting(self, username, room, file_, event):
|
def sendUserSetting(self, username, room, file_, event):
|
||||||
room = {"name": room.getName()}
|
room = {"name": room.getName()}
|
||||||
user = {}
|
user = {}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import random
|
||||||
|
import re
|
||||||
from twisted.internet import task, reactor
|
from twisted.internet import task, reactor
|
||||||
from twisted.internet.protocol import Factory
|
from twisted.internet.protocol import Factory
|
||||||
import syncplay
|
import syncplay
|
||||||
@ -11,12 +13,18 @@ import os
|
|||||||
from string import Template
|
from string import Template
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
# TODO: Check if room should have status of "Controlled room"
|
||||||
|
# TODO: Make only controllers able to control a room
|
||||||
|
# TODO: Send list of controllers
|
||||||
|
# TODO: Broadcast information about controller auth
|
||||||
class SyncFactory(Factory):
|
class SyncFactory(Factory):
|
||||||
def __init__(self, password='', motdFilePath=None, isolateRooms=False):
|
def __init__(self, password='', motdFilePath=None, isolateRooms=False):
|
||||||
print getMessage("welcome-server-notification").format(syncplay.version)
|
print getMessage("welcome-server-notification").format(syncplay.version)
|
||||||
if password:
|
if password:
|
||||||
password = hashlib.md5(password).hexdigest()
|
password = hashlib.md5(password).hexdigest()
|
||||||
self.password = password
|
self.password = password
|
||||||
|
# TODO: Make salt come from more reasonable place
|
||||||
|
self._salt = str(random.random())
|
||||||
self._motdFilePath = motdFilePath
|
self._motdFilePath = motdFilePath
|
||||||
if not isolateRooms:
|
if not isolateRooms:
|
||||||
self._roomManager = RoomManager()
|
self._roomManager = RoomManager()
|
||||||
@ -98,6 +106,19 @@ class SyncFactory(Factory):
|
|||||||
def getAllWatchersForUser(self, forUser):
|
def getAllWatchersForUser(self, forUser):
|
||||||
return self._roomManager.getAllWatchersForUser(forUser)
|
return self._roomManager.getAllWatchersForUser(forUser)
|
||||||
|
|
||||||
|
def authRoomController(self, watcher, password):
|
||||||
|
room = watcher.getRoom()
|
||||||
|
try:
|
||||||
|
success = RoomPasswordProvider.check(room.getName(), password, self._salt)
|
||||||
|
# TODO: Authenticate watcher to make changes in the room
|
||||||
|
watcher.sendControlledRoomAuthStatus(success)
|
||||||
|
except NotControlledRoom:
|
||||||
|
newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt)
|
||||||
|
watcher.sendNewControlledRoom(newName, password)
|
||||||
|
except ValueError:
|
||||||
|
watcher.sendControlledRoomAuthStatus(False)
|
||||||
|
|
||||||
|
|
||||||
class RoomManager(object):
|
class RoomManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._rooms = {}
|
self._rooms = {}
|
||||||
@ -277,6 +298,12 @@ class Watcher(object):
|
|||||||
def sendSetting(self, user, room, file_, event):
|
def sendSetting(self, user, room, file_, event):
|
||||||
self._connector.sendUserSetting(user, room, file_, event)
|
self._connector.sendUserSetting(user, room, file_, event)
|
||||||
|
|
||||||
|
def sendNewControlledRoom(self, roomName, password):
|
||||||
|
self._connector.sendNewControlledRoom(roomName, password)
|
||||||
|
|
||||||
|
def sendControlledRoomAuthStatus(self, success):
|
||||||
|
self._connector.sendControlledRoomAuthStatus(success)
|
||||||
|
|
||||||
def __lt__(self, b):
|
def __lt__(self, b):
|
||||||
if self.getPosition() is None or self._file is None:
|
if self.getPosition() is None or self._file is None:
|
||||||
return False
|
return False
|
||||||
@ -341,3 +368,33 @@ class ConfigurationGetter(object):
|
|||||||
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"))
|
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"))
|
||||||
self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument"))
|
self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument"))
|
||||||
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))
|
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))
|
||||||
|
|
||||||
|
|
||||||
|
class RoomPasswordProvider(object):
|
||||||
|
CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$")
|
||||||
|
PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check(roomName, password, salt):
|
||||||
|
if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName)
|
||||||
|
if not match:
|
||||||
|
raise NotControlledRoom()
|
||||||
|
roomHash = match.group(2)
|
||||||
|
computedHash = RoomPasswordProvider._computeRoomHash(match.group(1), password, salt)
|
||||||
|
return roomHash == computedHash
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getControlledRoomName(roomName, password, salt):
|
||||||
|
return "+" + roomName + ":" + RoomPasswordProvider._computeRoomHash(roomName, password, salt)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _computeRoomHash(roomName, password, salt):
|
||||||
|
salt = hashlib.sha256(salt).hexdigest()
|
||||||
|
provisionalHash = hashlib.sha256(roomName + salt).hexdigest()
|
||||||
|
return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper()
|
||||||
|
|
||||||
|
class NotControlledRoom(Exception):
|
||||||
|
pass
|
||||||
Loading…
x
Reference in New Issue
Block a user