Class CaldavCalendarShareService

java.lang.Object
org.exoplatform.caldav.service.CaldavCalendarShareService

@Service public class CaldavCalendarShareService extends Object
Shares a user's own eXo calendar, read-only, with colleagues connected to the same CalDAV server, by changing who may read the collection that calendar is bound to — through the server's own granting mechanism, confirmed by reading the access list back (EXO-90253).

Every check lives here, and none of them takes anything from the browser but a calendar id and a login:

  • the calendar exists and the caller owns it — a space calendar, owned by its space, is refused, and so is a colleague's;
  • the caller's account is connected, and the calendar is bound either to a collection eXo created for it — an SyncOrigin.EXO pair, active, whose collection carries the slug eXo derives from the calendar's own anchor — or to an imported collection the caller really owns: an active, anchored SyncOrigin.REMOTE pair that is not the meetings mirror, whose ownership is confirmed on the server before its access list is read or anything is changed (only the capability probe, which selects the rule, comes first) (on RFC 3744 servers its DAV:owner is the caller's recorded principal, it is writable for them and it sits under their calendar home; on BlueMind its path is under the caller's own uid, it is no subscription to someone else's calendar, and BlueMind's REST access list gives the caller All or Manage). The collection is resolved from the pair and never named by the request, so a share, a hidden share, the mirror or any other collection cannot be addressed; the client's CalDavClient.writeAcl(org.exoplatform.caldav.client.CalDavEndpoint, org.exoplatform.caldav.model.CalendarSync, java.util.List<org.exoplatform.caldav.client.AccessControlEntry>) applies the pair rule again;
  • the server offers a granting mechanism whose every change eXo confirms (SharingMechanism) — Stalwart's RFC 3744 ACL method, or BlueMind's CS:share confirmed through its REST API;
  • the sharee is an eXo user, not the caller, connected to the same server registration, whose principal was recorded by a discovery (EXO-90243) and is not the caller's own principal — two eXo users on one login would be "sharing" with themselves.

Read-modify-write, never overwrite. RFC 3744 §8.1 has an ACL request replace every entry that is neither protected nor inherited, and Stalwart replaces its grant list the same way. So a grant reads the list, keeps every modifiable entry — including entries eXo did not write, a share made from another client — adds one DAV:read grant, and writes the list back; a revoke removes that principal's read-only grants and nothing else. A list with an entry the client cannot represent is never written. Protected and inherited entries are never sent: the server keeps them.

What a list read over DAV cannot carry back. A server's access list over DAV is a projection of its own rights, and not a faithful one: on Stalwart (source, crates/dav/src/common/acl.rs and crates/jmap-proto/src/object/calendar.rs, main 474dd022) a colleague given "may delete" or "may write all" through JMAP reads back as DAV:write, and that entry written back becomes full write — eXo would widen somebody's rights by sharing with somebody else. Every widening is an entry reading back with more than the read-only privileges, so another principal holding any such entry stops the write (FOREIGN_ACCESS_NOT_PRESERVED), and the owner manages that calendar's access where it was set. What remains is narrowing: rights DAV does not show at all (JMAP "may write own", "may update private", "may RSVP"; the invite and reply parts of a scheduling grant) sit behind a read-only entry and are dropped when it is written back. That is accepted as the lesser loss, and stated here so it is a decision rather than a surprise.

Verified, not claimed. The list is read again after every write, and a grant absent from it — or a revoked grant still in it — is reported as not applied, whatever status the write answered.

Nothing is stored. Who a calendar is shared with is the server's state and anyone's other client can change it, so every listing reads it afresh. Concurrent edits by this node are serialised per collection; edits from another node or another client at the same moment are last writer wins, because RFC 3744 offers no conditional ACL request. Each applied grant and revoke is logged at info: who, to whom, which calendar.

