def unregister(conn: ServerConnection, pwhash: str, wid: str) -> RetVal: '''Deletes the online account at the specified server.''' if wid and not utils.validate_uuid(wid): return RetVal(BadParameterValue, 'bad workspace id') request = { 'Action' : 'UNREGISTER', 'Data' : { 'Password-Hash' : pwhash } } if wid: request['Data']['Workspace-ID'] = wid status = conn.send_message(request) if status.error(): return status response = conn.read_response(server_response) if response['Code'] == 202: return RetVal() # This particular command is very simple: make a request, because the server will return # one of three possible types of responses: success, pending (for private/moderated # registration modes), or an error. In all of those cases there isn't anything else to do. return wrap_server_error(response)
def regcode(conn: ServerConnection, regid: str, code: str, pwhash: str, devid: str, devpair: EncryptionPair, domain: str) -> RetVal: '''Finishes registration of a workspace''' request = { 'Action':'REGCODE', 'Data':{ 'Reg-Code': code, 'Password-Hash':pwhash, 'Device-ID':devid, 'Device-Key':devpair.public.as_string() } } if domain: request['Data']['Domain'] = domain if utils.validate_uuid(regid): request['Data']['Workspace-ID'] = regid else: request['Data']['User-ID'] = regid status = conn.send_message(request) if status.error(): return status response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 201: return wrap_server_error(response) return RetVal()
def login(conn: ServerConnection, wid: str, serverkey: CryptoString) -> RetVal: '''Starts the login process by sending the requested workspace ID.''' if not utils.validate_uuid(wid): return RetVal(BadParameterValue) challenge = b85encode(secrets.token_bytes(32)) ekey = PublicKey(serverkey) status = ekey.encrypt(challenge) if status.error(): return status conn.send_message({ 'Action' : "LOGIN", 'Data' : { 'Workspace-ID' : wid, 'Login-Type' : 'PLAIN', 'Challenge' : status['data'] } }) response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 100: return wrap_server_error(response) if response['Data']['Response'] != challenge.decode(): return RetVal(ServerError, 'server failed to decrypt challenge') return RetVal()
def iscurrent(conn: ServerConnection, index: int, wid='') -> RetVal: '''Finds out if an entry index is current. If wid is empty, the index is checked for the organization.''' if wid and not utils.validate_uuid(wid): return RetVal(AnsBadRequest).set_value('status', 400) request = { 'Action' : 'ISCURRENT', 'Data' : { 'Index' : str(index) } } if wid: request['Data']['Workspace-ID'] = wid conn.send_message(request) response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 200: return wrap_server_error(response) if 'Is-Current' not in response['Data']: return RetVal(ServerError, 'server did not return an answer') return RetVal().set_value('iscurrent', bool(response['Data']['Is-Current'] == 'YES'))
def devkey(conn: ServerConnection, devid: str, oldpair: EncryptionPair, newpair: EncryptionPair): '''Replaces the specified device's key stored on the server''' if not utils.validate_uuid(devid): return RetVal(AnsBadRequest, 'Invalid device ID').set_value('status', 400) conn.send_message({ 'Action' : "DEVKEY", 'Data' : { 'Device-ID': devid, 'Old-Key': oldpair.public.as_string(), 'New-Key': newpair.public.as_string() } }) # Receive, decrypt, and return the server challenge response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 100: return wrap_server_error(response) if 'Challenge' not in response['Data'] or 'New-Challenge' not in response['Data']: return RetVal(ServerError, 'server did not return both device challenges') status = oldpair.decrypt(response['Data']['Challenge']) if status.error(): cancel(conn) return RetVal(DecryptionFailure, 'failed to decrypt device challenge for old key') request = { 'Action' : "DEVKEY", 'Data' : { 'Response' : status['data'] } } status = newpair.decrypt(response['Data']['New-Challenge']) if status.error(): cancel(conn) return RetVal(DecryptionFailure, 'failed to decrypt device challenge for new key') request['Data']['New-Response'] = status['data'] conn.send_message(request) response = conn.read_response(None) if response.error(): return response if response['Code'] == 200: return RetVal() return wrap_server_error(response)
def device(conn: ServerConnection, devid: str, devpair: EncryptionPair) -> RetVal: '''Completes the login process by submitting device ID and its session string.''' if not utils.validate_uuid(devid): return RetVal(AnsBadRequest, 'Invalid device ID').set_value('status', 400) conn.send_message({ 'Action' : "DEVICE", 'Data' : { 'Device-ID' : devid, 'Device-Key' : devpair.public.as_string() } }) # Receive, decrypt, and return the server challenge response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 100: return wrap_server_error(response) if 'Challenge' not in response['Data']: return RetVal(ServerError, 'server did not return a device challenge') status = devpair.decrypt(response['Data']['Challenge']) if status.error(): cancel(conn) return RetVal(DecryptionFailure, 'failed to decrypt device challenge') conn.send_message({ 'Action' : "DEVICE", 'Data' : { 'Device-ID' : devid, 'Device-Key' : devpair.public.as_string(), 'Response' : status['data'] } }) response = conn.read_response(None) if response.error(): return response if response['Code'] == 200: return RetVal() return wrap_server_error(response)
def password(conn: ServerConnection, wid: str, pwhash: str) -> RetVal: '''Continues the login process sending a password hash to the server.''' if not password or not utils.validate_uuid(wid): return RetVal(BadParameterValue) conn.send_message({ 'Action' : "PASSWORD", 'Data' : { 'Password-Hash' : pwhash } }) response = conn.read_response(server_response) if response.error(): return response if response['Code'] != 100: return wrap_server_error(response) return RetVal()
def setstatus(conn: ServerConnection, wid: str, status: str): '''Sets the activity status of the workspace specified. Requires admin privileges''' if status not in ['active', 'disabled', 'approved']: return RetVal(BadParameterValue, "status must be 'active','disabled', or 'approved'") if not utils.validate_uuid(wid): return RetVal(BadParameterValue, 'bad wid') conn.send_message({ 'Action' : 'SETSTATUS', 'Data' : { 'Workspace-ID': wid, 'Status': status } }) response = conn.read_response(server_response) if response['Code'] != 200: return wrap_server_error(response) return RetVal()
def is_valid(self) -> bool: '''Returns true if data stored in the profile object is valid''' if self.name and utils.validate_uuid(self.id): return True return False
def test_validate_uuid(): '''Tests utils.validate_uuid''' assert utils.validate_uuid( '5a56260b-aa5c-4013-9217-a78f094432c3'), 'Failed to validate good ID' assert not utils.validate_uuid( '5a56260b-c-4013-9217-a78f094432c3'), 'Failed to reject bad ID'