Boxsdk JWTAuth keeps giving me this error: 'NoneType' object has no attribute 'encode'

Boxsdk JWTAuth keeps giving me this error: 'NoneType' object has no attribute 'encode'

我正在尝试在 python 中编写一个小脚本来连接到 BOX,但它一直给我这个错误:'NoneType' object has no attribute 'encode'

一开始我以为是我对Passphrase进行编码造成的,但好像不是这样。当我尝试使用 access_token = auth.authenticate_instance() 进行身份验证时,脚本失败。如果我 运行 脚本没有它似乎工作。可能是什么原因造成的?

在此先感谢您提供的任何帮助。

这是我的:

import keyring
from boxsdk import JWTAuth
from boxsdk import Client

def read_tokens():
    """Reads authorisation tokens from keyring"""
    # Use keyring to read the tokens
    auth_token = keyring.get_password('Box_Auth', 'mybox@box.com')
    refresh_token = keyring.get_password('Box_Refresh', 'mybox@box.com')
    return auth_token, refresh_token


def store_tokens(access_token, refresh_token):
    """Callback function when Box SDK refreshes tokens"""
    # Use keyring to store the tokens
    keyring.set_password('Box_Auth', 'mybox@box.com', access_token)
    keyring.set_password('Box_Refresh', 'mybox@box.com', refresh_token)

Passphrase = 'xxxxxxx';
my_str_as_bytes = Passphrase.encode('UTF-8','strict')

auth = JWTAuth(
    client_id='xxxxxxxxxx',
    client_secret='xxxxxxxx',
    enterprise_id='xxxxxxx',
    jwt_key_id='xxxxxxx',
    rsa_private_key_file_sys_path='/home/Marketscale/keys/private_key2.pem',
    rsa_private_key_passphrase=my_str_as_bytes,
    store_tokens=store_tokens,
)

access_token = auth.authenticate_instance()

这是错误的全文:

Traceback (most recent call last):
  File "/home/Marketscale/Tests/JWTTest.py", line 37, in <module>
    access_token = auth.authenticate_instance()
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/jwt_auth.py", line 186, in authenticate_instance
    return self._auth_with_jwt(self._enterprise_id, 'enterprise')
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/jwt_auth.py", line 158, in _auth_with_jwt
    return self.send_token_request(data, access_token=None, expect_refresh_token=False)[0]
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/oauth2.py", line 298, in send_token_request
    self._store_tokens(access_token, refresh_token)
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/oauth2.py", line 233, in _store_tokens
    self._store_tokens_callback(access_token, refresh_token)
  File "/home/Marketscale/Tests/JWTTest.py", line 22, in store_tokens
    keyring.set_password('Box_Refresh', 'mybox@box.com', refresh_token)
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/keyring/core.py", line 48, in set_password
    _keyring_backend.set_password(service_name, username, password)
  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/keyrings/alt/file_base.py", line 128, in set_password
    password_encrypted = self.encrypt(password.encode('utf-8'), assoc)
AttributeError: 'NoneType' object has no attribute 'encode'

我根本不知道你用的是什么 API,但是根据查看代码的一些想法:

自下而上处理堆栈跟踪,您有:

 File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/keyrings/alt/file_base.py", line 128, in set_password
    password_encrypted = self.encrypt(password.encode('utf-8'), assoc)
AttributeError: 'NoneType' object has no attribute 'encode'

那个代码在https://github.com/jaraco/keyrings.alt/blob/master/keyrings/alt/file_base.py,而密码(我们知道是None)是最后传入set_password函数的参数。调用自:

  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/keyring/core.py", line 48, in set_password
    _keyring_backend.set_password(service_name, username, password)

该代码位于 https://github.com/jaraco/keyring/blob/master/keyring/core.py,密码也是 set_password 函数的最后一个参数。接下来,我们有:

  File "/home/Marketscale/Tests/JWTTest.py", line 22, in store_tokens
    keyring.set_password('Box_Refresh', 'mybox@box.com', refresh_token)

..这是你的代码,所以 refresh_token 一定是 None。这意味着您的 store_tokens 必须使用 None 的 refresh_token 调用。下一篇:

  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/oauth2.py", line 233, in _store_tokens
    self._store_tokens_callback(access_token, refresh_token)

这是在 https://github.com/box/box-python-sdk/blob/master/boxsdk/auth/oauth2.py,再次意味着调用 _store_tokens 时 refresh_token 设置为 None。继续...

  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/oauth2.py", line 298, in send_token_request
    self._store_tokens(access_token, refresh_token)

代码与上一个页面相同,但现在更有趣了:

        url = '{base_auth_url}/token'.format(base_auth_url=API.OAUTH2_API_URL)
        headers = {'content-type': 'application/x-www-form-urlencoded'}
        network_response = self._network_layer.request(
            'POST',
            url,
            data=data,
            headers=headers,
            access_token=access_token,
        )
        if not network_response.ok:
            raise BoxOAuthException(network_response.status_code, network_response.content, url, 'POST')
        try:
            response = network_response.json()
            access_token = response['access_token']
            refresh_token = response.get('refresh_token', None)
            if refresh_token is None and expect_refresh_token:
                raise BoxOAuthException(network_response.status_code, network_response.content, url, 'POST')
        except (ValueError, KeyError):
            raise BoxOAuthException(network_response.status_code, network_response.content, url, 'POST')
        self._store_tokens(access_token, refresh_token)
        return self._access_token, self._refresh_token

所以我们知道调用 self._store_tokens 时 refresh_token 设置为 None,这意味着 expect_refresh_token 一定是 False,否则 BoxOAuthException 会被提出。事实上,如果我们查看堆栈跟踪中的下一行,我们可以看到:

  File "/home/Marketscale/.virtualenvs/myvirtualenv/lib/python3.5/site-packages/boxsdk/auth/jwt_auth.py", line 158, in _auth_with_jwt
    return self.send_token_request(data, access_token=None, expect_refresh_token=False)[0]

这向我表明,当您使用 JWT Auth 时,您不应该期待刷新令牌。并且考虑到当您将 None 作为密码传递给它时密钥环的文件后端会爆炸,听起来您需要以不同的方式处理 None 情况。因此,我建议更改您提供的 store_tokens 函数,以便它在 None 时忽略刷新令牌,即:

def store_tokens(access_token, refresh_token):
    """Callback function when Box SDK refreshes tokens"""
    # Use keyring to store the tokens
    keyring.set_password('Box_Auth', 'mybox@box.com', access_token)
    if refresh_token is not None:
        keyring.set_password('Box_Refresh', 'mybox@box.com', refresh_token)

...或者这样它将 None 转换成密钥环文件后端可以优雅处理的东西——也许一个空字符串就可以做到这一点:

    def store_tokens(access_token, refresh_token):
        """Callback function when Box SDK refreshes tokens"""
        # Use keyring to store the tokens
        keyring.set_password('Box_Auth', 'mybox@box.com', access_token)
        if refresh_token is None:
            refresh_token = ""
        keyring.set_password('Box_Refresh', 'mybox@box.com', refresh_token)

警告 -- 就像我说的,我不知道这些 APIs -- 既不知道你使用的盒子,也不知道你使用的钥匙圈。但是根据那里的代码,做这样的事情听起来很值得一试。