Created protocol exchanges for creating controlled rooms

This commit is contained in:
Uriziel 2014-10-05 11:08:28 +02:00
parent da633e2883
commit cdce17b10b
3 changed files with 105 additions and 23 deletions

View File

@ -417,38 +417,30 @@ class SyncplayClient(object):
def createControlledRoom(self):
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._protocol.requestControlledRoom(controlPassword)
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.setRoom(roomName)
self.sendRoom()
self._protocol.requestControlledRoom(controlPassword)
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):
# 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._protocol.requestControlledRoom(controlPassword)
def controllerIdentificationError(self, errormsg):
# NOTE (Client): Triggered by protocol handling identiedAsController, e.g. on server response or not supported error
# 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 controllerIdentificationError(self):
self.ui.showErrorMessage("Failed to identify as a room controller.")
def notControllerError(self, errormsg):
# NOTE (Client): Trigger when client gets a "not controller" error from server (e.g. due to illegal pauses, unpauses and seeks)
# NOTE (Server): Give "not controller" error when users try to perform illegal pause, unpause or seek
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.")
def controllerIdentificationSuccess(self):
# TODO: More UI stuff
self.ui.showErrorMessage("Authenticated as a room controller")
# 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):
def __init__(self, player, userlist, ui):

View File

@ -114,13 +114,21 @@ class SyncClientProtocol(JSONCommandProtocol):
self._client.userlist.modUser(username, room, file_)
def handleSet(self, settings):
for set_ in settings.iteritems():
command = set_[0]
for (command, values) in settings.iteritems():
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)
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):
self.sendMessage({"Set": setting})
@ -210,8 +218,15 @@ class SyncClientProtocol(JSONCommandProtocol):
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
self.sendMessage({"State": state})
def requestControlledRoom(self, password):
self.sendSet({
"controllerAuth": {
"password": password
}
})
def handleError(self, error):
self.dropWithError(error["message"]) # TODO: more processing and fallbacking
self.dropWithError(error["message"])
def sendError(self, message):
self.sendMessage({"Error": {"message": message}})
@ -311,10 +326,28 @@ class SyncServerProtocol(JSONCommandProtocol):
self._factory.setWatcherRoom(self._watcher, roomName)
elif command == "file":
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):
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):
room = {"name": room.getName()}
user = {}

View File

@ -1,4 +1,6 @@
import hashlib
import random
import re
from twisted.internet import task, reactor
from twisted.internet.protocol import Factory
import syncplay
@ -11,12 +13,18 @@ import os
from string import Template
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):
def __init__(self, password='', motdFilePath=None, isolateRooms=False):
print getMessage("welcome-server-notification").format(syncplay.version)
if password:
password = hashlib.md5(password).hexdigest()
self.password = password
# TODO: Make salt come from more reasonable place
self._salt = str(random.random())
self._motdFilePath = motdFilePath
if not isolateRooms:
self._roomManager = RoomManager()
@ -98,6 +106,19 @@ class SyncFactory(Factory):
def getAllWatchersForUser(self, 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):
def __init__(self):
self._rooms = {}
@ -277,6 +298,12 @@ class Watcher(object):
def sendSetting(self, 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):
if self.getPosition() is None or self._file is None:
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('--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"))
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