-
Notifications
You must be signed in to change notification settings - Fork 0
/
AuthWindows.py
329 lines (280 loc) · 11.8 KB
/
AuthWindows.py
1
"""Macstodon - a Mastodon client for classic Mac OSMIT LicenseCopyright (c) 2022-2024 Scott Small and ContributorsPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associateddocumentation files (the "Software"), to deal in the Software without restriction, including without limitation therights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permitpersons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of theSoftware.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THEWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS ORCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OROTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""# ############### Python Imports # ##############import icimport stringimport urllibimport W# ########### My Imports# ##########from MacstodonConstants import VERSIONfrom MacstodonHelpers import dprint, getCurrentUser, getLists, handleRequest, okDialog# ########### AuthWindow# ##########class AuthWindow(W.Dialog): def __init__(self, url): """ Initializes the AuthWindow class. """ W.Dialog.__init__(self, (320, 260), "Macstodon %s - Auth Code" % VERSION) self.setupwidgets(url) # ######################### # Window Handling Functions # ######################### def setupwidgets(self, url): """ Defines the OAuth code window """ auth_text_1 = "Please log in to Mastodon using your web browser, it should have " \ "been automatically launched.\r\rIf your web browser did not automatically " \ "open, or if you closed it, you can manually copy the auth URL below:" self.auth_label_1 = W.TextBox((10, 8, -10, 96), auth_text_1) self.url = W.EditText((10, 82, -10, 60), url, readonly=1) auth_text_2 = "After logging in through the web, you will be presented with a " \ "code, please copy that code and paste it below, then press OK." self.auth_label_2 = W.TextBox((10, 150, -10, 96), auth_text_2) self.code = W.EditText((10, 192, -10, 30)) self.logout_btn = W.Button((10, -22, 60, 16), "Logout", self.authLogoutCallback) self.ok_btn = W.Button((-69, -22, 60, 16), "OK", self.authTokenCallback) self.setdefaultbutton(self.ok_btn) # ################## # Callback Functions # ################## def authLogoutCallback(self): """ Run when the user clicks the "Logout" button from the auth window. Just closes the auth window and reopens the login window. """ self.parent.loginwindow = LoginWindow() self.parent.loginwindow.open() self.close() def authTokenCallback(self): """ Run when the user clicks the "OK" button from the auth window. Uses the user's auth code to make a request for a token. """ authcode = self.code.get() # Raise an error if authcode is blank if string.strip(authcode) == "": okDialog("Please enter the code that was shown in your web browser after you logged in.") return app = self.parent self.close() prefs = app.getprefs() req_data = { "client_id": prefs.client_id, "client_secret": prefs.client_secret, "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "grant_type": "authorization_code", "code": authcode, "scope": "read write follow" } path = "/oauth/token" data = handleRequest(app, path, req_data, title="Logging In...") if not data: # handleRequest failed (and should have popped an error dialog already) app.loginwindow = LoginWindow() app.loginwindow.open() return token = data.get('access_token') if token: prefs.token = token prefs.save() dprint("token: %s" % prefs.token) app.currentuser = getCurrentUser(app) if not app.currentuser: app.loginwindow = LoginWindow() app.loginwindow.open() return else: dprint("current user: %s" % app.currentuser["acct"]) app.lists = getLists(app) app.timelinewindow = app.TimelineWindow() app.timelinewindow.open() else: # we got a JSON response, but it didn't have the auth token dprint("ACK! Token is missing from the response :(") dprint("This is what came back from the server:") dprint(data) if data.get("error_description") is not None: okDialog("Server error when getting auth token:\r\r %s" % data['error_description']) elif data.get("error") is not None: okDialog("Server error when getting auth token:\r\r %s" % data['error']) else: okDialog("Server error when getting auth token.") app.loginwindow = LoginWindow() app.loginwindow.open() return# ############ LoginWindow# ###########class LoginWindow(W.Dialog): def __init__(self): """ Initializes the LoginWindow class. """ W.Dialog.__init__(self, (220, 60), "Macstodon %s - Server" % VERSION) self.setupwidgets() # ######################### # Window Handling Functions # ######################### def setupwidgets(self): """ Defines the login window (prompts for a server URL) """ prefs = self.parent.getprefs() self.sv_label = W.TextBox((0, 8, 110, 16), "Server URL:") self.server = W.EditText((70, 6, -10, 16), prefs.server or "") self.login_btn = W.Button((10, -22, 60, 16), "Login", self.loginCallback) self.quit_btn = W.Button((-69, -22, 60, 16), "Quit", self.parent._quit) self.setdefaultbutton(self.login_btn) # ################## # Callback Functions # ################## def loginCallback(self): """ Run when the user clicks the "Login" button from the login window. """ # Raise an error if URL is blank if string.strip(self.server.get()) == "": okDialog( "Please enter the URL to your Mastodon server, using HTTP instead of HTTPS. This value cannot be blank." ) return # Save server to prefs and clear tokens if it has changed prefs = self.parent.getprefs() if prefs.server and (self.server.get() != prefs.server): dprint("Server has changed, clearing tokens") prefs.token = None prefs.client_id = None prefs.client_secret = None prefs.max_toot_chars = None prefs.server = self.server.get() # Set default prefs if they do not already exist if not prefs.toots_to_load_startup: prefs.toots_to_load_startup = "5" if not prefs.toots_to_load_refresh: prefs.toots_to_load_refresh = "5" if not prefs.toots_per_timeline: prefs.toots_per_timeline = "20" if prefs.show_avatars not in [1, 0]: prefs.show_avatars = 1 if prefs.show_banners not in [1, 0]: prefs.show_banners = 0 if not prefs.timelinecol1: prefs.timelinecol1 = "home" if not prefs.timelinecol2: prefs.timelinecol2 = "local" if not prefs.timelinecol3: prefs.timelinecol3 = "notifications" # Close login window prefs.save() app = self.parent self.close() # If it's our first time using this server we need to # register our app with it. if prefs.client_id and prefs.client_secret: dprint("Using existing app credentials for this server") else: dprint("First time using this server, creating new app credentials") result = self.createAppCredentials(app) if not result: app.loginwindow = LoginWindow() app.loginwindow.open() return dprint("client id: %s" % prefs.client_id) dprint("client secret: %s" % prefs.client_secret) # Do we have a token (is the user logged in?) If not, we need to # log the user in and generate a token if not prefs.token: dprint("Need to generate a user token") self.getAuthCode(app) else: dprint("Using existing user token for this server") dprint("token: %s" % prefs.token) app.currentuser = getCurrentUser(app) if not app.currentuser: app.loginwindow = LoginWindow() app.loginwindow.open() return else: dprint("current user: %s" % app.currentuser["acct"]) app.lists = getLists(app) app.timelinewindow = app.TimelineWindow() app.timelinewindow.open() # ####################### # OAuth Support Functions # ####################### def createAppCredentials(self, app): """ Creates credentials on the user's Mastodon instance for this application """ req_data = { "client_name": "Macstodon", "website": "https://github.com/smallsco/macstodon", "redirect_uris": "urn:ietf:wg:oauth:2.0:oob", "scopes": "read write follow" } path = "/api/v1/apps" data = handleRequest(app, path, req_data, title="Registering app with instance...") if not data: # handleRequest failed and should have popped an error dialog return 0 client_secret = data.get('client_secret') client_id = data.get('client_id') if client_secret and client_id: prefs = app.getprefs() prefs.client_id = client_id prefs.client_secret = client_secret prefs.save() return 1 else: # we got a JSON response but it didn't have the client ID or secret dprint("ACK! Client ID and/or Client Secret are missing :(") dprint("This is what came back from the server:") dprint(data) if data.get("error_description") is not None: okDialog("Server error when creating application credentials:\r\r %s" % data['error_description']) elif data.get("error") is not None: okDialog("Server error when creating application credentials:\r\r %s" % data['error']) else: okDialog("Server error when creating application credentials.") return 0 def getAuthCode(self, app): """ Launches the user's web browser with the OAuth token generation URL for their instance. """ prefs = app.getprefs() req_data = { "client_id": prefs.client_id, "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "scope": "read write follow", "response_type": "code" } url = "%s/oauth/authorize?%s" % (prefs.server, urllib.urlencode(req_data)) try: ic.launchurl(url) except: # Internet Config is not installed, or, if it is installed, # then a default web browser has not been set. dprint("Missing Internet Config, or no default browser configured") app.authwindow = AuthWindow(url) app.authwindow.open()