【问题标题】:How to use Twisted to check Gmail with OAuth2.0 authentication如何使用 Twisted 通过 OAuth2.0 身份验证检查 Gmail
【发布时间】:2015-06-25 02:21:32
【问题描述】:

我有一个适用于 Google 邮件的 IMAP 客户端,但它最近停止工作。我相信问题是gmail不再允许TTL用户名/密码登录,但现在需要OAuth2.0。

我想知道更改下面示例的最佳方法,以便我的扭曲 IMAP 客户端使用 OAuth2.0 进行身份验证。 (如果可能的话,在没有 Google API 包的情况下这样做。)

使用用户名/密码登录的示例(不再有效)

class AriSBDGmailImap4Client(imap4.IMAP4Client):
    '''
    client to fetch and process SBD emails from gmail. the messages
    contained in the emails are sent to the AriSBDStationProtocol for
    this sbd modem.
    '''

    def __init__(self, contextFactory=None):
        imap4.IMAP4Client.__init__(self, contextFactory)

    @defer.inlineCallbacks
    def serverGreeting(self, caps):
        # log in
        try:
            # the line below no longer works for gmail
            yield self.login(mailuser, mailpass)
            try:
                yield self.uponAuthentication()
            except Exception as e:
                uponFail(e, "uponAuthentication")
        except Exception as e:
            uponFail(e, "logging in")

        # done. log out
        try:
            yield self.logout()
        except Exception as e:
            uponFail(e, "logging out")

    @defer.inlineCallbacks
    def uponAuthentication(self):
        try:
            yield self.select('Inbox')
            try:
                # read messages, etc, etc
                pass
            except Exception as e:
                uponFail(e, "searching unread")
        except Exception as e:
            uponFail(e, "selecting inbox")

我为这个客户提供了一个简单的工厂。它通过使用 reactor.connectSSL 与 Google 邮件的主机 url 和端口开始。

我已经按照https://developers.google.com/gmail/api/quickstart/quickstart-python 的指示进行了“已安装的应用程序”(但我不知道这是否是正确的选择)。我可以成功运行他们的“quickstart.py”示例。

我快速而肮脏的尝试(不起作用)

    @defer.inlineCallbacks
    def serverGreeting(self, caps):
        # log in
        try:
            #yield self.login(mailuser, mailpass)
            flow = yield threads.deferToThread(
                oauth2client.client.flow_from_clientsecrets,
                filename=CLIENT_SECRET_FILE, 
                scope=OAUTH_SCOPE)
            http = httplib2.Http()
            credentials = yield threads.deferToThread( STORAGE.get )
            if credentials is None or credentials.invalid:
                parser = argparse.ArgumentParser(
                    parents=[oauth2client.tools.argparser])
                flags = yield threads.deferToThread( parser.parse_args )
                credentials = yield threads.deferToThread(
                    oauth2client.tools.run_flow,
                    flow=flow, 
                    storage=STORAGE,
                    flags=flags, http=http)
            http = yield threads.deferToThread(
                credentials.authorize, http)

            gmail_service = yield threads.deferToThread(
                apiclient.discovery.build,
                serviceName='gmail', 
                version='v1',
                http=http)

            self.state = 'auth'

            try:
                yield self.uponAuthentication()
            except Exception as e:
                uponFail(e, "uponAuthentication")
        except Exception as e:
            uponFail(e, "logging in")

        # done. log out
        try:
            yield self.logout()
        except Exception as e:
            uponFail(e, "logging out")

我基本上只是将“quickstart.py”复制到serverGreeting,然后尝试将客户端状态设置为“auth”。

这个验证就好了,但是twisted无法选择收件箱:

[AriSBDGmailImap4Client (TLSMemoryBIOProtocol),client] FAIL: Unknown command {random gibberish}

随机乱码有字母和数字,每次选择收件箱命令失败时都是不同的。

感谢您的帮助!

