Browse Source

implement GUI authentication using webkit-gtk

Arnau Sanchez 10 years ago
parent
commit
975ece186e
2 changed files with 76 additions and 13 deletions
  1. 57 6
      youtube_upload/auth.py
  2. 19 7
      youtube_upload/main.py

+ 57 - 6
youtube_upload/auth.py

@@ -1,12 +1,61 @@
 """Wrapper for Google OAuth2 API."""
 import sys
+import json
 
+import gtk
+import webkit
 import googleapiclient.discovery
 import oauth2client
 import httplib2
 
 YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload"
 
+JAVASCRIPT_AUTHORIZATION_SET_STATUS = """
+    var code = document.getElementById("code");
+    var access_denied = document.getElementById("access_denied");
+    var result;
+    
+    if (code) {
+        result = {authorized: true, code: code.value};
+    } else if (access_denied) {
+        result = {authorized: false, message: access_denied.innerText};
+    } else {
+        result = {};
+    }
+    window.status = JSON.stringify(result);
+"""
+
+def _on_webview_status_bar_changed(webview, status, dialog):
+    if status:
+        result = json.loads(status)
+        if result.has_key("authorized"):
+            dialog.set_data("authorization", result)
+            dialog.response(0)
+    
+def _get_code_from_browser(url, size=(640, 480), title=None):
+    """Open a webkit window and return the code the user wrote."""
+    webview = webkit.WebView()
+    dialog = gtk.Dialog(title=(title or "Google authentication"))
+    scrolled = gtk.ScrolledWindow()
+    scrolled.add(webview)
+    dialog.get_children()[0].add(scrolled)
+    webview.load_uri(url)    
+    dialog.resize(*size)
+    dialog.show_all()
+    
+    dialog.connect("delete-event", lambda ev, data: dialog.response(1))
+    webview.connect("load-finished", 
+        lambda view, frame: webview.execute_script(JAVASCRIPT_AUTHORIZATION_SET_STATUS))       
+    webview.connect("status-bar-text-changed", _on_webview_status_bar_changed, dialog)
+    dialog.set_data("authorization", None)
+    status = dialog.run()
+    dialog.destroy()
+    while gtk.events_pending():
+        gtk.main_iteration(False)
+    authorization = dialog.get_data("authorization")    
+    if status == 0 and authorization and authorization["authorized"]:
+        return authorization["code"]
+        
 def _get_code_from_prompt(authorize_url):
     """Show authorization URL and return the code the user wrote."""
     message = "Check this link in your browser: {0}".format(authorize_url)
@@ -18,10 +67,11 @@ def _get_credentials_interactively(flow, storage, get_code_callback):
     flow.redirect_uri = oauth2client.client.OOB_CALLBACK_URN
     authorize_url = flow.step1_get_authorize_url()
     code = get_code_callback(authorize_url)
-    credential = flow.step2_exchange(code, http=None)
-    storage.put(credential)
-    credential.set_store(storage)
-    return credential
+    if code:
+        credential = flow.step2_exchange(code, http=None)
+        storage.put(credential)
+        credential.set_store(storage)
+        return credential
 
 def _get_credentials(flow, storage, get_code_callback):
     """Return the user credentials. If not found, run the interactive flow."""
@@ -38,5 +88,6 @@ def get_resource(client_secrets_file, credentials_file, get_code_callback=None):
     storage = oauth2client.file.Storage(credentials_file)
     get_code = get_code_callback or _get_code_from_prompt
     credentials = _get_credentials(flow, storage, get_code)
-    http = credentials.authorize(httplib2.Http())
-    return googleapiclient.discovery.build("youtube", "v3", http=http)
+    if credentials:
+        http = credentials.authorize(httplib2.Http())
+        return googleapiclient.discovery.build("youtube", "v3", http=http)

+ 19 - 7
youtube_upload/main.py

@@ -24,6 +24,7 @@ import youtube_upload.auth
 import youtube_upload.upload_video
 import youtube_upload.categories
 import youtube_upload.lib as lib
+import oauth2client
 
 # http://code.google.com/p/python-progressbar (>= 2.3)
 try:
@@ -33,10 +34,13 @@ except ImportError:
 
 class InvalidCategory(Exception): pass
 class OptionsMissing(Exception): pass
+class AuthenticationError(Exception): pass
 
 EXIT_CODES = {
     OptionsMissing: 2,
     InvalidCategory: 3,
+    AuthenticationError: 4,
+    oauth2client.client.FlowExchangeError: 4,
 }
 
 WATCH_VIDEO_URL = "https://www.youtube.com/watch?v={id}"
@@ -120,13 +124,19 @@ def run_main(parser, options, args, output=sys.stdout):
     credentials = options.credentials_file or default_credentials
     debug("Using client secrets: {0}".format(client_secrets))
     debug("Using credentials file: {0}".format(credentials))
-    youtube = youtube_upload.auth.get_resource(client_secrets, credentials)
-
-    for index, video_path in enumerate(args):
-        video_id = upload_video(youtube, options, video_path, len(args), index)
-        video_url = WATCH_VIDEO_URL.format(id=video_id)
-        debug("Video URL: {0}".format(video_url))
-        output.write(video_id + "\n")
+    get_code_callback = (youtube_upload.auth._get_code_from_browser 
+        if options.auth_gui else None)
+    youtube = youtube_upload.auth.get_resource(client_secrets, credentials,
+        get_code_callback=get_code_callback)
+
+    if youtube:
+        for index, video_path in enumerate(args):
+            video_id = upload_video(youtube, options, video_path, len(args), index)
+            video_url = WATCH_VIDEO_URL.format(id=video_id)
+            debug("Video URL: {0}".format(video_url))
+            output.write(video_id + "\n")
+    else:
+        raise AuthenticationError("Cannot get youtube resource")
 
 def main(arguments):
     """Upload videos to Youtube."""
@@ -158,6 +168,8 @@ def main(arguments):
         type="string", help='Client secrets JSON file')
     parser.add_option('', '--credentials-file', dest='credentials_file',
         type="string", help='Client secrets JSON file')
+    parser.add_option('', '--auth-gui', dest='auth_gui', action="store_true",
+        help='Open a GUI browser to authenticate if required')
 
     options, args = parser.parse_args(arguments)
     run_main(parser, options, args)