Skip to content

fix(caldav): allow free-busy requests for other users' principals - #1673

Open
thilo wants to merge 2 commits into
sabre-io:masterfrom
thilo:fix-freebusy-principal-acl
Open

thilo wants to merge 2 commits into
sabre-io:masterfrom
thilo:fix-freebusy-principal-acl

Conversation

@thilo

@thilo thilo commented Sep 11, 2026 •

Copy link
Copy Markdown

Fixes #1185 (also reported as #1601 and sabre-io/Baikal#1360).

Problem

A free-busy request (POST to the outbox) for another user on the same server
answers 3.7;No calendar-home-set property found unless the requester is an
admin. getFreeBusyForEmail() finds the recipient with principalSearch(),
which reads calendar-home-set and schedule-inbox-URL through the ACL. A
principal is readable only by itself ({DAV:}all for {DAV:}owner), so for
any other user the properties come back 403. With hideNodesFromListings the
lookup also triggers an Undefined array key 0 warning in principalSearch().

The existing tests pass because they make the requesting user an admin first.

Change

Run the lookup with the ACL propFind listener removed, the same way
scheduleLocalDelivery() already looks up the recipient's inbox, and put it
back in a finally.

Privacy

This was proposed in #1185 before, and @evert
declined it
for privacy reasons: the default should not let everyone read everyone's
information. I think the change keeps that default, because the principal ACL
is not what decides free-busy access:

  • The principal properties are only used to find the recipient's inbox and
    calendars. They are not sent to the client.
  • Whether free-busy may be queried is decided by the schedule-query-freebusy
    check on the recipient's inbox, which comes right after the lookup. RFC 6638
    defines that privilege on the scheduling inbox for this purpose. If the check
    fails, checkPrivileges() throws NeedPrivileges and the request fails with
    403
    (raised by @m-a-v,
    now covered by a test).
  • sabre's Schedule\Inbox::getACL() already grants schedule-deliver, which
    includes schedule-query-freebusy, to {DAV:}authenticated. The inbox
    already allows the query, and the principal ACL blocked it before that
    check was reached.
  • The response contains only busy periods, not event details.
  • It reveals nothing new about which addresses have accounts. Master already
    answers 3.7;Could not find principal for unknown addresses and
    3.7;No calendar-home-set property found for known ones.

This is a behaviour change: on a default setup, authenticated users can now
query each other's free-busy, as the inbox ACL says they may. To keep
free-busy private, restrict the inbox instead of the principal: grant
schedule-deliver-invite and schedule-deliver-reply rather than
schedule-deliver. Invitations keep working and free-busy queries are refused.
testFailWithoutFreeBusyPrivilege does exactly that.

If you'd rather keep the old behaviour as the default, I can put this behind
an opt-in property on the Schedule plugin instead.

Tests

  • testSucceedWithoutAdmin and testSucceedWithoutAdminWithHiddenNodes in
    FreeBusyRequestTest repeat testSucceed without the admin shortcut, once
    with and once without hideNodesFromListings. Both fail on master and pass
    with the change. They also check that the ACL is active again afterwards.
  • testFailWithoutFreeBusyPrivilege restricts the recipient's inbox as
    described above and expects NeedPrivileges. It fails if the
    schedule-query-freebusy check is removed.
  • tests/Sabre/CalDAV/Schedule passes except
    SchedulingObjectTest::testGetLastModified, which errors on master too
    (PHP 8.3 ctype_digit() deprecation).

Found running Davis (sabre/dav 4.7.1) with macOS Calendar, which shows "There
was a problem accessing availability" when an event has attendees on the same
server.

The free-busy lookup read the recipient's calendar-home-set and
schedule-inbox-URL through the ACL, which lets only the principal itself (or
an admin) read them, so every request from another user answered 3.7. Look
them up with the ACL propFind listener removed, as local delivery already
does; the schedule-query-freebusy check still decides access.

Fixes sabre-io#1185
@thilo

thilo commented Sep 11, 2026

Copy link
Copy Markdown
Author

A downstream data point: Baikal is working around the same bug on its own side in sabre-io/Baikal#1406. It grants the organizer temporary calendar-proxy-read membership on each attendee principal for the duration of the outbox POST, behind an allow_free_busy_lookup option that defaults to on.

That is the same conclusion this PR reaches, that free-busy between users of one server should work without extra configuration, but it has to be re-implemented in each downstream project (it subclasses both the principal backend and the scheduling plugin). If the default here is deliberate, a note in the docs on where to hook in would probably save that work; if not, the inbox privilege already expresses it.

The principal lookup now ignores the ACL, so add a test that an inbox
granting only schedule-deliver-invite and schedule-deliver-reply refuses
the free-busy query with NeedPrivileges. Also wrap the comment in
getFreeBusyForEmail() to 80 columns.
@thilo
thilo force-pushed the fix-freebusy-principal-acl branch from ec87081 to c3c38e6 Compare September 11, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FreeBusy-Report fails with Status 3.7;No calendar-home-set property found

1 participant