【问题讨论】:

    标签: python oauth-2.0 gmail google-oauth twisted


    【解决方案1】:

    经过大量阅读和测试,我终于能够使用 OAuth2 实现对 gmail 的有效登录。

    一个重要的注意事项是,使用“服务帐户”的两步过程对我来说工作。我仍然不清楚为什么不能使用这个过程,但是服务帐户似乎无法访问同一帐户中的 gmail。即使服务帐户具有“可以编辑”权限并且启用了 Gmail API 也是如此。

    有用的参考资料

    使用概述 OAuth2https://developers.google.com/identity/protocols/OAuth2

    通过“已安装的应用程序”使用 OAuth2 的指南 https://developers.google.com/identity/protocols/OAuth2InstalledApp

    设置帐户以通过“已安装的应用程序”使用 OAuth2 的指南 https://developers.google.com/api-client-library/python/auth/installed-app

    没有完整 Google API 的 OAuth2 例程集合 https://code.google.com/p/google-mail-oauth2-tools/wiki/OAuth2DotPyRunThrough

    第 1 步 - 获取 Google 客户端 ID

    用gmail账号登录https://console.developers.google.com/

    启动一个项目,启用 Gmail API 并为已安装的应用程序创建一个新的客户端 ID。说明https://developers.google.com/api-client-library/python/auth/installed-app#creatingcred

    单击“下载 JSON”按钮并将此文件保存在公众无法访问的位置(因此可能不在代码存储库中)。

    第 2 步 - 获取 Google OAuth2 Python 工具

    https://code.google.com/p/google-mail-oauth2-tools/wiki/OAuth2DotPyRunThrough下载oauth2.py脚本

    第 3 步 - 获取授权 URL

    使用第 2 步中的脚本获取一个 URL,允许您授权您的 Google 项目。

    在终端中:

    python oauth2.py --user={myaccount@gmail.com} --client_id={your client_id from the json file} --client_secret={your client_secret from the json file} --generate_oauth2_token

    第四步——获取授权码

    将第 3 步中的 URL 粘贴到浏览器中,然后点击“接受”按钮。

    从网页复制代码。

    将代码粘贴到终端并按回车键。您将获得:

     To authorize token, visit this url and follow the directions:   https://accounts.google.com/o/oauth2/auth?client_id{...}
     Enter verification code: {...}
     Refresh Token: {...}
     Access Token: {...}
     Access Token Expiration Seconds: 3600
    

    第 5 步 - 保存刷新令牌

    从终端复制刷新令牌并将其保存在某处。在这个例子中,我将它保存到一个带有“Refresh Token”键的 json 格式的文本文件中。但它也可以保存到私有数据库中。

    确保刷新令牌不能被公众访问!

    第 6 步 - 制作 Twisted Authenticator

    这是一个 OAuth2 身份验证器的工作示例。它需要步骤 2 中的 oauth2.py 脚本。

    import json
    import oauth2
    
    from zope.interface import implementer
    from twisted.internet import threads
    
    MY_GMAIL = {your gmail address}
    REFRESH_TOKEN_SECRET_FILE = {name of your refresh token file from Step 5}
    CLIENT_SECRET_FILE = {name of your cliend json file from Step 1}
    
    @implementer(imap4.IClientAuthentication)
    class GmailOAuthAuthenticator():
        authName     = "XOAUTH2"
        tokenTimeout = 3300      # 5 mins short of the real timeout (1 hour)
    
        def __init__(self, reactr):
            self.token   = None
            self.reactor = reactr
            self.expire  = None
    
        @defer.inlineCallbacks
        def getToken(self):
    
            if ( (self.token==None) or (self.reactor.seconds() > self.expire) ):
                rt = None
                with open(REFRESH_TOKEN_SECRET_FILE) as f:
                    rt = json.load(f)
    
                cl = None
                with open(CLIENT_SECRET_FILE) as f:
                    cl = json.load(f)
    
                self.token = yield threads.deferToThread(
                    oauth2.RefreshToken,
                    client_id = cl['installed']['client_id'], 
                    client_secret = cl['installed']['client_secret'],
                    refresh_token = rt['Refresh Token'] )
    
                self.expire = self.reactor.seconds() + self.tokenTimeout
    
    
        def getName(self):
            return self.authName
    
        def challengeResponse(self, secret, chal):
            # we MUST already have the token
            # (allow an exception to be thrown if not)
    
            t = self.token['access_token']
    
            ret = oauth2.GenerateOAuth2String(MY_GMAIL, t, False)
    
            return ret
    

    第 7 步 - 为协议注册身份验证器

    在 IMAP4ClientFactory 中:

        def buildProtocol(self, addr):
            p = self.protocol(self.ctx)
            p.factory = self
            x = GmailOAuthAuthenticator(self.reactor)
            p.registerAuthenticator(x)
            return p
    

    第 8 步 - 使用访问令牌进行身份验证

    不要使用“登录”,而是获取访问令牌(如果需要),然后使用身份验证。

    修改问题中的示例代码:

        @defer.inlineCallbacks
        def serverGreeting(self, caps):
            # log in
            try:
                # the line below no longer works for gmail
                # yield self.login(mailuser, mailpass)
                if GmailOAuthAuthenticator.authName in self.authenticators:
                    yield self.authenticators[AriGmailOAuthAuthenticator.authName].getToken()
    
                yield self.authenticate("")
    
                try:
                    yield self.uponAuthentication()
                except Exception as e:
                    uponFail(e, "uponAuthentication")
            except Exception as e:
                uponFail(e, "logging in")
    

    【讨论】:

    • 您不妨看看 GYB git.io/gyb 的工作服务帐户 IMAP 身份验证。
    • @corey - 非常感谢您在发现如何做之后花时间写出这个深思熟虑和完整的答案。 不知道该怎么做,这绝对是我想学习的东西,因为我的帐户启用了 2FA :)。
    • @Glyph 希望有用!
    • @Corey - 现在已经用了 3 次了。我已经向人们展示了这个作为如何在 Twisted 中扩展 IMAP 的示例,我已经用它编写了一些小技巧,现在我已经在工作中使用了它。此时我可能应该编写一个合适的版本来放入 Twisted 本身:-)
    • @Glyph - 很高兴听到这个消息!嗯...也许我应该做公关... :)
    猜你喜欢
    • 2015-11-19
    • 2016-11-12
    • 1970-01-01
    • 2017-07-15
    • 2013-12-08
    • 2016-04-18
    • 2023-03-22
    • 2014-11-02
    • 1970-01-01
    相关资源
    最近更新 更多