A blog about systems and statements
Integrating DAV and Google Calendar using only Open Source Software
Integrating DAV and Google Calendar using only Open Source Software

Integrating DAV and Google Calendar using only Open Source Software

It's no secret that I'm a huge free software fanatic. And so I was faced a big personal moral dilemma when I started working for a company that uses Google services for most internal admin - Gmail, Contacts, Drive, Documents, and Calendar. I figured causing enough of a ruckus to shift the entire company admin system to free software was going to be a huge uphill battle, so instead I opted for a more quiet and realistic revolution: staying (minimally) connected to my work life on-the-go without installing any proprietary software on my phone.

I can live without my work email, drive, and documents on my mobile, but one thing I cannot live without is my calendar on my mobile. I already have my personal calendar synced on my Android, but since I've started working you could say my calendar has been lacking a lot of information. Not just during-business-hours events - we often have teambuilding events after hours and having two separate calendars on two separate devices to keep track of is just too much for my liking.

Now the easy way out would be to install the Google Calendar app, and sync up with my work account. This is no bueno for two reasons:

  • Proprietary software sucks (here's why), and I will never use it if I don't have to.
  • Syncing my work stuff to my phone this way requires me to install the Corporate Policy Manager app, which gives my company's desktop support staff the ability to monitor what work-related things I do on my phone, and also the ability to wipe my phone remotely. No thank you.

The investigation begins

So now I have my goal: get a unified view of my personal and work calendars on my phone without installing Google Calendar or syncing my work account on my device. The first thing I did was search through F-droid for anything related to Google Calendar. My hope was that someone built a little daemon that would interact with the Google Calendar API to sync to a local Android calendar. No such luck I'm afraid.

The only relevant thing I could find was DAVx5, a super useful app for background syncing remote DAV-based calendars to the local device calendars. At this point I was already a user (and big fan) of DAVx5, specifically using it to sync my personal calendar from my personal server where I self-host Radicale, a really simple DAV server. And so I was more than happy to try it out.

According to the docs, DAVx5 has been tested with Google Calendar, but is not officially supported by either party. Indeed, I was quite surprised to learn that Google has a DAV endpoint available to the public (beyond the normal Calendar API):

Screenshot of the Google Calendar DAV docs

Excitedly, I jumped onto DAVx5 on my phone, and started typing in all my credentials (creating an app-password and everything). To my dismay, all I kept getting was a 401 error. I triple checked all my details, but sadly it just wasn't working. I looked around the docs and eventually found this warning banner which really dropped my smile:

This is exactly the endpoint the DAVx5 docs were referring to 😒:

All hope is not lost

So Google's deprecated an old DAV endpoint, but they seem to be supporting a new one. So what's the problem? Well an embarrassingly long time spent browsing the docs later, I eventually figured out that the problem was that the new Google DAV endpoint only works with OAuth, whereas the previous endpoint supported Basic Auth.

An all-too-easily overlooked paragraph in the Google DAV docs.

Basic Auth is part of the RFC standard for CalDAV, and is well supported across most calendar clients out there (including of course DAVx5). What's not supported across many clients (or any clients, as far as I know), is OAuth authentication for CalDAV. (Read here for the difference between Basic and OAuth). The short version is that the authentication is now a lot more secure, but also a lot more complicated and a lot less compatible with CalDAV.

So what now

The situation is as follows:

  • The only way to automatically access my work calendar is either with the Calendar API, or with this non-standard OAuth CalDAV endpoint.
  • There are no known FOSS clients that sync the Google Calendar API on Android. I feel like writing my own would be reinventing the wheel a little too much. And besides, what's the point of writing a FOSS app if its only purpose is to integrate with a proprietary API?
  • DAVx5 does support CalDAV really well, but does not have OAuth support. I could possibly open a pull request on the app's codebase and write OAuth support myself.
  • Otherwise, if I could somehow add a compatibility networking layer of some sort to "convert" Basic Auth into OAuth on the fly, that would allow DAVx5 to do all the heavy lifting without needing to make any code changes to it.

Looking back at this now, I wonder if the pull request option would not have been better. I have some experience with Android app development from my high school days, but I guess the ramp-up cost of getting to know the DAVx5 codebase put me off. Instead what I decided to do was implement the second option: I.e., somehow build a middleware endpoint that will manage Basic Auth from DAVx5, and manage OAuth to Google servers.

At the time I was solving this problem I was using a tight app called Traefik to manage the networking of my self-hosted apps on my private server. Being absolutely amazed by the power and versatility of Traefik, I decided to use it to solve this auth-compatibility problem. Conveniently, Traefik already has the ability to forward HTTP requests to an arbitrary server (the Google CalDAV endpoint in my case), and it supports configurable Basic Auth for incoming connections (DAVx5 in my case).

Now cool as it is, Traefik does not have a middleware router for managing OAuth for outgoing connections. This, however, did not stop me. The good maintainers of Traefik made the forward-thinking decision of creating a plug-point for middlewares, thus allowing anyone to write arbitrary middlewares for Traefik. The Traefik source code is all in Golang, and luckily I have some experience in Go with a Cards Against Humanity game server I built back in 2020. So writing a plugin it is!

OAuth upstream Traefik plugin

The purpose of this plugin is to manage OAuth upstream (i.e., for outgoing connections). So the idea is basically this: an HTTP request comes in to Traefik from DAVx5 as per usual. My plugin then communicates with Google's OAuth servers to obtain the right token, attaches the token to HTTP request, and sends the request onwards. Traefik then takes this authenticated request and sends it on to the Google CalDAV endpoint. When Google responds, Traefik simply passes the HTTP response back to the client (DAVx5). Here's the full flow for sake of interest:

sequenceDiagram participant B as Downstream client participant O as OAuth Provider participant P as Traefik OAuth Plugin participant U as Upstream server alt First ever request B->>P: Plain request P->>B: 302 B->>O: Auth request O->>B: Success redirect B->>+P: OAuth callback Note right of P: Token & refresh token stored P->>-B: Redirect back to original request end alt Token still valid B->>+P: Plain request Note right of P: Bearer token added P->>-U: Authorised request U->>P: Response P->>B: Response end alt Token expired B->>P: Plain request P->>O: Refresh token O->>+P: Refreshed tokens Note right of P: Tokens updated and bearer added P->>-U: Authorised request U->>P: Response P->>B: Response end

You can peruse the source code here, and see the plugin listing on Traefik's website here. I'm very proud of this plugin, and the fact that it has even been by others! (although I have no clue what for)

Putting it all together

Very few things in this life give me as much pleasure as getting a hacked-together solution working for my own purposes. I typed my work credentials into DAVx5, set the URL to my private server, and voila! It syncs! Every request made by DAVx5 gets converted to OAuth by my wonderful plugin, and then sent on to Google, allowing DAVx5 to seamlessly sync my work calendar that lives on Google without needing to worry about their non-standard auth requirement.

And another wonderful feature I realised later is that this allows me full read+write access to my work calendar from any CalDAV app, since Google's endpoint is fully RFC compliant (save for the OAuth bit). So now I can use Simple Calendar on my Android, or Thunderbird on my PC, and see a nice aggregated overview of my personal and work calendars. Plus I can do any read/write operations I want, as if it was a just a pair of normal CalDAV calendars. Without touching a single piece of dirty proprietary software.

A screenshot of Simple Calendar (Android) showcasing my personal (green) and work (yellow) calendars in the same view. Some personal and post-NDA content redacted.

Banner image credit: Refika Çivici

Maak 'n opvolg-bydrae

Jou e-posadres sal nie gepubliseer word nie. Verpligte velde word met * aangedui