-
Notifications
You must be signed in to change notification settings - Fork 238
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
Feature: Backup & Restore with GoogleDrive #1376
Feature: Backup & Restore with GoogleDrive #1376
Conversation
Cool, looks very interesting. In the tests, i see:
Did you forget to list the needed modules in |
Oops, yes! How do I update the requirements? I manually added
to requirements.in and ran
Is this related to jazzband/pip-tools#1228? |
…o/specter-desktop into add-google-drive-backup
Ran |
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.
I guess that's enough for the code-review for now.
) | ||
.execute() | ||
) | ||
return {"success": True} |
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.
This style of returning stuff is usually common for (rest-) endpoints but not for a function in a utils-class. Consider to let the exception get exposed to the calling context and deal with it there!
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.
Understood.
.execute() | ||
) | ||
return {"success": True} | ||
except Exception as e: |
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.
Also it's usually not a good idea to catch "Exception". It usually means you're aware that your code is buggy but you don't want to dive into the details of why that is.
Your probably wrote it for a reason. Reproduce the issue and check which exception it is exactly. Probably some google-drive-specific ones. So catch exactly those exceptions. As a benefit, it's easier to react on those exceptions rather than not knowing what to do because of a generic exception.
The default behaviour of app.logger(...)
and then proceeding is usually implemented anyway somewhere up the stacktrace.
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.
Hmm. I wanted to account for any and every exception that google-drive's apis may throw. Even if I am able to manually try a few possibilities, wouldn't there be some exception that I couldn't find? Shouldn't except Exception as e:
still be there just in case this happens?
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.
The thing is: Do you know how to deal with those exceptions? Maybe yes. But then figure out the exception hirarchy of google. Probably they have a common Exception they are all derived from. That way you would handle Google-Exceptions, not ALL exceptions. That's an important difference as you might cover e.g. an Out-of-memory-Error and assume it's some Google-Exception.
So in general, it's much better to NOT handle an exception and let it propagate then to suppress it and try to move on (if you don't have a rough understanding why that Exception is thrown).
The kind of generic handling is already built in in certain levels of the system:
https://github.com/cryptoadvance/specter-desktop/blob/master/src/cryptoadvance/specter/server_endpoints/controller.py#L49-L87
As you can see, we're handling 3 different errors slightly different. So in your case (and if you would remove your catching-code) if weird Google-exceptions might be thrown, you end up in Line 77 and a generic error-handler-page (and logging in the logs).
This is the best to figure out what's happening and a proper fix can get delivered based on that information which deals with the details of this issue in a much more informed way,
Rule of thumb: Don't deal with errors you don't know about.
That doesn't mean that you should look out for errors and think about how to deal with them best. But don't assume all Exceptions are Google-Exceptions.
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.
That really makes so much sense. Thanks! I'll keep this in mind and make changes.
) | ||
files = response.get("files", []) | ||
if len(files) == 0: | ||
return {"success": False, message: "No backups found."} |
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.
Instead of "Rest-style" consider to raise your own SpecterError
or maybe your own derivation of it. An Exception system is a great thing! Use it!
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.
Haha yea. Didn't realise that. Will do!
@settings_endpoint.route("/backup_to_google_drive", methods=["POST"]) | ||
@login_required | ||
def backup_to_google_drive(): | ||
return backup(app.specter.specter_backup_file(), current_user) |
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.
These implementations are too "thin". You should put the logic of returning json in this layer and the heavy lifitng in the util-layer. See below!
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.
Got it!
window.location = jsonResponse.redirect_url; | ||
return | ||
} | ||
if (jsonResponse.success) { |
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.
Is that enough? Could also be {"success": False}
(or True) right?
But maybe you should overthink the whole approach here. Http-status-codes are great! Use them for your own purposes. So you can return them in the endpoints as you need.
Get inspired by https://http.cat/
But you don't even have to check for specific codes. You can just remove this part because you already check above for != 200.
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.
Will look into it.
The OAuth scopeI like the separation of the data and i dislike that the user can't access that data. Can we have a "leak" so that the user can download that data in raw-format from google-drive at any time (independent of whether that file is currently restored or not)? That way we can stick to the app-data approach but make it visible to the user as well. versioning questionYeah, that would be nice. But let's push that back until we have a reasonable minimum version. #OAUTHLIB_INSECURE_TRANSPORT=1 Port question
I'm not sure i understand that. Do you talk about this:
You could replace the port there with the port from the flask-config, no? |
The OAuth ScopeThere is no way a user can access an application's data. So the "leak" is out of the question I think, at least as per what Google docs say. However there is another scope OAUTHLIB_INSECURE_TRANSPORT=1 and PortI tested out with REDIRECT_URI = "http://localhost:25441/settings/google_oauth/callback" or REDIRECT_URI = "https://localhost:25441/settings/google_oauth/callback" based on whether the server is running with ssl or not. Also, when creating API creds on the GCP console, we need to define Authorized JavaScript origins as:
and Authorized redirect URIs as:
to allow servers with and without ssl to complete oauth. I did rename the callback url from Anyways, since we have to define these uris in the GCP console (with port numbers), we lose flexibility with Port numbers. If we're using x, it should be listed in Authorized JavaScript origins and Authorized redirect URIs. And since this is specific to the organisation's GCP credentials, we cannot allow users to add custom port numbers to this list. The best we can do is add as many common port numbers as we can. |
@k9ert, I've made changes based on your suggestions. There's one CORS error I tried solving but couldn't. Initially, I used to send a
Also, need a way to figure out if the current server is being run with |
CORS errors are nasty. It was some time that i digged into all of that OAuth2/CORS stuff and i feel with you because it's time-consuming, difficult to troubleshoot and a lot of poking and hoping (and then backtracking in order to figure out what exactly made the difference). And i also didn't enjoyed it. Whether you run on ssl should be checkable via: Thank you for your work so far. Looks really interesting. I'll definitely will give it a try today or tomorrow! |
Avoids the CORS error that accounts.google.com has with Origin:http://localhost:25441/ by updating HTTP 307 Redirect with a HTTP 401. Redirection is done by the browser instead.
@k9ert, an update: The CORS error was inevitable and had nothing to do with the flask server. It was thrown by
Also, since you will be running this soon, you do need .flaskenv to contain these:
I've been using my own creds to test. If you need any help with setting this up for the organisation, do let me know. |
Defintely i need some help. Are you on telegram? My handle there is the same! |
I think this needs to be reimplemented as extension. I'm closing this PR for now. It would really be cool to maybe have that as extension but we can't no longer accept it in the core codebase. |
Has this topic been picked back up? |
Adds support to backup and restore to Google Drive.
Backup works the same way as "Download Specter backup files" except that it uploads the zip file to Google Drive.
Restore works the same way as "Load Specter backup" but without prompting the user to choose files.
Action Required:
.flaskenv
should contain the following:NOTE: Authorized JavaScript origins should contain
http://localhost:25441
and Authorized redirect URIs should containhttp://localhost:25441/settings/backup_to_google_drive/callback
.Things to discuss:
Currently, the scope we request from Google drive is
https://www.googleapis.com/auth/drive.appdata
as recommended here. This means we do not request access to the user's drive folders or files. A possible downside (or maybe an advantage) is that the user will not be able to access this "app data" that we're storing. The link above says:We have access to all versions of the backup file. Currently, we're only accessing the last saved one. We can maybe include a picker to let the users choose this based on timestamp. We might have to download and preview the contents here too.
Since we do not have SSL running on localhost, we're disabling OAuth2's requirement of https by setting
OAUTHLIB_INSECURE_TRANSPORT=1
. Not sure if this has consequences. From here,In case the server isn't running on port 25441, Google OAuth won't work as this specific port number is provided in Authorized JavaScript origins and Authorized redirect URIs while creating credentials.