pythonoauth-2.0caldavnextcloud

Python client to access CalDAV via OAuth2 on Nextcloud


The canonical examples for using CalDAV always use username/password authentication. However Nextcloud supports OAuth2, therefore I would like to use CalDAV via oauth.

I already have done the same with the Google calendar API, but just adapting the oauth2client sample provided by Google:

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())

by replacing build_http() by an instance of caldav.DAVClient does not work. The internal request() APIs are quite different and calling any method of the caldav client will miserably fail when wrapped by authorize(). So, the question is: how to integrate caldav.DAVClient with oauth2client?

Also documentation on using OAuth with nextCloud is scarce. I have found this posting, but it is still not obvious what goes where.


Solution

  • Let's start with the configuration. In Nextcloud, go to the security settings (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 which should then look like this:

    {
      "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": []
      }
    }
    

    When you now run the example from the question section, your browser should automatically be directed to the mycloud.example.com instance and there should be a message saying "You are about to grant calendar access to your mycloud.example.com account." Click "Grant access". After entering your username and password, the browser should now be redirected to http://localhost:8080 and you should see the message "The authentication flow has completed."

    Notes:

    Now the programming question (this is a programmer's forum after all...)

    The constructor of caldav.DAVClient allows for an auth parameter, which should be an instance of requests.auth.AuthBase. So let's create one:

    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
    

    Now instead of calling credentials.authorize(http=build_http()) as in the original example from Google, we write

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

    That's it! We can now write

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

    as in the original example.