Python 客户端通过 Nextcloud 上的 OAuth2 访问 CalDAV

Python client to access CalDAV via OAuth2 on Nextcloud

使用 CalDAV 的规范 examples 始终使用 username/password 身份验证。但是 Nextcloud 支持 OAuth2,因此我想通过 oauth 使用 CalDAV。

我已经对 Google 日历 API 做了同样的事情,但只是改编了 Google 提供的 oauth2client 示例:

client_secrets = 'client_secrets.json'
flow = client.flow_from_clientsecrets(client_secrets, scope="",
                                      message=tools.message_if_missing(client_secrets))
storage = file.Storage('calendar_credentials.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
    credentials = tools.run_flow(flow, storage)

http = credentials.authorize(http=build_http())

build_http() 替换为 caldav.DAVClient 的实例是行不通的。内部 request() API 非常不同,调用 caldav 客户端的任何方法在被 authorize() 包裹时都会惨败。所以,问题是:如何将 caldav.DAVClientoauth2client 整合?

关于将 OAuth 与 nextCloud 结合使用的文档也很少。我找到了 this posting,但仍然不清楚什么地方去了。

让我们从配置开始。在 Nextcloud 中,转到安全设置(https://mycloud.example.com/settings/admin/security). There is a section OAuth 2.0 clients. Add a client. You can use any name, e.g. calendar, but it is important that the redirect URI is http://localhost:8080. Why? tools.run_flow() will instantiate an http server to receive the authentication call on this address by default. Click "add". You should now see a new client ID. Copy the client ID and the secret (click the eye icon to reveal) to client_secrets.json 应该如下所示:

{
  "web": {
    "client_id": "stuff copied from Client Identifier",
    "client_secret": "stuff copied from secret",
    "auth_uri": "https://mycloud.example.com/index.php/apps/oauth2/authorize",
    "token_uri": "https://mycloud.example.com/index.php/apps/oauth2/api/v1/token",
    "redirect_uris": []
  }
}

当您现在 运行 问题部分的示例时,您的浏览器应该自动定向到 mycloud.example.com 实例并且应该有一条消息说“您即将授予 calendar 访问您的 mycloud.example.com 帐户。”单击“授予访问权限”。输入您的用户名和密码后,浏览器现在应该被重定向到 http://localhost:8080,您应该会看到消息“身份验证流程已完成。”

备注:

  • 我发现 client_secrets.json 是否以 web 开头没有区别 或 installed。但是,它必须是这两者之一。
  • 显然,redirect_uris 可以留空。

现在是编程题(毕竟这是个程序员论坛。。。)

caldav.DAVClient 的构造函数允许 auth 参数,它应该是 requests.auth.AuthBase 的一个实例。那么让我们创建一个:

from requests.auth import AuthBase


class OAuth(AuthBase):
    def __init__(self, credentials):
        self.credentials = credentials

    def __call__(self, r):
        self.credentials.apply(r.headers)
        return r

现在,我们不再像 Google 中的原始示例那样调用 credentials.authorize(http=build_http()),而是编写

caldav_client = caldav.DAVClient(
    "https://mycloud.example.com/remote.php/dav/",
    auth=OAuth(credentials))

就是这样!我们现在可以写

principal = caldav_client.principal()
calendars = principal.calendars()

original example.