Upgrading from SabreDAV 3.1 to 3.2

sabre/dav 3.2 has been released. sabre/dav 3.2 is a new major version, and has several new features. To accommodate those features, a number of backwards-compatibility breaks were needed.

This document is split in three segments. First the new features, then a list of changes as they relate to CalDAV and CardDAV clients, and lastly a list of changes for users who deployed sabre/dav themselves (so server admins or developers using sabre/dav as a dependency).

New features

For a full list of changes, consult the changelog.

Changes for CalDAV and CardDAV clients

We've made some pretty big changes to the iCalendar and vCard validation systems. We've detailed these changes in a separate blogpost. Read the whole thing here:

https://sabre.io/blog/2016/validation-changes/

API Changes and other BC breaks

Database changes

The database model changes to accommodate calendar sharing. If you use the standard Sabre\CalDAV\Backend\PDO you must run the database migration script.

The migration script is a command-line script that's in the bin/ directory of sabredav. The migration script supports both Sqlite and MySQL. To get more information on how to run it, just run:

php migrationto32.php

This will give you a help message with more information on how to invoke the script.

The Href class has changed

If you were using custom properties, and in particular if you made use of the Sabre\DAV\Xml\Property\Href class, this class has now changed a bit.

In the past this class was used for two purposes:

  1. To generate relative URIs to paths that are on the server. For instance, you might have specified principals/userA.
  2. To generate absolute URIs, such as http://example.org/ or mailto:foo@example.org.

To differentiate between the two, you had to specify a second boolean argument to the property to specify that it was to be treated as a absolute path, so:

new Href('principals/userA');
new Href('http://example.org/', true);
new Href('mailto:foo@example.org', true);

This was quite confusing in a number of ways. The biggest one was that the first example actually has different encoding rules than the second and third.

In the case of the first we automatically prepend a base url, but we also assumed that it was a raw local path and we still need to percent-encode everything.

So to bring some sanity into all this, there are now two classes. To specify the same three hrefs, you now use:

new LocalHref('principals/userA');
new Href('http://example.org/');
new Href('mailto:foo@example.org');

Each of these will still result in a <d:href>..</d:href> xml property, but only the first is treated as a 'local sabredav path' that we will urlencode and add the base url to. The Href class is now strictly for URIs that target something outside of the sabre/dav system and we will not touch the string at all.

We were not using all the correct ACL privileges when doing scheduling. This has been corrected in 3.2. If you had custom ACL rules that affected CalDAV scheduling, be aware that the exact privilege definitions may have changed a bit.

We now use all the following CalDAV ACL privileges:

Many other ACL rules have been simplified

Many places now have the {DAV:}all privilege instead of separate {DAV:}read and {DAV:}write privileges.

If you request {DAV:}current-user-privilege-set you should still see a similar list of privileges though.

Support for unauthenticated access

By default it's now possible to specify ACL rules that allow someone who is not authenticated to perform actions on the server.

To do this, simply return a rule from getACL from your node and instead of a principal url, you return the string {DAV:}unauthenticated or {DAV:}all.

To support this new feature the server's behavior had to be changed pretty radically. In particular, before 3.1 it would work as follows:

  1. If the authentication plugin is enabled, check authentication headers.
  2. If auth headers were incorrect or not specified, immediately deny access and return a 401 status.
  3. Later in the request, do an access control check and return a 403 header if access was denied.

From 3.2 the behavior is as follows:

  1. At the start of the request, see if authentication headers were set. If they were, attempt to authenticate the user but don't deny the user if they aren't set or incorrect.
  2. Proceed with the request as normal. Continue until the ACL plugin needs to validate a privilege.
  3. If the current (authenticated or not authenticated) user did not have access to the a specific required privilege, stop the request and:
    1. Return a 401 header if the user was not authenticated.
    2. Return a 403 header if the user was authenticated.

This radically changes the security model, as we actually let requests through, even if users are not authenticated correctly. This will behave correctly for sabre/dav internals because access control is checked everywhere.

However, if you wrote a custom plugin, and this plugin allows the user to do a dangerous operation and you're not integrating with the ACL plugin, you are creating a serious security hole.

Ideally you fix your plugin to leverage ACL, but there are two other workarounds:

  1. If you disable the ACL plugin, authentication will behave as before. We immediately deny the user if they did not authenticate. Disabling ACL could create other security and privilege escalation problems, so be aware.
  2. You can set the $allowUnauthenticatedAccess property to false. Setting it to false means you go back to the 3.1 ACL / Auth behavior, but you lose the public access ability.

Removed $allowAccessToNodesWithoutACL

This setting allowed you to control in the ACL plugin how it should behave for nodes in the tree that did not support ACL. By default it was set to true, but if it were set to false then the default would be to not allow access to system-wide nodes that did not implement Sabre\DAVACL\IACL.

This feature has now been replaced by a 'Default ACL' rule. By default, similar to the old behavior, we assign the {DAV:}all privilege to {DAV:}authenticated.

To mimic setting $allowAccessToNodesWithoutACL to false, as it was possible in the 3.1, you can now just call:

$aclPlugin->setDefaultACL([]);

But because of the nature of this new feature, it's now also possible to specify much more fine-grained permissions for these situations. You might for example choose to only give read access to these nodes, which makes a lot of sense for a lot of standard caldav/carddav servers.

No longer a direct database upgrade path from version 1.6

We're no longer maintaining the migration script from sabre/dav version 1.6 and older. People running that sabre/dav version had 3.5 years to upgrade, so this seems like enough time.

Calendar sharing has gone under heavy modifications

The internal API for calendar sharing has changed. This is important if you wrote your own CalDAV backend and added sharing support for it.

The changes for this have been detailed in a separate blog post, as they were a bit too big to be included in this document.

PDO CalDAV backend has changed the structure of calendar ids.

Note that if this affects you, your code was already broken to begin with. From this point onwards the PDO backend will now return multiple numbers as an array for calendar ids. This is to uniquely identify specific instances of shared calendars.

So if you integrated directly with the PDO backends and somewhere you assumed that the 'id' returned from getCalendarsForUser was always a number, it is now an array. But ideally you should just treat this field as an opaque identifier and don't assign any meaning to it.