如何使用Twisted通过OAuth2.0身份验证检查Gmail

Cor*_*rey 10 python gmail twisted oauth-2.0 google-oauth

我有一个有效的谷歌邮件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")
Run Code Online (Sandbox Code Playgroud)

我有一个琐碎的工厂为这个客户.它通过使用reactor.connectSSL谷歌邮件的主机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")
Run Code Online (Sandbox Code Playgroud)

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

这种验证很好,但是后来扭曲无法选择收件箱:

[AriSBDGmailImap4Client(TLSMemoryBIOProtocol),客户端]失败:未知命令{random gibberish}

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

谢谢你的帮助!

Cor*_*rey 14

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

一个重要的说明是,使用"服务帐户"二步法工艺也不要为我工作.我仍然不清楚为什么这个过程无法使用,但服务帐户似乎无法访问同一帐户中的Gmail.即使服务帐户具有"可以编辑"权限并且启用了Gmail API,也是如此.

有用的参考资料

使用OAuth2的概述https://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中的脚本获取允许您授权Google项目的URL.

在终端:

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

第4步 - 获取授权码

将步骤3中的URL粘贴到浏览器中,然后单击"接受"按钮.

从网页复制代码.

将代码粘贴到终端并按Enter键.您将获得:

 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
Run Code Online (Sandbox Code Playgroud)

第5步 - 保存刷新令牌

从终端复制刷新令牌并将其保存在某处.在此示例中,我将其保存为json格式的文本文件,其中包含"Refresh Token"键.但它也可以保存到私人数据库中.

确保公众无法访问刷新令牌!

第6步 - 制作扭曲的身份验证器

以下是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
Run Code Online (Sandbox Code Playgroud)

步骤7 - 为协议注册Authenitcator

在IMAP4ClientFactory中:

    def buildProtocol(self, addr):
        p = self.protocol(self.ctx)
        p.factory = self
        x = GmailOAuthAuthenticator(self.reactor)
        p.registerAuthenticator(x)
        return p
Run Code Online (Sandbox Code Playgroud)

步骤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")
Run Code Online (Sandbox Code Playgroud)