2016-02-13 35 views
2

我一直在研究一個使用OAuth2來識別用戶的python應用程序。我似乎成功實現了OAuth2隱式授權(通常用於已安裝應用程序和用戶代理應用程序)的工作流程,但在接收令牌的最後一步,似乎出現了問題。用PyQt4/5隱式OAuth2授權

每當用戶需要認證時,就會產生一個顯示登錄頁面的PyQt QWebView窗口(基於webkit)。在用戶登錄並允許我的應用程序使用範圍權限後,OAuth2服務器將重定向到預先指定的redirect_uri。

問題是,當使用QWebView瀏覽器時,通常出現在#後的標記字符串似乎已從URL中刪除:QWebView返回的URL僅爲基本的redirect_uri。

如果我複製粘貼OAuth授權URL並按照這些相同的步驟在Chrome或Firefox等普通Web瀏覽器中登錄和授權,我確實會看到包含令牌字符串的redirect_uri,所以問題確實如此不在OAuth2過程中,但在執行過程中必須出錯。

這是QWebView或webkit實現的固有行爲嗎?我錯誤地讀出了QUrl?

爲了完整起見,這裏是我的代碼:

產生用於開放科學框架中的OAuth2網址

osf.py模塊。

# Import basics 
import sys 
import os 

# Module for easy OAuth2 usage, based on the requests library, 
# which is the easiest way to perform HTTP requests. 

# OAuth2Session object 
from requests_oauthlib import OAuth2Session 
# Mobile application client that does not need a client_secret 
from oauthlib.oauth2 import MobileApplicationClient 

#%%----------- Main configuration settings ---------------- 
client_id = "cbc4c47b711a4feab974223b255c81c1" 
# TESTED, just redirecting to Google works in normal browsers 
# the token string appears in the url of the address bar 
redirect_uri = "https://google.nl" 

# Generate correct URLs 
base_url = "https://test-accounts.osf.io/oauth2/" 
auth_url = base_url + "authorize" 
token_url = base_url + "token" 
#%%-------------------------------------------------------- 

mobile_app_client = MobileApplicationClient(client_id) 

# Create an OAuth2 session for the OSF 
osf_auth = OAuth2Session(
    client_id, 
    mobile_app_client, 
    scope="osf.full_write", 
    redirect_uri=redirect_uri, 
) 

def get_authorization_url(): 
    """ Generate the URL with which one can authenticate at the OSF and allow 
    OpenSesame access to his or her account.""" 
    return osf_auth.authorization_url(auth_url) 

def parse_token_from_url(url): 
    token = osf_auth.token_from_fragment(url) 
    if token: 
     return token 
    else: 
     return osf_auth.fetch_token(url) 

主程序,打開了登錄屏幕QWebView瀏覽器窗口

# Oauth2 connection to OSF 
import off 
import sys 
from PyQt4 import QtGui, QtCore, QtWebKit 

class LoginWindow(QtWebKit.QWebView): 
    """ A Login window for the OSF """ 

    def __init__(self): 
     super(LoginWindow, self).__init__() 
     self.state = None 
     self.urlChanged.connect(self.check_URL) 

    def set_state(self,state): 
     self.state = state 

    def check_URL(self, url): 
     #url is a QUrl object, covert it to string for easier usage 
     url_string = url.toEncoded() 
     print(url_string) 

     if url.hasFragment(): 
      print("URL CHANGED: On token page: {}".format(url)) 
      self.token = osf.parse_token_from_url(url_string) 
      print(self.token) 
     elif not osf.base_url in url_string: 
      print("URL CHANGED: Unexpected url") 

if __name__ == "__main__": 
    """ Test if user can connect to OSF. Opens up a browser window in the form 
    of a QWebView window to do so.""" 
    # Import QT libraries 

    app = QtGui.QApplication(sys.argv) 
    browser = LoginWindow() 

    auth_url, state = osf.get_authorization_url() 
    print("Generated authorization url: {}".format(auth_url)) 

    browser_url = QtCore.QUrl.fromEncoded(auth_url) 
    browser.load(browser_url) 
    browser.set_state(state) 
    browser.show() 

    exitcode = app.exec_() 
    print("App exiting with code {}".format(exitcode)) 
    sys.exit(exitcode) 

基本上,這是由QWebView的URL_CHANGED事件決不會包含OAuth令牌片段提供給check_URL功能的網址當從OAuth服務器回來時,無論我用於redirect_uri(在本例中,爲簡單起見,我都簡單地重定向到谷歌)。

任何人都可以請幫助我嗎?我已經用盡了我在哪裏尋找解決這個問題的選擇。

回答

2

這似乎是在WebKit的/ Safari瀏覽器一個已知的錯誤:

https://bugs.webkit.org/show_bug.cgi?id=24175 https://phabricator.wikimedia.org/T110976#1594914

基本上它是不固定的,因爲人們不期望的行爲,應根據HTTP規範是什麼達成一致。在How do I preserve uri fragment in safari upon redirect?中描述了一種可能的修復方法,但我無法對此進行測試。

編輯

我設法找到一個(不那麼優雅)解決來解決這個問題。我沒有使用QWebView中的urlChanged事件(它沒有顯示OAuth服務器完成的301重定向),而是使用了QNetworkAccessManager的finished()事件。這會在任何 http請求完成後觸發(因此也適用於頁面的所有鏈接內容,如圖像,樣式表等,因此您必須執行大量過濾)。

所以現在我的代碼看起來是這樣的:

class LoginWindow(QtWebKit.QWebView): 
    """ A Login window for the OSF """ 
    # Login event is emitted after successfull login 
    logged_in = QtCore.pyqtSignal(['QString']) 

    def __init__(self): 
     super(LoginWindow, self).__init__() 

     # Create Network Access Manager to listen to all outgoing 
     # HTTP requests. Necessary to work around the WebKit 'bug' which 
     # causes it drop url fragments, and thus the access_token that the 
     # OSF Oauth system returns 
     self.nam = self.page().networkAccessManager() 

     # Connect event that is fired if a HTTP request is completed. 
     self.nam.finished.connect(self.checkResponse) 

    def checkResponse(self,reply): 
     request = reply.request() 
     # Get the HTTP statuscode for this response 
     statuscode = reply.attribute(request.HttpStatusCodeAttribute) 
     # The accesstoken is given with a 302 statuscode to redirect 

     if statuscode == 302: 
      redirectUrl = reply.attribute(request.RedirectionTargetAttribute) 
      if redirectUrl.hasFragment(): 
       r_url = redirectUrl.toString() 
       if osf.redirect_uri in r_url: 
        print("Token URL: {}".format(r_url)) 
        self.token = osf.parse_token_from_url(r_url) 
        if self.token: 
         self.logged_in.emit("login") 
         self.close()