def _check_accepted_state(self, accepted_state, storage_content): """ If there was an accepted_state, checks if the declared state is coherent with the accepted one. The server cannot declare same basis with different content or viceversa. @param accepted_state: an accepted state, None if not state was accepted or a string as "server_basis<space>get_hash(declared_content)" @param storage_content: list of files declared from server """ declared_content = get_hash( remove_lmtime_from_filelist(storage_content)) if accepted_state is not None: accepted_basis, accepted_content = accepted_state.split() same_basis = (accepted_basis == self.server_basis) same_content = (accepted_content == declared_content) if (same_basis and not same_content)\ or (same_content and not same_basis): raise HashMismatchException( 'ERROR! Basis mismatch Server declare inconsistent ServerBasis and StorageContent' )
def _catch_wrong_states(self, storage_content, accepted_state, out_of_sync): """Perform basic integrity checks on the basis/filelist sent by the server. The (basis, filelist) pair can be one of those that we already know: the trusted, the candidate, the accepted. In such case there is nothing new to synchronize. If both the elements of the pair have changed then we have to ask the user for confirmation. If only one has changed then we can tell for sure that something is wrong about the integrity of this synchronization. @param storage_content: Filelist of the remote storage. @param accepted_state: A (basis, filelist_hash) pair that we accepted on last synchronization, if any. @param out_of_sync: Boolean flag telling whether there are remote modification to replicate on the local data. """ declared_content = get_hash( remove_lmtime_from_filelist(storage_content)) current_state = "%s %s" % (self.server_basis, declared_content) self.logger.debug('storage_content = %s', storage_content) self.logger.debug('server_state = %s', current_state) self.logger.debug('accepted_state = %s', accepted_state) self.logger.debug('out_of_sync = %s', out_of_sync) if out_of_sync: if self.server_basis == self.client_basis: raise HashMismatchException( 'ERROR! Something to sync with same basis') else: self._check_accepted_state(accepted_state, storage_content) else: if self.server_basis == self.client_basis: accepted_state = None self._check_accepted_state(accepted_state, storage_content) if accepted_state is not None: accepted_basis, _ = accepted_state.split() if self.candidate_basis is None: check_candidate_basis = True else: check_candidate_basis = (self.server_basis != self.candidate_basis) if (accepted_state is None or self.server_basis != accepted_basis) \ and self.server_basis != self.client_basis \ and check_candidate_basis: raise HashMismatchException( 'ERROR! Basis mismatch but nothing to sync') return current_state
def _catch_wrong_states(self, storage_content, accepted_state, out_of_sync): """Perform basic integrity checks on the basis/filelist sent by the server. The (basis, filelist) pair can be one of those that we already know: the trusted, the candidate, the accepted. In such case there is nothing new to synchronize. If both the elements of the pair have changed then we have to ask the user for confirmation. If only one has changed then we can tell for sure that something is wrong about the integrity of this synchronization. @param storage_content: Filelist of the remote storage. @param accepted_state: A (basis, filelist_hash) pair that we accepted on last synchronization, if any. @param out_of_sync: Boolean flag telling whether there are remote modification to replicate on the local data. """ declared_content = get_hash(storage_content) current_state = "%s %s" % (self.server_basis, declared_content) self.logger.debug('storage_content = %s', storage_content) self.logger.debug('current_state = %s', current_state) self.logger.debug('accepted_state = %s', accepted_state) self.logger.debug('out_of_sync = %s', out_of_sync) if out_of_sync: if self.server_basis == self.client_basis: raise HashMismatchException('ERROR! Something to sync with same basis') else: self._check_accepted_state(accepted_state, storage_content) else: if self.server_basis == self.client_basis: accepted_state = None self._check_accepted_state(accepted_state, storage_content) if accepted_state is not None: accepted_basis, _ = accepted_state.split() if self.candidate_basis is None: check_candidate_basis = True else: check_candidate_basis = (self.server_basis != self.candidate_basis) if (accepted_state is None or self.server_basis != accepted_basis) \ and self.server_basis != self.client_basis \ and check_candidate_basis: raise HashMismatchException('ERROR! Basis mismatch but nothing to sync') return current_state
def _check_accepted_state(self, accepted_state, storage_content): """ If there was an accepted_state, checks if the declared state is coherent with the accepted one. The server cannot declare same basis with different content or viceversa. @param accepted_state: an accepted state, None if not state was accepted or a string as "server_basis<space>get_hash(declared_content)" @param storage_content: list of files declared from server """ declared_content = get_hash(storage_content) if accepted_state is not None: accepted_basis, accepted_content = accepted_state.split() same_basis = (accepted_basis == self.server_basis) same_content = (accepted_content == declared_content) if (same_basis and not same_content)\ or (same_content and not same_basis): raise HashMismatchException('ERROR! Basis mismatch Server declare inconsistent ServerBasis and StorageContent')
def _check_hash_mismatch(self): """Handles the hash mismatch, if any. An "hash mismatch" means that the content on the storage is different from what we remember from our last connection. We have to ask the user to tell if such modifications are acceptable (meaning that the user himself did them with another client) or if it's the result of malicious data tampering. The user will match both the hash (together with its visual representation, the "robohash") and the list of remote modification to replicate on the local data to make a decision. @return Boolean telling whether it's OK to proceed with the synchronization or not. """ # Collect the data resulting from the diff algorithm diff = self._context.startup_synchronization content_to_download = sorted(diff.content_to_download) content_to_delete_locally = sorted(diff.content_to_delete_locally) content_to_delete_locally.reverse() edit_conflicts = diff.edit_conflicts deletion_conflicts = diff.deletion_conflicts # Any pathname to synchronize? something_to_sync = len(content_to_download) > 0 something_to_sync |= len(content_to_delete_locally) > 0 something_to_sync |= len(deletion_conflicts) > 0 # Any modification to the storage cache to do? storage_cache_needs_update = len(diff.remote_deletions) > 0 storage_cache_needs_update |= len(diff.ignored_conflicts) > 0 # Anything the user should be notified of by opening the dialog? out_of_sync = something_to_sync or storage_cache_needs_update remote_sizes = self._context.startup_synchronization.remote_size local_sizes = self._context.startup_synchronization.local_size accepted_state = self._context.metadataDB.try_get( metadata.LASTACCEPTEDSTATEKEY) # if there is an accepted basis but it is the same of the current basis # and there is nothing to sync, maybe the client was crashed before # removing it from storage_cache if accepted_state is not None: accepted_basis, _ = accepted_state.split() if not out_of_sync and self.client_basis == accepted_basis: self._context.metadataDB.delete_key(metadata.LASTACCEPTEDSTATEKEY) accepted_state = None if self.client_basis is not None: # First Start, I cannot check anything current_state = self._catch_wrong_states(self.storage_content, accepted_state, out_of_sync) if self.client_basis is None: client_server_differ = True else: client_server_differ = (self.server_basis != self.client_basis) if self.candidate_basis is None: candidate_server_differ = True else: candidate_server_differ = (self.server_basis != self.candidate_basis) # Ask the user if he's OK with proceeding with the synchronization if self.client_basis is None or accepted_state is None \ or accepted_state != current_state: if client_server_differ and candidate_server_differ: if out_of_sync \ and not self._user_accepts_synchronization( content_to_download, content_to_delete_locally, edit_conflicts, deletion_conflicts, self.client_basis, self.server_basis, remote_sizes, local_sizes): self.logger.warning( u'User has refused the synchronization, shutting down') return False storage_list_hash = get_hash(self.storage_content) to_save = "%s %s" % (self.server_basis, storage_list_hash) self._context.metadataDB.set(metadata.LASTACCEPTEDSTATEKEY, to_save) self._context._save_basis_in_history(self.client_basis, self.server_basis, True) if self._context._internal_facade.is_first_startup(): self._context._save_basis_in_history(self.client_basis, self.server_basis, True) return True
def _check_hash_mismatch(self): """Handles the hash mismatch, if any. An "hash mismatch" means that the content on the storage is different from what we remember from our last connection. We have to ask the user to tell if such modifications are acceptable (meaning that the user himself did them with another client) or if it's the result of malicious data tampering. The user will match both the hash (together with its visual representation, the "robohash") and the list of remote modification to replicate on the local data to make a decision. @return Boolean telling whether it's OK to proceed with the synchronization or not. """ # Collect the data resulting from the diff algorithm diff = self._context.startup_synchronization content_to_download = sorted(diff.content_to_download) content_to_delete_locally = sorted(diff.content_to_delete_locally) content_to_delete_locally.reverse() edit_conflicts = diff.edit_conflicts deletion_conflicts = diff.deletion_conflicts # Any pathname to synchronize? something_to_sync = len(content_to_download) > 0 something_to_sync |= len(content_to_delete_locally) > 0 something_to_sync |= len(deletion_conflicts) > 0 # Any modification to the storage cache to do? storage_cache_needs_update = len(diff.remote_deletions) > 0 storage_cache_needs_update |= len(diff.ignored_conflicts) > 0 # Anything the user should be notified of by opening the dialog? out_of_sync = something_to_sync or storage_cache_needs_update remote_sizes = self._context.startup_synchronization.remote_size local_sizes = self._context.startup_synchronization.local_size accepted_state = self._context.metadataDB.try_get( metadata.LASTACCEPTEDSTATEKEY) # if there is an accepted basis but it is the same of the current basis # and there is nothing to sync, maybe the client was crashed before # removing it from storage_cache if accepted_state is not None: accepted_basis, _ = accepted_state.split() if not out_of_sync and self.client_basis == accepted_basis: self._context.metadataDB.delete_key( metadata.LASTACCEPTEDSTATEKEY) accepted_state = None if self.client_basis is not None: # First Start, I cannot check anything current_state = self._catch_wrong_states(self.storage_content, accepted_state, out_of_sync) if self.client_basis is None: client_server_differ = True else: client_server_differ = (self.server_basis != self.client_basis) if self.candidate_basis is None: candidate_server_differ = True else: candidate_server_differ = (self.server_basis != self.candidate_basis) # Ask the user if he's OK with proceeding with the synchronization if self.client_basis is None or accepted_state is None \ or accepted_state != current_state: if client_server_differ and candidate_server_differ: if out_of_sync \ and not self._user_accepts_synchronization( content_to_download, content_to_delete_locally, edit_conflicts, deletion_conflicts, self.client_basis, self.server_basis, remote_sizes, local_sizes): self.logger.warning( u'User has refused the synchronization, shutting down') return False storage_list_hash = get_hash( remove_lmtime_from_filelist(self.storage_content)) to_save = "%s %s" % (self.server_basis, storage_list_hash) self._context.metadataDB.set(metadata.LASTACCEPTEDSTATEKEY, to_save) self._save_basis_in_history(self.client_basis, self.server_basis, True) if self._context._internal_facade.is_first_startup(): self._save_basis_in_history(self.client_basis, self.server_basis, True) return True