2016-02-13 138 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()