-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When token invalid user must log back in #750
Changes from all commits
8f7c719
8676df9
f05794b
ebc8df7
220995c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
|
||
from securedrop_client import storage | ||
from securedrop_client import db | ||
from securedrop_client.api_jobs.base import ApiInaccessibleError | ||
from securedrop_client.api_jobs.downloads import FileDownloadJob, MessageDownloadJob, \ | ||
ReplyDownloadJob, DownloadChecksumMismatchException, MetadataSyncJob | ||
from securedrop_client.api_jobs.sources import DeleteSourceJob | ||
|
@@ -279,13 +280,24 @@ def call_api(self, | |
new_api_thread.start() | ||
|
||
def on_queue_paused(self) -> None: | ||
if self.api is None: | ||
self.gui.update_error_status(_('The SecureDrop server cannot be reached.')) | ||
else: | ||
self.gui.update_error_status( | ||
_('The SecureDrop server cannot be reached.'), | ||
duration=0, | ||
retry=True) | ||
# TODO: remove if block once https://github.com/freedomofpress/securedrop-client/pull/739 | ||
# is merged and rely on continuous metadata sync to encounter same auth error from the | ||
# server which will log the user out in the on_sync_failure handler | ||
if ( | ||
not self.api or | ||
not self.api_job_queue.main_queue.api_client or | ||
not self.api_job_queue.download_file_queue.api_client or | ||
not self.api_job_queue.metadata_queue.api_client | ||
): | ||
self.invalidate_token() | ||
self.logout() | ||
self.gui.show_login(error=_('Your session expired. Please log in again.')) | ||
return | ||
|
||
self.gui.update_error_status( | ||
_('The SecureDrop server cannot be reached.'), | ||
duration=0, | ||
retry=True) | ||
|
||
def resume_queues(self) -> None: | ||
self.api_job_queue.resume_queues() | ||
|
@@ -344,9 +356,8 @@ def on_authenticate_success(self, result): | |
|
||
def on_authenticate_failure(self, result: Exception) -> None: | ||
# Failed to authenticate. Reset state with failure message. | ||
self.api = None | ||
error = _('There was a problem signing in. ' | ||
'Please verify your credentials and try again.') | ||
self.invalidate_token() | ||
error = _('There was a problem signing in. Please verify your credentials and try again.') | ||
self.gui.show_login_error(error=error) | ||
|
||
def login_offline_mode(self): | ||
|
@@ -424,14 +435,16 @@ def on_sync_success(self) -> None: | |
|
||
def on_sync_failure(self, result: Exception) -> None: | ||
""" | ||
Called when syncronisation of data via the API fails after a background sync. Resume the | ||
queues so that we continue to retry syncing with the server in the background. | ||
Called when syncronisation of data via the API fails after a background sync. If the reason | ||
a sync fails is ApiInaccessibleError then we need to log the user out for security reasons | ||
and show them the login window in order to get a new token. | ||
""" | ||
logger.debug('The SecureDrop server cannot be reached due to Error: {}'.format(result)) | ||
self.gui.update_error_status( | ||
_('The SecureDrop server cannot be reached.'), | ||
duration=0, | ||
retry=True) | ||
|
||
if isinstance(result, ApiInaccessibleError): | ||
self.invalidate_token() | ||
self.logout() | ||
self.gui.show_login(error=_('Your session expired. Please log in again.')) | ||
|
||
def update_sync(self): | ||
""" | ||
|
@@ -481,18 +494,26 @@ def update_star(self, source_db_object, callback): | |
|
||
def logout(self): | ||
""" | ||
Call logout function in the API, reset the API object, and force the UI | ||
to update into a logged out state. | ||
If the token is not already invalid, make an api call to logout and invalidate the token. | ||
Then mark all pending draft replies as failed, stop the queues, and show the user as logged | ||
out in the GUI. | ||
""" | ||
self.call_api(self.api.logout, | ||
self.on_logout_success, | ||
self.on_logout_failure) | ||
self.api = None | ||
if self.api is not None: | ||
self.call_api(self.api.logout, self.on_logout_success, self.on_logout_failure) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha, so if i think it would be good to write that this is what's going on in a comment somewhere (as it's only obvious from tracing the logic and could be easily missed in future modifications). alternatively we could move the setting of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think only setting api to none in one place is a good idea. i was thinking about how I'd like to refactor how we check whether or not we're in offline_mode vs just offline (no token) but i think make a small change to only set api to None in one place would be a good addition to this pr. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. more on the whole offline-mode vs no-token: this is relevant if we want to provide a way for users to regenerate a new token without losing jobs in the api queue (so just pause and wait for a new token), but this is definitely a post-pilot issue |
||
self.invalidate_token() | ||
|
||
failed_replies = storage.mark_all_pending_drafts_as_failed(self.session) | ||
for failed_reply in failed_replies: | ||
self.reply_failed.emit(failed_reply.uuid) | ||
|
||
self.api_job_queue.logout() | ||
storage.mark_all_pending_drafts_as_failed(self.session) | ||
self.gui.logout() | ||
|
||
self.is_authenticated = False | ||
|
||
def invalidate_token(self): | ||
self.api = None | ||
|
||
def set_status(self, message, duration=5000): | ||
""" | ||
Set a textual status message to be displayed to the user for a certain | ||
|
@@ -507,7 +528,7 @@ def _submit_download_job(self, | |
if object_type == db.Reply: | ||
job = ReplyDownloadJob( | ||
uuid, self.data_dir, self.gpg | ||
) # type: Union[ReplyDownloadJob, MessageDownloadJob, FileDownloadJob] | ||
) # type: Union[ReplyDownloadJob, MessageDownloadJob, FileDownloadJob] | ||
job.success_signal.connect(self.on_reply_download_success, type=Qt.QueuedConnection) | ||
job.failure_signal.connect(self.on_reply_download_failure, type=Qt.QueuedConnection) | ||
elif object_type == db.Message: | ||
|
@@ -697,6 +718,7 @@ def on_delete_source_success(self, result) -> None: | |
|
||
def on_delete_source_failure(self, result: Exception) -> None: | ||
logging.info("failed to delete source at server") | ||
|
||
error = _('Failed to delete source at server') | ||
self.gui.update_error_status(error) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very minor nit: maybe
invalidate_token_locally
or something? (just because otherwise the dev might think this method callsApi.logout
)