def handle_fetch(host_obj, connection, address, msg_obj): _log = get_mylog() db = host_obj.get_instance().make_db_session() _log.debug('handle_fetch 1') # the id's are swapped because they are named from the origin host's POV. other_id = msg_obj.my_id local_id = msg_obj.other_id cloudname = msg_obj.cname cloud_uname = msg_obj.cloud_uname requested_root = msg_obj.root # Go to the remote, ask them if the fetching host is ok'd to be here. rd = verify_host(db, cloud_uname, cloudname, local_id, other_id) if not rd.success: err = HostVerifyHostFailureMessage(rd.data) send_error_and_close(err, connection) return _log.debug('handle_fetch 2') matching_mirror = rd.data their_ip = address[0] _log.debug('The connected host is via IP="{}"'.format(their_ip)) send_tree(db, other_id, matching_mirror, requested_root, connection) _log.debug('Bottom of handle_fetch') _log.debug('handle_fetch 3')
def recv_file_transfer(host_obj, msg, cloud, socket_conn, db, is_client): # type: (HostController, BaseMessage, Cloud, AbstractConnection, SimpleDB, bool) -> ResultAndData _log = get_mylog() msg_file_isdir = msg.isdir msg_file_size = msg.fsize msg_rel_path = msg.fpath rel_path = RelativePath() rd = rel_path.from_relative(msg_rel_path) if not rd.success: msg = '{} is not a valid cloud path'.format(msg_rel_path) err = InvalidStateMessage(msg) _log.debug(err) send_error_and_close(err, socket_conn) return rd full_path = rel_path.to_absolute(cloud.root_directory) rd = do_recv_file_transfer(host_obj, cloud, socket_conn, rel_path, msg_file_isdir, msg_file_size) if rd.success: # if it wasn't a client file transfer, update our node. # We don't want to see that it was updated and send updates to the other hosts. # else (this came from a client): # We DO want to tell other mirrors about this change, so don't change the DB> # The local thread will find the change and alert the other mirrors. # if not is_client: updated_node = cloud.create_or_update_node(rel_path.to_string(), db) if updated_node is not None: old_modified_on = updated_node.last_modified updated_node.last_modified = datetime.utcfromtimestamp(os.path.getmtime(full_path)) # mylog('update mtime {}=>{}'.format(old_modified_on, updated_node.last_modified)) db.session.commit()
def do_client_list_files(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() cloudname = cloud.name session_id = client.uuid if client is not None else None client_uid = client.user_id if client else PUBLIC_USER_ID private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) host_obj.log_client(client, 'ls', cloud, None, 'error') send_error_and_close(err, connection) return # todo: I believe this should be more complicated. # Say a person has permission to read some children of the directory, # but not the directory itself. ls returns ACCESS_ERROR currently. # Perhaps it should return the children it can access? # though, is this process recursive? What if I ls "/", but only have access to "/foo/bar/..."? rel_path = RelativePath() rd = rel_path.from_relative(msg_obj.fpath) if not rd.success: msg = '{} is not a valid cloud path'.format(msg_obj.fpath) err = InvalidStateMessage(msg) _log.debug(err) send_error_and_close(err, connection) host_obj.log_client(client, 'ls', cloud, rel_path, 'error') return rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, READ_ACCESS) if rd.success: full_path = rel_path.to_absolute(cloud.root_directory) if not os.path.exists(full_path): resp = FileDoesNotExistErrorMessage() host_obj.log_client(client, 'ls', cloud, rel_path, 'error') elif not os.path.isdir(full_path): mylog( 'Responding to ClientListFiles with error - {} is a file, not dir.' .format(rel_path.to_string())) resp = FileIsNotDirErrorMessage() host_obj.log_client(client, 'ls', cloud, rel_path, 'error') else: mylog('Responding successfully to ClientListFiles') resp = ListFilesResponseMessage(cloudname, session_id, rel_path.to_string()) resp.stat = make_stat_dict(rel_path, private_data, cloud, client_uid) resp.ls = make_ls_array(rel_path, private_data, cloud, client_uid) host_obj.log_client(client, 'ls', cloud, rel_path, 'success') connection.send_obj(resp) else: # the access check will send error host_obj.log_client(client, 'ls', cloud, rel_path, 'error') pass
def do_client_get_permissions(host_obj, connection, address, msg_obj, client, cloud): # type: (HostController, AbstractConnection, object, ClientGetPermissionsMessage, Client, Cloud) -> object _log = get_mylog() session_id = client.uuid if client else None client_uid = client.user_id if client else PUBLIC_USER_ID fpath = msg_obj.path rel_path = RelativePath() rd = rel_path.from_relative(fpath) if not rd.success: msg = '{} is not a valid cloud path'.format(fpath) err = InvalidStateMessage(msg) _log.debug(err) send_error_and_close(err, connection) return # private_data = host_obj.get_private_data(cloud) # if private_data is None: # msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' # err = InvalidStateMessage(msg) # mylog(err.message, '31') # send_error_and_close(err, connection) # return rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, NO_ACCESS) if not rd.success: # conn was closed by client_access_check_or_close return rd perms = rd.data _log.debug('{} has {} permission for {}'.format(client_uid, perms, rel_path.to_string())) resp = ClientGetPermissionsResponseMessage(perms) connection.send_obj(resp)
def do_client_remove_dir(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID session_id = client.uuid if client else None fpath = msg_obj.path recurse = msg_obj.recursive rel_path = RelativePath() rd = rel_path.from_relative(fpath) if not rd.success: msg = '{} is not a valid cloud path'.format(fpath) err = InvalidStateMessage(msg) send_error_and_close(err, connection) host_obj.log_client(client, 'rmdir', cloud, rel_path, 'error') return Error(err) # TODO Does the client need access to write the file, or the parent directory? # Technically they're modifying the parent dir rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, WRITE_ACCESS) if not rd.success: # conn was closed by client_access_check_or_close return full_path = rel_path.to_absolute(cloud.root_directory) if not os.path.exists(full_path): resp = FileDoesNotExistErrorMessage() host_obj.log_client(client, 'rmdir', cloud, rel_path, 'error') elif not os.path.isdir(full_path): resp = FileIsDirErrorMessage() host_obj.log_client(client, 'rmdir', cloud, rel_path, 'error') else: subdirs = os.listdir(full_path) if len(subdirs) > 0 and not recurse: resp = DirIsNotEmptyMessage() elif len(subdirs) > 0 and recurse: try: shutil.rmtree(full_path) resp = ClientDeleteResponseMessage() except OSError as e: msg = 'error deleting {}, "{}"', rel_path.to_string( ), e.message _log.error(msg) resp = UnknownIoErrorMessage(msg) else: # len subdirs == 0 try: os.rmdir(full_path) resp = ClientDeleteResponseMessage() except IOError as e: msg = 'error deleting {}, "{}"', rel_path.to_string( ), e.message _log.error(msg) resp = UnknownIoErrorMessage(msg) host_obj.log_client( client, 'rmdir', cloud, rel_path, 'success' if resp.type == CLIENT_DELETE_RESPONSE else 'error') connection.send_obj(resp)
def do_client_create_link(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID session_id = client.uuid if client else None rel_path = RelativePath() rel_path.from_relative(msg_obj.path) _log.debug('Creating a link to {}'.format(rel_path.to_string())) private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'link', cloud, rel_path, 'error') send_error_and_close(err, connection) return Error(msg) # how do we want to gate this? Owners only? or sharers only? rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, SHARE_ACCESS) if not rd.success: # conn was closed by client_access_check_or_close return rd # We'll ask the remote to give us a link id remote_req = HostReserveLinkRequestMessage(cloud.uname(), cloud.cname()) rd = cloud.get_remote_conn() if not rd.success: msg = 'Failed to connect to remote for {}: {}'.format( cloud.full_name(), rd.data) _log.error(msg) host_obj.log_client(client, 'link', cloud, rel_path, 'error') connection.send_obj(InvalidStateMessage(msg)) connection.close() return Error(msg) remote_conn = rd.data _log.debug('Got remote connection') remote_conn.send_obj(remote_req) remote_resp = remote_conn.recv_obj() if remote_resp.type is not HOST_RESERVE_LINK_RESPONSE: msg = 'Remote failed to reserve link for us' _log.error(msg) host_obj.log_client(client, 'link', cloud, rel_path, 'error') connection.send_obj(InvalidStateMessage(msg)) connection.close() return Error(msg) _log.debug('Got link from remote') link_str = remote_resp.link_string # Create the link in the private data private_data.add_link(rel_path, link_str) _log.debug('Committing .nebs to add link {}->{}'.format( link_str, rel_path.to_string())) private_data.commit() resp = ClientCreateLinkResponseMessage(link_str) connection.send_obj(resp) host_obj.log_client(client, 'link', cloud, rel_path, 'success')
def do_client_stat_files(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() cloudname = cloud.name session_id = client.uuid if client is not None else None client_uid = client.user_id if client else PUBLIC_USER_ID private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) host_obj.log_client(client, 'stat', cloud, None, 'error') send_error_and_close(err, connection) return Error(err) rel_path = RelativePath() rd = rel_path.from_relative(msg_obj.fpath) if not rd.success: msg = '{} is not a valid cloud path'.format(msg_obj.fpath) err = InvalidStateMessage(msg) _log.debug(err) send_error_and_close(err, connection) host_obj.log_client(client, 'stat', cloud, rel_path, 'error') return Error(err) rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, READ_ACCESS) if rd.success: full_path = rel_path.to_absolute(cloud.root_directory) if not os.path.exists(full_path): resp = FileDoesNotExistErrorMessage() host_obj.log_client(client, 'stat', cloud, rel_path, 'error') # elif not os.path.isdir(full_path): # mylog('Responding to ClientListFiles with error - {} is a file, not dir.'.format(rel_path.to_string())) # resp = FileIsNotDirErrorMessage() # host_obj.log_client(client, 'ls', cloud, rel_path, 'error') else: mylog('Responding successfully to ClientStatFile') resp = StatFileResponseMessage(cloudname, session_id, rel_path.to_string()) resp.stat = make_stat_dict(rel_path, private_data, cloud, client_uid) # resp.ls = make_ls_array(rel_path, private_data, cloud, client_uid) host_obj.log_client(client, 'stat', cloud, rel_path, 'success') connection.send_obj(resp) return ResultAndData(resp.type == STAT_FILE_RESPONSE, resp) else: # the access check will send error host_obj.log_client(client, 'stat', cloud, rel_path, 'error') return rd
def do_client_read_link(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID session_id = client.uuid if client else None link_str = msg_obj.link_string private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'read-link', cloud, link_str, 'error') send_error_and_close(err, connection) return Error(msg) # TODO: how do we handle seperate link permissions here? # We're just translating it straight to a normal readfile message rd = host_obj.client_link_access_check_or_close(connection, session_id, cloud, link_str, READ_ACCESS) if not rd.success: # conn was closed by client_access_check_or_close return # get the path rom the link rel_path = RelativePath() path = private_data.get_path_from_link(link_str) if path is None: msg = 'The link {} is not valid for this cloud'.format(link_str) err = LinkDoesNotExistMessage(msg) _log.error(err.message) host_obj.log_client(client, 'read-link', cloud, link_str, 'error') send_error_and_close(err, connection) return rel_path.from_relative(path) # construct a ReadFile message, using the path from the link translated = ReadFileRequestMessage(session_id, cloud.uname(), cloud.cname(), rel_path.to_string()) return do_client_read_file(host_obj, connection, address, translated, client, cloud, lookup_permissions=False)
def do_client_get_shared_paths(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') send_error_and_close(err, connection) return resp = ClientGetSharedPathsResponseMessage( private_data.get_user_permissions(user_id)) connection.send_obj(resp)
def do_client_add_owner(host_obj, connection, address, msg_obj, client, cloud): cloudname = cloud.cname() session_id = client.uuid if client else None client_uid = client.user_id if client else PUBLIC_USER_ID new_owner_id = msg_obj.new_user_id private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'add-owner', cloud, None, 'error') send_error_and_close(err, connection) return if new_owner_id == PUBLIC_USER_ID: msg = 'The public can\'t be a owner of a cloud' err = AddOwnerFailureMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'add-owner', cloud, None, 'error') send_error_and_close(err, connection) return if not private_data.has_owner(client_uid): msg = 'User [{}] is not an owner of the cloud "{}"'.format( client_uid, cloudname) err = AddOwnerFailureMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'add-owner', cloud, None, 'error') send_error_and_close(err, connection) return rd = cloud.get_remote_conn() if rd.success: remote_conn = rd.data request = msg_obj # todo:24 too lazy to do now remote_conn.send_obj(request) response = remote_conn.recv_obj() if response.type == ADD_OWNER_SUCCESS: rd = Success() else: rd = Error(response.message) if not rd.success: msg = 'failed to validate the ADD_OWNER request with the remote, msg={}'.format( rd.data) err = AddOwnerFailureMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'add-owner', cloud, None, 'error') send_error_and_close(err, connection) else: private_data.add_owner(new_owner_id) private_data.commit() mylog('Added user [{}] to the owners of {}'.format( new_owner_id, cloudname)) # todo:15 host_obj.log_client(client, 'add-owner', cloud, None, 'success') response = AddOwnerSuccessMessage(session_id, new_owner_id, cloud.uname(), cloudname) connection.send_obj(response)
def do_client_set_link_permissions(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID session_id = client.uuid if client else None link_str = msg_obj.link_string permissions = msg_obj.permissions private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'chmod-link', cloud, RelativeLink(link_str), 'error') send_error_and_close(err, connection) return Error(msg) # get the path from the link rel_path = RelativePath() path = private_data.get_path_from_link(link_str) if path is None: msg = 'The link {} is not valid for this cloud'.format(link_str) err = LinkDoesNotExistMessage(msg) _log.error(err.message) host_obj.log_client(client, 'chmod-link', cloud, RelativeLink(link_str), 'error') send_error_and_close(err, connection) return rel_path.from_relative(path) # Using the actual file, check if the client has access to share the file. rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, SHARE_ACCESS) if not rd.success: return rd rd = private_data.set_link_permissions(link_str, permissions) if rd.success: private_data.commit() response = ClientSetLinkPermissionsSuccessMessage( ) if rd.success else LinkDoesNotExistMessage() host_obj.log_client(client, 'chmod-link', cloud, RelativeLink(link_str), 'success' if rd.success else 'error') connection.send_obj(response)
def do_client_get_link_permissions(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() user_id = client.user_id if client else PUBLIC_USER_ID session_id = client.uuid if client else None link_str = msg_obj.link_string private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') # host_obj.log_client(client, 'chown+link', cloud, RelativeLink(link_str), 'error') send_error_and_close(err, connection) return Error(msg) # get the path from the link rel_path = RelativePath() path = private_data.get_path_from_link(link_str) if path is None: msg = 'The link {} is not valid for this cloud'.format(link_str) err = LinkDoesNotExistMessage(msg) _log.error(err.message) # host_obj.log_client(client, 'chown+link', cloud, RelativeLink(link_str), 'error') send_error_and_close(err, connection) return rel_path.from_relative(path) # TODO: Get the permissions of the backing file too, and OR them with the permissions on the link. rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, NO_ACCESS) if not rd.success: return rd file_perms = rd.data rd = private_data.get_link_full_permissions(link_str) if rd.success: link_perms = rd.data[0] users = rd.data[1] response = ClientGetLinkPermissionsResponseMessage( link_perms | file_perms, users) else: response = LinkDoesNotExistMessage() # host_obj.log_client(client, 'chown+link', cloud, RelativeLink(link_str), 'success' if rd.success else 'error') connection.send_obj(response)
def client_link_wrapper( host_obj, connection, address, msg_obj, callback # type: (HostController, AbstractConnection, object, BaseMessage, Client, Cloud) -> ResultAndData ): # type: (HostController, AbstractConnection, object, BaseMessage, ...) -> None session_id = msg_obj.sid link_id = msg_obj.link_string db = host_obj.get_db() _log = get_mylog() # Search all of the private datas for one that has the given link. clouds = host_obj.find_link_clouds(link_id) if len(clouds) < 1: err = '{} is not a link on this device'.format(link_id) _log.debug(err) msg = InvalidStateMessage(err) connection.send_obj(msg) connection.close() cloud = clouds[0] # validate that cloud as the one the client was prepped to find # TODO: this is very similar to the body of client_message_wrapper rd = validate_or_get_client_session(db, session_id, cloud.uname(), cloud.cname()) if not rd.success: response = ClientAuthErrorMessage(rd.data) send_error_and_close(response, connection) return client = rd.data # None is the public user if client is not None: # refresh the session: rd = cloud.get_remote_conn() if rd.success: refresh = ClientSessionRefreshMessage(session_id) rd.data.send_obj(refresh) rd.data.close() else: mylog('Failed to refresh client session.') return callback(host_obj, connection, address, msg_obj, client, cloud)
def client_message_wrapper( host_obj, connection, address, msg_obj, callback # type: (HostController, AbstractConnection, object, BaseMessage, Client, Cloud) -> ResultAndData ): # type: (HostController, AbstractConnection, object, BaseMessage, ...) -> None session_id = msg_obj.sid cloudname = msg_obj.cname cloud_uname = msg_obj.cloud_uname # todo:15 db = host_obj.get_db() rd = validate_or_get_client_session(db, session_id, cloud_uname, cloudname) if not rd.success: response = ClientAuthErrorMessage(rd.data) send_error_and_close(response, connection) return else: mylog('valid client session') client = rd.data cloud = None if client is None: # The client is the public client. Callers should be prepared to handle # the public client case. clouds = get_clouds_by_name(db, cloud_uname, cloudname) if len(clouds) > 0: cloud = clouds[0] else: err = InvalidStateMessage( 'Public client came for {}/{}, but was unable to find it.'. format(cloud_uname, cloudname)) mylog(err.message, '31') send_error_and_close(err, connection) return else: cloud = client.cloud mylog('client.cloud={}'.format(client.cloud.full_name())) if cloud is None: # todo:17 The cloud could have been deleted while a client had it err = InvalidStateMessage( 'Somehow the client object did not have ' 'a cloud associated with it.') mylog(err.message, '31') send_error_and_close(err, connection) return # refresh the session: rd = cloud.get_remote_conn() if rd.success: refresh = ClientSessionRefreshMessage(session_id) rd.data.send_obj(refresh) rd.data.close() else: mylog('Failed to refresh client session.') return callback(host_obj, connection, address, msg_obj, client, cloud)
def do_client_add_contributor(host_obj, connection, address, msg_obj, client, cloud): _log = get_mylog() cloudname = cloud.name session_id = client.uuid if client else None new_user_id = msg_obj.new_user_id fpath = msg_obj.fpath new_permissions = msg_obj.permissions rel_path = RelativePath() rd = rel_path.from_relative(fpath) if not rd.success: msg = '{} is not a valid cloud path'.format(fpath) err = InvalidStateMessage(msg) _log.debug(err) host_obj.log_client(client, 'share', cloud, rel_path, 'error') send_error_and_close(err, connection) return private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) mylog(err.message, '31') send_error_and_close(err, connection) host_obj.log_client(client, 'share', cloud, rel_path, 'error') return rd = host_obj.client_access_check_or_close(connection, session_id, cloud, rel_path, SHARE_ACCESS) if not rd.success: # conn was closed by client_access_check_or_close return perms = rd.data if not permissions_are_sufficient(perms, new_permissions): msg = 'Client doesn\'t have permission to give to other user' err = AddContributorFailureMessage(msg) mylog(err.message, '31') send_error_and_close(err, connection) host_obj.log_client(client, 'share', cloud, rel_path, 'error') return mylog('Client has sharing permission') rd = cloud.get_remote_conn() if rd.success: remote_conn = rd.data request = AddContributorMessage(cloud.my_id_from_remote, new_user_id, cloud.uname(), cloudname) # todo:24 too lazy to do now remote_conn.send_obj(request) response = remote_conn.recv_obj() if response.type == ADD_CONTRIBUTOR_SUCCESS: rd = Success() else: rd = Error(response.message) mylog('completed talking to remote, {}'.format(rd)) if not rd.success: msg = 'failed to validate the ADD_ADD_CONTRIBUTOR request with the remote, msg={}'.format( rd.data) err = AddContributorFailureMessage(msg) mylog(err.message, '31') host_obj.log_client(client, 'share', cloud, rel_path, 'error') send_error_and_close(err, connection) else: # PrivateData will be able to handle the public_user_id private_data.add_user_permission(new_user_id, rel_path, new_permissions) private_data.commit() mylog('Added permission {} for user [{}] to file {}:{}'.format( new_permissions, new_user_id, cloudname, fpath)) host_obj.log_client(client, 'share', cloud, rel_path, 'success') response = AddContributorSuccessMessage(new_user_id, cloud.uname(), cloudname) connection.send_obj(response)
def do_recv_file_from_client(host_obj, connection, address, msg_obj, client, cloud): db = host_obj.get_db() _log = get_mylog() client_uuid = client.uuid if client is not None else None client_uid = client.user_id if client else PUBLIC_USER_ID file_isdir = msg_obj.isdir file_size = msg_obj.fsize file_path = msg_obj.fpath # todo: maybe add a quick response to tell the client the transfer is okay. # Originally, there was a ClientFilePut followed by a ClientFileTransfer. rel_path = RelativePath() rd = rel_path.from_relative(file_path) if not rd.success: msg = '{} is not a valid cloud path'.format(file_path) err = InvalidStateMessage(msg) _log.debug(err) host_obj.log_client(client, 'write', cloud, rel_path, 'error') send_error_and_close(err, connection) return Error(err) # make sure that it's not the private data file # make sure we have appropriate permissions # if it's a existing file, we'll need to make sure we have write access on that file # otherwise, the next existing parent must have either write or append permission # if we're creating a new file, and the parent only has append permission, then we should # SHOULD WE? make the child write access? # or should it be the responsibility of the client to also set that permission? # The user will likely not be able to modify the permissions of the cloud, # so they likely wont be able to append the file then chmod the file to # have write-access. However, there'd also be no way for the owner (of the # append-only dir) to pre-authorize any changes in ownership. # lets give them write permissions. Yes. Lets. # but then they could append a new dir, and then that would have write # access, and then they could go and write whatever the hell they want # But I guess that's not technically any worse than letting them append as # many children as they want. full_path = rel_path.to_absolute(cloud.root_directory) if host_obj.is_private_data_file(full_path, cloud): msg = 'Clients are not allowed to modify the {} file'.format( rel_path.to_string()) err = SystemFileWriteErrorMessage(msg) _log.debug(err) host_obj.log_client(client, 'write', cloud, rel_path, 'error') send_error_and_close(err, connection) return Error(err) appending_new = False if os.path.exists(full_path): rd = host_obj.client_access_check_or_close(connection, client_uuid, cloud, rel_path, WRITE_ACCESS) else: # get_client_permissions will call private_data.get_permissions, which # will traverse top-down to build the users's permissions. If there # are intermediate paths that dont exist, but at least one parent has # append access, get_client_permisssions will know permissions = host_obj.get_client_permissions(client_uuid, cloud, rel_path) if not permissions_are_sufficient(permissions, WRITE_ACCESS): if not permissions_are_sufficient(permissions, APPEND_ACCESS): msg = 'Session does not have sufficient permission to access <{}>'.format( rel_path.to_string()) _log.debug(msg) err = InvalidPermissionsMessage(msg) host_obj.log_client(client, 'write', cloud, rel_path, 'error') send_error_and_close(err, connection) return Error(err) else: # user does have append permission appending_new = True rd = Success() else: #user does have write access, this is good pass rd = Success() if rd.success: rd = do_recv_file_transfer(host_obj, cloud, connection, rel_path, file_isdir, file_size) if rd.success and appending_new: private_data = host_obj.get_private_data(cloud) if private_data is None: msg = 'Somehow the cloud doesn\'t have a privatedata associated with it' err = InvalidStateMessage(msg) _log.debug(err) host_obj.log_client(client, 'write', cloud, rel_path, 'error') send_error_and_close(err, connection) return Error(err) private_data.add_user_permission(client_uid, rel_path, WRITE_ACCESS) private_data.commit() _log.debug( 'Added permission {} for user [{}] to file {}:{} while appending_new' .format(WRITE_ACCESS, client_uid, cloud.cname(), rel_path.to_string())) host_obj.log_client(client, 'write', cloud, rel_path, 'success' if rd.success else 'error') connection.send_obj(rd.data)
def do_client_read_file(host_obj, connection, address, msg_obj, client, cloud, lookup_permissions=True): db = host_obj.get_db() _log = get_mylog() client_uuid = client.uuid if client is not None else None # cloud = client.cloud cloudname = cloud.name requested_file = msg_obj.fpath rel_path = RelativePath() rd = rel_path.from_relative(requested_file) if not rd.success: msg = '{} is not a valid cloud path'.format(requested_file) err = InvalidStateMessage(msg) _log.debug(err) send_error_and_close(err, connection) host_obj.log_client(client, 'read', cloud, rel_path, 'error') return requesting_all = requested_file == '/' filepath = None # if the root is '/', send all of the children of the root if requesting_all: filepath = cloud.root_directory # todo: if they're requesting all, it's definitely a dir, # which is an error else: filepath = rel_path.to_absolute(cloud.root_directory) try: req_file_stat = os.stat(filepath) except Exception: err_msg = FileDoesNotExistErrorMessage() connection.send_obj(err_msg) host_obj.log_client(client, 'read', cloud, rel_path, 'error') # connection.close() return if lookup_permissions: rd = host_obj.client_access_check_or_close(connection, client_uuid, cloud, rel_path, READ_ACCESS) if not rd.success: host_obj.log_client(client, 'read', cloud, rel_path, 'error') return req_file_is_dir = S_ISDIR(req_file_stat.st_mode) if req_file_is_dir: err_msg = FileIsDirErrorMessage() connection.send_obj(err_msg) host_obj.log_client(client, 'read', cloud, rel_path, 'error') # connection.close() else: # send RFP - ReadFileResponse req_file_size = req_file_stat.st_size requested_file = open(filepath, 'rb') response = ReadFileResponseMessage(client_uuid, rel_path.to_string(), req_file_size) connection.send_obj(response) mylog('sent RFRp:{}, now sending file bytes'.format( response.serialize())) l = 1 total_len = 0 num_MB = int(math.floor(req_file_size / (1024 * 1024))) transfer_size = 1024 + (10 * 1024 * num_MB) num_transfers = 0 # send file bytes while l > 0: new_data = requested_file.read(transfer_size) sent_len = connection.send_next_data(new_data) l = sent_len total_len += sent_len num_transfers += 1 if (num_transfers % 127 == 1) and num_transfers >= 1: mylog('sent {} blobs of <{}> ({}/{}B total)'.format( num_transfers, filepath, total_len, req_file_size)) time.sleep(.1) mylog('(RFQ)[{}]Sent <{}> data to [{}]'.format(cloud.my_id_from_remote, filepath, client.uuid)) requested_file.close() host_obj.log_client(client, 'read', cloud, rel_path, 'success') mylog('[{}]bottom of handle_read_file_request(...,{})'.format( client.uuid, msg_obj))