On BlueMind (SharingMechanism.BLUEMIND_SHARE) the change is a POST CS:share naming the colleague by the mail address their own principal publishes, and the proof is the container's access list read back through BlueMind's REST API with the owner's credentials. A colleague sees the calendar only once they subscribed to it in BlueMind, which the listing says (subscriptionRequired).

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    The access list holds an entry eXo cannot write back faithfully.
    static final String
    The server would not let the caller read the calendar's access list.
    static final String
    The calendar does not exist, or was deleted.
    static final String
    The calendar is bound to no collection eXo can share: none eXo created for it, and no active imported one.
    static final String
    The server refused the account's stored credentials.
    static final String
    Another principal holds access beyond seeing the calendar, which writing the list back could change on the server.
    static final String
    The server accepted the change, and the list read back does not hold it.
    static final String
    The caller has no connected CalDAV account.
    static final String
    The imported collection is not the caller's own on the server: another principal owns it, it is read-only for them, it lies outside their calendar home, it is a subscription to someone else's calendar, or the server gives them no right to manage who sees it.
    static final String
    The caller does not own the calendar.
    static final String
    The sharee holds more than read access, granted outside eXo.
    static final String
    The server offers no way to grant access whose changes eXo can confirm.
    static final String
    Neither the server nor eXo's record says which principal the caller is connected as, so a colleague on the caller's own login cannot be told apart.
    static final String
    The sharee is connected to the server as the caller's own principal.
    static final String
    The server refused the change.
    static final String
    The server could not be reached or answered something unusable.
    static final String
    The server named no mail address for the sharee's principal, which is the only way BlueMind's share handler finds a sharee.
    static final String
    The colleague holds access below viewing given outside eXo — seeing when the owner is free, say — which a share from eXo would replace and a later stop would erase.
    static final String
    The sharee is the caller.
    static final String
    The sharee has no recorded identity on the same server.
    static final String
    No sharee was named.
    static final String
    The sharee is not a known, active eXo user.
  • Constructor Summary

    Constructors
    Constructor
    Description
    CaldavCalendarShareService(org.exoplatform.agenda.service.AgendaCalendarService agendaCalendarService, CaldavConnectorStorage caldavConnectorStorage, CaldavSyncStorage caldavSyncStorage, CalDavClient calDavClient, CaldavConnectionIdentityService caldavConnectionIdentityService, org.exoplatform.social.core.manager.IdentityManager identityManager, BlueMindAclClient blueMindAclClient, CaldavPushService caldavPushService, long probeMemoSeconds)
     
  • Method Summary

    Modifier and Type
    Method
    Description
    List<org.exoplatform.caldav.model.CalendarShares.ShareUser>
    candidates(long userIdentityId, String username, long calendarId, String query)
    The colleagues a calendar of the caller's can be shared with: eXo users connected to the same server registration, with a recorded principal that is not the caller's own.
    org.exoplatform.caldav.model.CalendarShares
    grant(long userIdentityId, String username, long calendarId, String shareeUsername)
    Gives one colleague read access to a calendar of the caller's.
    org.exoplatform.caldav.model.CalendarShares
    listShares(long userIdentityId, String username, long calendarId)
    Who a calendar of the caller's is shared with, read from the server now.
    org.exoplatform.caldav.model.CalendarShares
    revoke(long userIdentityId, String username, long calendarId, String shareeUsername)
    Takes read access to a calendar of the caller's away from one colleague.
    shareableCalendarIds(long userIdentityId, String username)
    The caller's calendars that can be shared from eXo: owned, bound to a collection eXo created or to an imported collection they own on the server, on a server offering a mechanism whose changes eXo can confirm.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • CALENDAR_NOT_FOUND

      public static final String CALENDAR_NOT_FOUND
      The calendar does not exist, or was deleted.
      See Also:
    • NOT_OWNER

      public static final String NOT_OWNER
      The caller does not own the calendar.
      See Also:
    • NOT_CONNECTED

      public static final String NOT_CONNECTED
      The caller has no connected CalDAV account.
      See Also:
    • CALENDAR_NOT_ON_SERVER

      public static final String CALENDAR_NOT_ON_SERVER
      The calendar is bound to no collection eXo can share: none eXo created for it, and no active imported one.
      See Also:
    • NOT_OWNED_ON_SERVER

      public static final String NOT_OWNED_ON_SERVER
      The imported collection is not the caller's own on the server: another principal owns it, it is read-only for them, it lies outside their calendar home, it is a subscription to someone else's calendar, or the server gives them no right to manage who sees it.
      See Also:
    • NOT_SUPPORTED

      public static final String NOT_SUPPORTED
      The server offers no way to grant access whose changes eXo can confirm.
      See Also:
    • SHAREE_REQUIRED

      public static final String SHAREE_REQUIRED
      No sharee was named.
      See Also:
    • SHAREE_UNKNOWN

      public static final String SHAREE_UNKNOWN
      The sharee is not a known, active eXo user.
      See Also:
    • SHAREE_IS_OWNER

      public static final String SHAREE_IS_OWNER
      The sharee is the caller.
      See Also:
    • SHAREE_NOT_CONNECTED

      public static final String SHAREE_NOT_CONNECTED
      The sharee has no recorded identity on the same server.
      See Also:
    • SAME_PRINCIPAL

      public static final String SAME_PRINCIPAL
      The sharee is connected to the server as the caller's own principal.
      See Also:
    • NOT_READ_ONLY

      public static final String NOT_READ_ONLY
      The sharee holds more than read access, granted outside eXo.
      See Also:
    • ACL_UNREADABLE

      public static final String ACL_UNREADABLE
      The server would not let the caller read the calendar's access list.
      See Also:
    • ACL_NOT_UNDERSTOOD

      public static final String ACL_NOT_UNDERSTOOD
      The access list holds an entry eXo cannot write back faithfully.
      See Also:
    • SERVER_REFUSED

      public static final String SERVER_REFUSED
      The server refused the change.
      See Also:
    • OWNER_UNKNOWN

      public static final String OWNER_UNKNOWN
      Neither the server nor eXo's record says which principal the caller is connected as, so a colleague on the caller's own login cannot be told apart.
      See Also:
    • SHAREE_ADDRESS_UNKNOWN

      public static final String SHAREE_ADDRESS_UNKNOWN
      The server named no mail address for the sharee's principal, which is the only way BlueMind's share handler finds a sharee.
      See Also:
    • SHAREE_HAS_OTHER_ACCESS

      public static final String SHAREE_HAS_OTHER_ACCESS
      The colleague holds access below viewing given outside eXo — seeing when the owner is free, say — which a share from eXo would replace and a later stop would erase.
      See Also:
    • FOREIGN_ACCESS_NOT_PRESERVED

      public static final String FOREIGN_ACCESS_NOT_PRESERVED
      Another principal holds access beyond seeing the calendar, which writing the list back could change on the server.
      See Also:
    • NOT_APPLIED

      public static final String NOT_APPLIED
      The server accepted the change, and the list read back does not hold it.
      See Also:
    • SERVER_UNAVAILABLE

      public static final String SERVER_UNAVAILABLE
      The server could not be reached or answered something unusable.
      See Also:
    • CREDENTIALS

      public static final String CREDENTIALS
      The server refused the account's stored credentials.
      See Also:
  • Constructor Details

    • CaldavCalendarShareService

      @Autowired public CaldavCalendarShareService(org.exoplatform.agenda.service.AgendaCalendarService agendaCalendarService, CaldavConnectorStorage caldavConnectorStorage, CaldavSyncStorage caldavSyncStorage, CalDavClient calDavClient, CaldavConnectionIdentityService caldavConnectionIdentityService, org.exoplatform.social.core.manager.IdentityManager identityManager, BlueMindAclClient blueMindAclClient, CaldavPushService caldavPushService, @Value("${exo.caldav.share.probeMemoSeconds:300}") long probeMemoSeconds)
      Parameters:
      agendaCalendarService - where the calendar and its owner are read
      caldavConnectorStorage - the caller's connected account
      caldavSyncStorage - the pair binding the calendar to its collection
      calDavClient - the protocol
      caldavConnectionIdentityService - who each eXo user is on the server
      identityManager - the social identities of caller and sharees
      blueMindAclClient - reads a BlueMind calendar's access list back
      caldavPushService - says where the copies of eXo meetings are written
  • Method Details

    • shareableCalendarIds

      public List<Long> shareableCalendarIds(long userIdentityId, String username)
      The caller's calendars that can be shared from eXo: owned, bound to a collection eXo created or to an imported collection they own on the server, on a server offering a mechanism whose changes eXo can confirm.

      What decides whether agenda shows "Share" on a calendar, so it never fails: anything that goes wrong — no account, a server that cannot be reached, agenda failing — answers no calendar, and the entry is simply not offered. The server is asked what one such collection advertises — one OPTIONS, plus a depth-0 PROPFIND where that answer carries no DAV header, as on the BlueMind deployments observed — since what a server supports does not vary between two collections of one account in any server characterised. A collection eXo created is asked first; when a collection fails on its own (gone, or any error status other than 401, 403, 407 and a gateway status) the next calendar is asked, up to MAX_CAPABILITY_PROBES, while refused credentials (a 403 among them: on a read verb it cannot be told from a credential refusal) and an unreachable server end the listing at once, since asking again would only add failed requests. Every share operation asks its own collection again.

      The server's answer — the mechanism it offers, and which imported collections the caller owns — stands for exo.caldav.share.probeMemoSeconds (300 by default; 0 asks every time), since agenda asks on every refresh of its panel and every open of a calendar's menu. What is read from eXo's own storage — the account, the pairs, the calendars — is read every time, so a calendar exported or imported a moment ago appears at once; only a server reconfigured to offer sharing, or an imported collection whose ownership changed on the server, waits out the memo. A failed probe is never remembered.

      Parameters:
      userIdentityId - the caller
      username - the caller's login
      Returns:
      the agenda ids of the shareable calendars, possibly empty
    • listShares

      public org.exoplatform.caldav.model.CalendarShares listShares(long userIdentityId, String username, long calendarId) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException
      Who a calendar of the caller's is shared with, read from the server now.
      Parameters:
      userIdentityId - the caller
      username - the caller's login
      calendarId - the agenda calendar
      Returns:
      the sharees
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when the calendar does not exist
      IllegalAccessException - when the caller does not own it
      IllegalArgumentException - when it is bound to no collection eXo can share
      CaldavShareException - when the account, the server or its answer stops the read
    • grant

      public org.exoplatform.caldav.model.CalendarShares grant(long userIdentityId, String username, long calendarId, String shareeUsername) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException
      Gives one colleague read access to a calendar of the caller's.

      Idempotent: a colleague who can already read it — through a grant eXo made or one made elsewhere — is left as they are and nothing is written. On BlueMind that holds for plain view access only: its access list shows a colleague holding more as reading too, and such a colleague is refused (NOT_READ_ONLY) rather than rewritten, as is one holding only other access given outside eXo (SHAREE_HAS_OTHER_ACCESS).

      Parameters:
      userIdentityId - the caller
      username - the caller's login
      calendarId - the agenda calendar
      shareeUsername - the colleague's login
      Returns:
      the sharees as the server lists them after the grant
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when the calendar does not exist
      IllegalAccessException - when the caller does not own it
      IllegalArgumentException - with a caldav.share.* code when the calendar or the sharee cannot be shared with
      CaldavShareException - when the account, the server or its answer stops the grant
    • revoke

      public org.exoplatform.caldav.model.CalendarShares revoke(long userIdentityId, String username, long calendarId, String shareeUsername) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException
      Takes read access to a calendar of the caller's away from one colleague.

      Removes that colleague's read-only grants and nothing else; a grant that gives them more — made outside eXo — is not eXo's to take back, and the whole request is refused rather than half applied. Idempotent: a colleague with no grant is left alone and nothing is written.

      Parameters:
      userIdentityId - the caller
      username - the caller's login
      calendarId - the agenda calendar
      shareeUsername - the colleague's login
      Returns:
      the sharees as the server lists them after the revoke
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when the calendar does not exist
      IllegalAccessException - when the caller does not own it
      IllegalArgumentException - with a caldav.share.* code when the calendar or the sharee cannot be handled
      CaldavShareException - when the account, the server or its answer stops the revoke
    • candidates

      public List<org.exoplatform.caldav.model.CalendarShares.ShareUser> candidates(long userIdentityId, String username, long calendarId, String query) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException
      The colleagues a calendar of the caller's can be shared with: eXo users connected to the same server registration, with a recorded principal that is not the caller's own.

      Listed per eXo user, while a grant names a principal: two eXo users connected under one DAV login are two candidates, and granting to either gives both access — the server knows the login, not the person. listShares(long, java.lang.String, long) then names every user the grant reached (CalendarShares.CalendarSharee.users()). Whether the picker should say so before the grant, by grouping candidates per principal, is a product decision this method leaves open.

      The server is asked what the collection advertises, the capability check the listing, the grant and the revoke make, so that nobody is listed on a server where eXo offers no sharing. The colleagues themselves are read from eXo's own record of each connection; the server is asked who the caller is only when that is not recorded yet. Bounded by the storage's read of the server's connections; a colleague beyond that bound is not offered.

      Parameters:
      userIdentityId - the caller
      username - the caller's login
      calendarId - the agenda calendar
      query - text their login or full name must contain, ignoring case; blank for everyone
      Returns:
      the candidates, by full name
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when the calendar does not exist
      IllegalAccessException - when the caller does not own it
      IllegalArgumentException - when it is bound to no collection eXo can share
      CaldavShareException - when sharing is not offered on the server, the credentials are refused, the server cannot be reached, the caller's principal cannot be named, or the calendar is an imported one the caller does not own on the server