代码之家  ›  专栏  ›  技术社区  ›  madtyn

用于博客作者从Heroku服务器中的电报机器人发布的oauth2client

  •  1
  • madtyn  · 技术社区  · 8 年前

    我刚刚在Heroku部署了我的电报机器人和python电报机器人。

    我的webhooks机器人使用blogger发布某些内容。到目前为止,我都是使用模块google\u apli\u客户机上稍加修改的sample\u tools自定义版本来完成这项工作的。

    my\u工具 :

    """
    dependencies:
        pip3 install --upgrade google-api-python-client
    
    This is a slightly modified implementation 
    for substituting googleapiclient.sample_tools. It helps customizing some paths 
    for my project files under different environments
    """
    from __future__ import absolute_import
    
    from environments import get_active_env
    
    __all__ = ['init']
    
    import argparse
    import os
    
    from googleapiclient import discovery
    from googleapiclient.http import build_http
    from oauth2client import tools, file, client
    
    
    def init(argv, name, version, doc, scope=None, parents=[], 
             discovery_filename=None):
        """A common initialization routine for samples.
    
        Many of the sample applications do the same initialization, which has now
        been consolidated into this function. This function uses common idioms found
        in almost all the samples, i.e. for an API with name 'apiname', the
        credentials are stored in a file named apiname.dat, and the
        client_secrets.json file is stored in the same directory as the application
        main file.
    
        Args:
            argv: list of string, the command-line parameters of the application.
            name: string, name of the API.
            version: string, version of the API.
            doc: string, description of the application. Usually set to __doc__.
            file: string, filename of the application. Usually set to __file__.
            parents: list of argparse.ArgumentParser, additional command-line flags.
            scope: string, The OAuth scope used.
            discovery_filename: string, name of local discovery file (JSON). Use 
            when discovery doc not available via URL.
    
        Returns:
        A tuple of (service, flags), where service is the service object and flags
        is the parsed command-line flags.
        """
        if scope is None:
            scope = 'https://www.googleapis.com/auth/' + name
    
        # Parser command-line arguments.
        parent_parsers = [tools.argparser]
        parent_parsers.extend(parents)
        parser = argparse.ArgumentParser(
            description=doc,
            formatter_class=argparse.RawDescriptionHelpFormatter,
            parents=parent_parsers)
        flags = parser.parse_args(argv[1:])
    
        # Name of a file containing the OAuth 2.0 information for this
        # application, including client_id and client_secret, which are found
        # on the API Access tab on the Google APIs
        # Console <http://code.google.com/apis/console>.
        client_secrets = os.path.join(os.path.dirname(__file__), get_active_env(),
                                     'client_secrets.json')
    
        # Set up a Flow object to be used if we need to authenticate.
        flow = client.flow_from_clientsecrets(client_secrets,
          scope=scope,
          message=tools.message_if_missing(client_secrets))
    
        # Prepare credentials, and authorize HTTP object with them.
        # If the credentials don't exist or are invalid, 
        # run through the native client flow.
        # The Storage object will ensure that if successful the good
        # credentials will get written back to a file in google_core directory.
        storage_file_path = os.path.join(os.path.dirname(__file__), name + '.dat')
        storage = file.Storage(storage_file_path)
        credentials = storage.get()
        if credentials is None or credentials.invalid:
            credentials = tools.run_flow(flow, storage, flags)
        http = credentials.authorize(http=build_http())
    
        if discovery_filename is None:
            # Construct a service object via the discovery service.
            service = discovery.build(name, 
                                      version, 
                                      http=http, 
                                      cache_discovery=False)
        else:
            # Construct a service object using a local discovery document file.
            with open(discovery_filename) as discovery_file:
                service = discovery.build_from_document(
                    discovery_file.read(),
                    base='https://www.googleapis.com/',
                    http=http)
            service = discovery.build(name, 
                                      version, 
                                      http=http, 
                                      cache_discovery=False)
        return (service, flags)
    

    有了它,我可以进行身份验证,操作系统中的浏览器将很好地打开,并允许我(或最终用户)授权应用程序使用我(或用户)的博客。

    使用my\u工具的初始代码段 :

    service, flags = my_tools.init(
        [], 'blogger', 'v3', __doc__,
        scope='https://www.googleapis.com/auth/blogger')
    
    try:
        posts = service.posts()
        # This new_post is a custom object, but the important thing here
        # is getting the authorization, and then the service at the top
        insert = posts.insert(blogId=new_post.blog_id, body=new_post.body(), isDraft=new_post.is_draft)
        posts_doc = insert.execute()
        return posts_doc
    except client.AccessTokenRefreshError:
        print('The credentials have been revoked or expired, please re-run the application to re-authorize')
    

    但现在我不能这样做,因为它在heroku中,日志中会显示以下消息:

    app[web.1]: Your browser has been opened to visit:
    app[web.1]: 
    app[web.1]:     https://accounts.google.com/o/oauth2/auth?client_id=<client_id>&redirect_uri=http%3A%2F%2Flocalhost%3A8090%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&access_type=offline&response_type=code
    app[web.1]: 
    app[web.1]: If your browser is on a different machine then exit and re-run this
    app[web.1]: application with the command-line parameter
    app[web.1]: 
    app[web.1]:   --noauth_local_webserver
    app[web.1]:
    

    我需要自动授权heroku应用程序,因为它只能从仅限于某些用户的telegram bot访问,所以不需要让用户访问浏览器并授权。

    我需要一些方法让机器人可以使用blogger,用户可以在需要时通过简单的方式授权,或者使用服务器中已经存储的授权文件来使用机器人。

    我在谷歌上搜索并查看了以下资源:

    https://developers.google.com/api-client-library/python/auth/web-app https://github.com/burnash/gspread/wiki/How-to-get-OAuth-access-token-in-console%3F Django oauth2 google not working on server

    但我完全不知道该怎么做,该怎么做。我觉得我需要一个傻瓜式的解释。

    已编辑 :我已被指向此网站

    https://developers.google.com/api-client-library/python/auth/service-accounts

    所以我尝试了这个新代码。

    新建代码段 :

    from oauth2client import service_account
        import googleapiclient.discovery
        import os
        from environments import get_active_env
        SERVICE_ACCOUNT_FILE = os.path.join(os.path.dirname(__file__), os.pardir, 'google_core', get_active_env(),
                                            'service_account.json')
    
        credentials = service_account.ServiceAccountCredentials.from_json_keyfile_name(
            SERVICE_ACCOUNT_FILE, scopes=['https://www.googleapis.com/auth/blogger'])
        service = googleapiclient.discovery.build('blogger', 'v3', credentials=credentials)
    
        try:
            posts = service.posts()
            insert = posts.insert(blogId=new_post.blog_id, body=new_post.body(), isDraft=new_post.is_draft)
            posts_doc = insert.execute()
            return posts_doc
        except client.AccessTokenRefreshError:
            print('The credentials have been revoked or expired, please re-run the application to re-authorize')
    

    因此,我现在在日志中看到了这一点(我认为403 HttpError就是问题所在,关于memcache或oauth2client.contrib.locked\u文件未导入的另一个错误也没什么大不了的):

    heroku[web.1]: Unidling
    heroku[web.1]: State changed from down to starting
    heroku[web.1]: Starting process with command `python my_bot.py`
    heroku[web.1]: State changed from starting to up
    heroku[router]: at=info method=POST path="/<bot_token>" host=telegram-bot-alfred.herokuapp.com request_id=<request_id> fwd="<ip>" dyno=web.1 connect=1ms service=2ms status=200 bytes=97 protocol=https
    app[web.1]: INFO - Input: post_asin 
    app[web.1]: INFO - Input ASIN: B079Z8THTF
    app[web.1]: INFO - Printing offers for asin B079Z8THTF:
    app[web.1]: INFO - EUR 36.98
    app[web.1]: INFO - URL being requested: GET https://www.googleapis.com/discovery/v1/apis/blogger/v3/rest
    app[web.1]: INFO - Attempting refresh to obtain initial access_token
    app[web.1]: INFO - URL being requested: POST https://www.googleapis.com/blogger/v3/blogs/2270688467086771731/posts?isDraft=true&alt=json
    app[web.1]: INFO - Refreshing access_token
    app[web.1]: WARNING - Encountered 403 Forbidden with reason "forbidden"
    app[web.1]: ERROR - Error with asin B079Z8THTF. We go to the next.
    app[web.1]: Traceback (most recent call last):
    app[web.1]:   File "my_bot.py", line 171, in process_asin_string
    app[web.1]:     send_post_to_blogger(update.message, post)
    app[web.1]:   File "/app/api_samples/blogger/blogger_insert.py", line 85, in send_post_to_blogger
    app[web.1]:     response = post_at_blogger(post)
    app[web.1]:   File "/app/api_samples/blogger/blogger_insert.py", line 72, in post_at_blogger
    app[web.1]:     posts_doc = insert.execute()
    app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/googleapiclient/http.py", line 844, in execute
    app[web.1]:     raise HttpError(resp, content, uri=self.uri)
    app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/oauth2client/_helpers.py", line 133, in positional_wrapper
    app[web.1]:     return wrapped(*args, **kwargs)
    app[web.1]: googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/blogger/v3/blogs/2270688467086771731/posts?isDraft=true&alt=json returned "We're sorry, but you don't have permission to access this resource.">
    app[web.1]: ERROR - Exception HttpError not handled
    app[web.1]: Traceback (most recent call last):
    app[web.1]:   File "my_bot.py", line 171, in process_asin_string
    app[web.1]:     send_post_to_blogger(update.message, post)
    app[web.1]:   File "/app/api_samples/blogger/blogger_insert.py", line 85, in send_post_to_blogger
    app[web.1]:     response = post_at_blogger(post)
    app[web.1]:   File "/app/api_samples/blogger/blogger_insert.py", line 72, in post_at_blogger
    app[web.1]:     posts_doc = insert.execute()
    app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/googleapiclient/http.py", line 844, in execute
    app[web.1]:     raise HttpError(resp, content, uri=self.uri)
    app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/oauth2client/_helpers.py", line 133, in positional_wrapper
    app[web.1]:     return wrapped(*args, **kwargs)
    app[web.1]: googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/blogger/v3/blogs/2270688467086771731/posts?isDraft=true&alt=json returned "We're sorry, but you don't have permission to access this resource.">
    app[web.1]: 
    app[web.1]: During handling of the above exception, another exception occurred:
    app[web.1]: 
    app[web.1]: Traceback (most recent call last):
    app[web.1]:   File "/app/exceptions/errors.py", line 47, in alfred
    app[web.1]:     message.reply_text(rnd.choice(answers[type(exception)]))
    app[web.1]: KeyError: <class 'googleapiclient.errors.HttpError'>
    app[web.1]: WARNING - Error with asin B079Z8THTF. We go to the next
    
    1 回复  |  直到 8 年前
        1
  •  1
  •   madtyn    8 年前

    我通过提供如下参数找到了解决方案:

    service, flags = my_tools.init(
        ['', '--noauth_local_webserver'], 'blogger', 'v3', __doc__,
        scope='https://www.googleapis.com/auth/blogger')
    

    然后我不得不从oauth2client定制一些方法。工具。我在我的\u工具中执行了两个方法和附加代码。每一个缺失的部分都很容易从谷歌的原始工具中导入或复制:

    # module scope
    import argparse
    from googleapiclient import discovery
    from googleapiclient.http import build_http
    from oauth2client import tools, file, client, _helpers
    from oauth2client.tools import _CreateArgumentParser
    
    _GO_TO_LINK_MESSAGE = """
    Visit this link to get auth code
    
        {address}
    
    """
    
    # argparser is an ArgumentParser that contains command-line options expected
    # by tools.run(). Pass it in as part of the 'parents' argument to your own
    # ArgumentParser.
    argparser = _CreateArgumentParser()
    
    _flow = None
    
    
    # Methods
    @_helpers.positional(3)
    def run_flow(flow, flags=None):
        """
        Emulates the original method run_flow from oauth2client.tools getting the website to visit.
    
        The ``run()`` function is called from your application and runs
        through all the steps to obtain credentials. It takes a ``Flow``
        argument and attempts to open an authorization server page in the
        user's default web browser. The server asks the user to grant your
        application access to the user's data.  The user can then get an
        authentication code for inputing later
    
        :param flow: the google OAuth 2.0 Flow object with which the auth begun
        :param flags: the provided flags
        :return: the string with the website link where the user can authenticate and obtain a code
        """
        global _flow
    
        # I update the _flow object for using internally later
        _flow = flow
    
        # Really the flags aren't very used. In practice I copied the method as if noauth_local_webserver was provided
        if flags is None:
            flags = argparser.parse_args()
        logging.getLogger().setLevel(getattr(logging, flags.logging_level))
    
        oauth_callback = client.OOB_CALLBACK_URN
        _flow.redirect_uri = oauth_callback
        authorize_url = _flow.step1_get_authorize_url()
    
        return _GO_TO_LINK_MESSAGE.format(address=authorize_url)
    
    
    def oauth_with(code, http=None):
        """
        If the code grants access,
        the function returns new credentials. The new credentials
        are also stored in the ``storage`` argument, which updates the file
        associated with the ``Storage`` object.
    
        :param code: the auth code
        :param http: the http transport object
        :return: the credentials if any
        """
        global _flow
        storage_file_path = get_credentials_path('blogger')
        storage = file.Storage(storage_file_path)
        try:
            # We now re-use the _flow stored earlier
            credential = _flow.step2_exchange(code, http=http)
        except client.FlowExchangeError as e:
            raise AlfredException(msg='Authentication has failed: {0}'.format(e))
    
        storage.put(credential)
        credential.set_store(storage)
        # We reset the flow
        _flow = None
    
        return credential