From aed1fa463983187ea925b9449537ea170b089a8e Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 11 Nov 2025 11:19:03 -0500 Subject: [PATCH 01/16] fix: correct webhook listener event descriptions and examples Updated the webhook listeners documentation for clarity and consistency, including corrections to event descriptions and examples. Fixes: - nextcloud/server#56371 - https://github.com/nextcloud/server/pull/53648#pullrequestreview-2953626347 Signed-off-by: Josh --- admin_manual/webhook_listeners/index.rst | 862 ++++++++--------------- 1 file changed, 293 insertions(+), 569 deletions(-) diff --git a/admin_manual/webhook_listeners/index.rst b/admin_manual/webhook_listeners/index.rst index a9a43d164..8d5d75d53 100644 --- a/admin_manual/webhook_listeners/index.rst +++ b/admin_manual/webhook_listeners/index.rst @@ -7,7 +7,7 @@ Webhook Listeners Introduction ------------ -Nextcloud supports sending notifications to external services whenever something +Nextcloud supports sending notifications to external services whenever something important happens, such as when files are changed or updated. Overview @@ -20,13 +20,6 @@ can set up custom HTTP notifications (webhooks) that are triggered by specific internal events, allowing seamless integration with other platforms and automation of workflows without manual intervention. -The Webhook Listeners app enables your Nextcloud server to automatically notify -external services whenever important events - such as file changes, uploads, or -deletions - occur in your instance. By configuring webhook listeners, administrators -can set up custom HTTP notifications (webhooks) that are triggered by specific -internal events, allowing seamless integration with other platforms and automation of -workflows without manual intervention. - The app works by monitoring Nextcloud's event system and dispatching HTTP requests to defined endpoints whenever a matching event takes place. Management and configuration of webhook listeners are handled via the Nextcloud OCS API and @@ -46,7 +39,7 @@ Enable the ``webhook_listeners`` app that comes bundled with Nextcloud - e.g. Listening to events ------------------- -You can use the OCS API to add webhooks for specific events. See: +You can use the OCS API to add webhooks for specific events. See: `Register a new webhook `_. .. TODO ON RELEASE: Update version number above upon release. @@ -79,7 +72,7 @@ because you are inside a regular expression. You can also use additional comparison operators (``$e``, ``$ne``, ``$gt``, ``$gte``, ``$lt``, ``$lte``, ``$in``, ``$nin``) as well as logical operators (``$and``, ``$or``, ``$not``, ``$nor``). For example, use ``{ "time": { "$lt": 1711971024 } }`` to accept -only events prior to April 1st, 2024, and ``{ "time": { "$not": { "$lt": 1711971024 } }}`` +only events prior to April 1st, 2024, and ``{ "time": { "$not": { "$lt": 1711971024 } }}`` to accept events after April 1st, 2024. Speeding up webhook dispatch @@ -94,9 +87,9 @@ The following command will launch a worker for the webhook call background job: Screen or tmux session ^^^^^^^^^^^^^^^^^^^^^^ -Run the following ``occ`` command inside a screen or tmux session, preferably four +Run the following ``occ`` command inside a screen or tmux session, preferably four or more times, to enable parallel processing of multiple requests by different users -or the same user. It is best to run one command per screen session or tmux window/pane +or the same user. It is best to run one command per screen session or tmux window/pane to keep logs visible and make each worker easy to restart. .. code-block:: @@ -172,571 +165,302 @@ It is recommended to restart this worker at least once a day to make sure code c Nextcloud Webhook Events ------------------------ -This is an exhaustive list of available events. It features the event ID and the available variables for filtering. +This is list of typically available events. It features the event ID and the available variables for filtering. - * OCA\\Forms\\Events\\FormSubmittedEvent +.. note:: - .. code-block:: text + In addition to the events listed below (which are provided by Nextcloud server and included apps), optional apps may register their own webhook-compatible events. The exact set of events available on your server will depend on your Nextcloud version and installed apps. If you need to discover available events, consult your app documentation or refer to your app source code or the Nextcloud developer documentation. - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "form": array{ - "id": int, - "hash": string, - "title": string, - "description": string, - "ownerId": string, - "fileId": string|null, - "fileFormat": string|null, - "created": int, - "access": int, - "expires": int, - "isAnonymous": bool, - "submitMultiple": bool, - "showExpiration": bool, - "lastUpdated": int, - "submissionMessage": string|null, - "state": int, - }, - "submission": array{ - "id": int, - "formId": int, - "userId": string, - "timestamp": int, - }, - } - } +**Note:** Example payloads below are based on actual webhook HTTP POST payloads, using real JSON and data types. Field types are indicated in example objects by their value type: for example, ``"id": 123`` is an integer, not a string. - * OCA\\Tables\\Event\\RowAddedEvent +Node (Files / Folders) Events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. code-block:: text +Node events include those that act on a single node as well as those that act on two nodes (such as copy, rename, and restore). In single-node events, the payload includes a single ``node`` object with information about the affected file or folder. In two-node events, the payload includes both a ``source`` object (the original node) and a ``target`` object (the resulting node after the operation). The examples below demonstrate these unique payload formats. - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "tableId": int, - "rowId": int, - "previousValues": null|array, - "values": null|array - } - } +**1. Single-node events** - * OCA\\Tables\\Event\\RowDeletedEvent +Applies to: - .. code-block:: text +- ``OCP\Files\Events\Node\BeforeNodeCreatedEvent`` +- ``OCP\Files\Events\Node\BeforeNodeTouchedEvent`` +- ``OCP\Files\Events\Node\BeforeNodeWrittenEvent`` +- ``OCP\Files\Events\Node\BeforeNodeReadEvent`` +- ``OCP\Files\Events\Node\BeforeNodeDeletedEvent`` +- ``OCP\Files\Events\Node\NodeCreatedEvent`` +- ``OCP\Files\Events\Node\NodeTouchedEvent`` +- ``OCP\Files\Events\Node\NodeWrittenEvent`` +- ``OCP\Files\Events\Node\NodeDeletedEvent`` - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "tableId": int, - "rowId": int, - "previousValues": null|array, - "values": null|array +.. code-block:: json + + { + "event": "NodeCreatedEvent", + "node": { + "id": 437, + "path": "/admin/files/test-webhook.txt" + } + } + +Where: + +- ``"id"`` is an integer (unique node ID) +- ``"path"`` is a string (file/folder path) + +.. note:: + + In some events (such as ``NodeDeletedEvent`` or when the node refers to a non-existent file or folder), the ``node`` object may not have an ``id`` field. This happens when the node no longer exists in the filesystem/database, and is represented in Nextcloud by an internal ``NonExistingFile`` or ``NonExistingFolder``. In such cases, only the ``path`` field will be present. Always check for the presence of the ``id`` field in these payloads. Usually, the corresponding ``Before*`` event (such as ``BeforeNodeDeletedEvent``) can be used if you require the ``id`` of the node before deletion. + +Example of a deleted node webhook payload: + +.. code-block:: json + + { + "event": "NodeDeletedEvent", + "node": { + "path": "/user/files/oldfile.txt" + } + } + +**2. Two-node events** + +Applies to: + +- ``OCP\Files\Events\Node\NodeCopiedEvent`` +- ``OCP\Files\Events\Node\NodeRenamedEvent`` +- ``OCP\Files\Events\Node\NodeRestoredEvent`` +- ``OCP\Files\Events\Node\BeforeNodeCopiedEvent`` +- ``OCP\Files\Events\Node\BeforeNodeRestoredEvent`` +- ``OCP\Files\Events\Node\BeforeNodeRenamedEvent`` + +.. code-block:: json + + { + "event": "NodeRestoredEvent", + "source": { + "id": 399, + "path": "/admin/files/Deleted/myfile.txt" + }, + "target": { + "id": 437, + "path": "/admin/files/myfile.txt" + } + } + +Where: + +- ``"source"`` is an object representing the original node, with ``"id"`` (integer) and ``"path"`` (string) +- ``"target"`` is an object representing the resulting/copied/restored/renamed node, with ``"id"`` (integer) and ``"path"`` (string) + +.. note:: + + For some two-node events, the ``source`` or ``target`` node may not have an ``id`` field, for instance if the node was deleted or is otherwise missing from the filesystem. Always check for the presence of the ``id`` field in these objects. Typically, if you need the ``id`` of a node just before deletion or change, the respective ``Before*`` event (such as ``BeforeNodeRenamedEvent``) will include it. + +Example of a two-node event (NodeRenamedEvent) where the source node is missing: + +.. code-block:: json + + { + "event": "NodeRenamedEvent", + "source": { + "path": "/user/files/previousname.txt" + }, + "target": { + "id": 599, + "path": "/user/files/newname.txt" + } + } + +.. note:: + + Only these fields are guaranteed by Nextcloud core. Additional fields may appear if added by custom plugins or future versions. + +System Tag Events +~~~~~~~~~~~~~~~~~ + +* ``OCP\SystemTag\TagAssignedEvent`` +* ``OCP\SystemTag\TagUnassignedEvent`` + +.. code-block:: json + + { + "event": "TagAssignedEvent", + "objectType": "files", + "objectIds": ["437","438"], + "tagIds": [3,17] + } + +Calendar Object Events +~~~~~~~~~~~~~~~~~~~~~~ + +Calendar object events use two distinct payload formats, depending on the event. + +**Standard payload** + +Applies to: + +- ``OCP\Calendar\Events\CalendarObjectCreatedEvent`` +- ``OCP\Calendar\Events\CalendarObjectDeletedEvent`` +- ``OCP\Calendar\Events\CalendarObjectMovedToTrashEvent`` +- ``OCP\Calendar\Events\CalendarObjectRestoredEvent`` +- ``OCP\Calendar\Events\CalendarObjectUpdatedEvent`` + +.. code-block:: json + + { + "event": "CalendarObjectCreatedEvent", + "user": { + "uid": "david", + "displayName": "David" + }, + "time": 1700100000, + "eventData": { + "calendarId": 9, + "calendarData": { + "id": 9, + "uri": "work", + "{http://calendarserver.org/ns/}getctag": "1736283", + "{http://sabredav.org/ns}sync-token": 9651, + "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": "VEVENT,VTODO", + "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": "opaque" + }, + "shares": [ + { + "href": "mailto:alice@example.com", + "commonName": "Alice", + "status": 2, + "readOnly": false, + "{http://owncloud.org/ns}principal": "principal:users/alice", + "{http://owncloud.org/ns}group-share": false + } + ], + "objectData": { + "id": 22, + "uri": "event-20251111T100000Z.ics", + "lastmodified": 1700099500, + "etag": "19fa45b394", + "calendarid": 9, + "size": 4096, + "component": "VEVENT", + "classification": 0 } } - - * OCA\\Tables\\Event\\RowUpdatedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "tableId": int, - "rowId": int, - "previousValues": null|array, - "values": null|array - } - } - - * OCP\\Calendar\\Events\\CalendarObjectCreatedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "calendarId": int, - "calendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "shares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Calendar\\Events\\CalendarObjectDeletedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "calendarId": int, - "calendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "shares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Calendar\\Events\\CalendarObjectMovedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "sourceCalendarId": int, - "sourceCalendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "targetCalendarId": int, - "targetCalendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "sourceShares": list, - "targetShares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Calendar\\Events\\CalendarObjectMovedToTrashEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "calendarId": int, - "calendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "shares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Calendar\\Events\\CalendarObjectRestoredEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "calendarId": int, - "calendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "shares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Calendar\\Events\\CalendarObjectUpdatedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "calendarId": int, - "calendarData": array{ - "id": int, - "uri": string, - "{http://calendarserver.org/ns/}getctag": string, - "{http://sabredav.org/ns}sync-token": int, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": 'Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet', - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": 'Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp' - "{urn:ietf:params:xml:ns:caldav}calendar-timezone": string|null - }, - "shares": list, - "objectData": array{ - "id": int, - "uri": string, - "lastmodified": int, - "etag": string, - "calendarid": int, - "size": int, - "component": string|null, - "classification": int - } - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeCreatedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeTouchedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeReadEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeCreatedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeTouchedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeWrittenEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeDeletedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "node": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeCopiedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeRestoredEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\NodeRenamedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeCopiedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeRestoredEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\Files\\Events\\Node\\BeforeNodeRenamedEvent - - .. code-block:: text - - array{ - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "source": array{"id": string, "path": string} - "target": array{"id": string, "path": string} - } - } - - * OCP\\SystemTag\\TagAssignedEvent - - .. code-block:: text - - array { - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "objectType": string (e.g. 'files'), - "objectIds": string[], - "tagIds": int[], - } - } - - * OCP\\SystemTag\\TagUnassignedEvent - - .. code-block:: text - - array { - "user": array {"uid": string, "displayName": string}, - "time": int, - "event": array{ - "class": string, - "objectType": string (e.g. 'files'), - "objectIds": string[], - "tagIds": int[], - } - } + } + +**Distinct payload for two-calendar events** + +Applies to: + +- ``OCP\Calendar\Events\CalendarObjectMovedEvent`` + +.. code-block:: json + + { + "event": "CalendarObjectMovedEvent", + "sourceCalendarId": 9, + "sourceCalendarData": { + "id": 9, + "uri": "work" + }, + "targetCalendarId": 11, + "targetCalendarData": { + "id": 11, + "uri": "meetings" + }, + "sourceShares": [ + { + "href": "mailto:alice@example.com", + "commonName": "Alice" + } + ], + "targetShares": [ + { + "href": "mailto:bob@example.com", + "commonName": "Bob" + } + ], + "objectData": { + "id": 22, + "uri": "event-20251111T100000Z.ics" + } + } + +Forms App Events +~~~~~~~~~~~~~~~~ + +When the optional ``forms`` app is installed: + +* ``OCA\Forms\Events\FormSubmittedEvent`` + +.. code-block:: json + + { + "event": "FormSubmittedEvent", + "user": { + "uid": "bob", + "displayName": "Bob" + }, + "time": 1700001234, + "eventData": { + "class": "OCA\\Forms\\Events\\FormSubmittedEvent", + "form": { + "id": 51, + "hash": "abc123def456", + "title": "Employee Feedback", + "description": "Annual employee feedback form.", + "ownerId": "alice", + "fileId": 1002, + "fileFormat": "pdf", + "created": 1700001000, + "access": 0, + "expires": 1702606600, + "isAnonymous": false, + "submitMultiple": false, + "showExpiration": true, + "lastUpdated": 1700001200, + "submissionMessage": null, + "state": 1 + }, + "submission": { + "id": 220, + "formId": 51, + "userId": "bob", + "timestamp": 1700001234 + } + } + } + +Tables App Events +~~~~~~~~~~~~~~~~~ + +When the optional ``tables`` app is installed: + +- ``OCA\Tables\Event\RowAddedEvent`` +- ``OCA\Tables\Event\RowDeletedEvent`` +- ``OCA\Tables\Event\RowUpdatedEvent`` + +.. code-block:: json + + { + "event": "RowAddedEvent", + "user": { + "uid": "carol", + "displayName": "Carol" + }, + "time": 1700054321, + "eventData": { + "class": "OCA\\Tables\\Event\\RowAddedEvent", + "tableId": 34, + "rowId": 7, + "previousValues": null, + "values": { + "0": "Project X", + "1": 2025, + "2": "active" + } + } + } + +.. note:: + + For filtering or automation, always check the actual payload you receive, as it matches the JSON examples above, not PHPDoc or internal PHP array type style. From f06db6001a26e9b2ad51aa6f44682bee8b19191f Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Wed, 11 Mar 2026 00:53:30 -0400 Subject: [PATCH 02/16] docs(admin): improve property scope coverage Signed-off-by: Josh Richards --- .../profile_configuration.rst | 179 ++++++++++++++++-- 1 file changed, 166 insertions(+), 13 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 48c222617..676e6b756 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -49,20 +49,157 @@ all available ``config.php`` options. Property scopes --------------- -User properties (Full name, Address, Website, Role, …) have specific visibility scopes (Private, Local, Federated, Published). +User properties (Full name, Address, Website, Role, …) have visibility scopes: +Private, Local, Federated, Published. -The visibility scopes are explained below: +These scopes are evaluated per attribute. A profile being reachable does not imply +that all its attributes are visible. + +The visibility scopes are: :Private: - Contact details visible locally only -:Local: - Contact details visible locally and through public link access on local instance -:Federated: - Contact details visible locally, through public link access and on trusted federated servers. -:Published: - Contact details visible locally, through public link access, on trusted federated servers and published to the public lookup server. + Most restrictive scope. Not exposed through public profile contexts, federation, + or the public lookup server. -The default values for each property for each new user is listed below, but you should consult the declaration of the ``DEFAULT_SCOPES`` constant in the ``OC\Accounts\AccountManager`` class (`see the code `_) to make sure these are up-to-date. + On local-instance user-to-user surfaces, ``Private`` data is not generally visible + to all local users. Visibility requires an authenticated requester and a + server-recognized known-user relationship with the target user. +:Local: + Contact details visible on the local instance and through public share-links + (where profile/account attributes are inherently required - i.e. as file + owner/uploader metadata, etc.). Not shared to federated servers and not published + to the public lookup server. +:Federated: + Contact details visible on the local instance, through local public-link contexts, + and on trusted federated servers. +:Published: + Contact details visible on the local instance, through local public-link contexts, + on trusted federated servers, and published to the public lookup server. + +.. important:: + A reachable profile does not mean all attributes are public. Each attribute is + filtered by its own scope, and effective visibility can also depend on the + consuming feature. + +.. important:: + On profile surfaces, the effective visibility is the more restrictive of + profile-visibility settings and property scope. + +Scope audience overview +~~~~~~~~~~~~~~~~~~~~~~~ + ++------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ +| Scope | User themself (*) | Other users on same local instance | Public link/public | Trusted federation | Public lookup server | ++============+===================+=============================================================+====================+=====================+======================+ +| Private | Yes | Limited: authenticated + known-user relation required | No | No | No | ++------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ +| Local | Yes | Yes | Yes | No | No | ++------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ +| Federated | Yes | Yes | Yes | Yes | No | ++------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ +| Published | Yes | Yes | Yes | Yes | Yes | ++------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ + +(*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. + +Known-user relation (for ``Private``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For ``Private`` properties, Nextcloud may allow visibility on specific local feature +paths only when the requester is considered a *known user* of the target user. + +In practical terms, this relation is derived by server contact-matching logic and is +directional (A known to B does not imply B known to A). Users are always known to +themselves. + +What local users can see +~~~~~~~~~~~~~~~~~~~~~~~~ + +A common question is what one user can see about another user on the same instance. + +In general, profile visibility is controlled by each property's scope, but the +exact UI/API surface depends on the consuming feature (for example profile page, +share dialogs, search, mentions, Contacts, and other integrations). + +For local users on the same instance: + +- ``Private``: not generally visible to all local users; visibility is restricted + to authenticated users that satisfy the known-user relation for that feature path. +- ``Local``: visible on the local instance. +- ``Federated``: visible on the local instance (and also shared with trusted federated servers). +- ``Published``: visible on the local instance (and also federated + public lookup). + +Verification workflow for administrators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because effective visibility can vary by feature path, administrators should verify +scope behavior in their own deployment. + +Recommended test procedure: + +1. Create test users: + + - ``alice`` (target profile owner) + - ``bob`` (authenticated local user) + - ``charlie`` (second local user for control) + +2. As ``alice``, set distinct test values for profile fields and assign different + scopes (Private, Local, Federated, Published). +3. Verify as ``alice``: + + - Confirm owner-visible values as expected. + +4. Verify as ``bob`` (authenticated local user): + + - Check the feature paths used in your instance (for example profile page, + user card, share dialog, search, mentions, Contacts integrations). + - Confirm ``Local/Federated/Published`` fields are visible where expected. + - Confirm ``Private`` fields are visible only on paths that satisfy the known-user + relation and other feature constraints. + +5. Verify as unauthenticated user (private browser session): + + - Confirm only public-appropriate fields are visible. + +6. Verify federation/publication behavior (if enabled): + + - From a trusted federated server, confirm Federated/Published behavior. + - Confirm only Published fields are exposed to the public lookup server. + +7. Re-test with a newly created user after changing + ``account_manager.default_property_scope``: + + - Confirm new defaults apply only to newly initialized accounts. + - Confirm existing users retain stored scopes unless explicitly changed. + +.. tip:: + Keep one "scope matrix" test account in staging and re-run this checklist after upgrades. + +Scope defaults and precedence +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Visibility is determined per property using this order: + +1. **Server defaults** from ``OC\Accounts\AccountManager::DEFAULT_SCOPES``. +2. **Administrator default override** via ``account_manager.default_property_scope``. +3. **User-set value** in personal/profile settings (subject to server-side constraints). + +Practical implications: + +- Admin overrides in ``account_manager.default_property_scope`` are applied at account + initialization and therefore affect **new users**. +- Existing users keep already stored scopes unless changed explicitly. +- ``PROPERTY_DISPLAYNAME`` and ``PROPERTY_EMAIL`` cannot be ``Private``; server-side + validation/enforcement requires at least ``Local``. + +Default scope values +~~~~~~~~~~~~~~~~~~~~ + +Default values are defined in server code and may change over time. The authoritative +source is the ``DEFAULT_SCOPES`` constant in ``OC\Accounts\AccountManager``. The latest +version is `here `_). + +Example defaults (verify against your deployed version): +--------------+--------------------------+ | Property | Default visibility scope | @@ -81,6 +218,10 @@ The default values for each property for each new user is listed below, but you +--------------+--------------------------+ | Twitter | Local | +--------------+--------------------------+ +| Bluesky | Local | ++--------------+--------------------------+ +| Fediverse | Local | ++--------------+--------------------------+ | Organisation | Local | +--------------+--------------------------+ | Role | Local | @@ -89,14 +230,26 @@ The default values for each property for each new user is listed below, but you +--------------+--------------------------+ | Biography | Local | +--------------+--------------------------+ +| Birthdate | Local | ++--------------+--------------------------+ +| Pronouns | Federated | ++--------------+--------------------------+ -If you'd like to override the value for one or several default visibility scopes, use the ``account_manager.default_property_scope`` ``config.php`` configuration key, which defaults to an empty array: +Override defaults in ``config.php`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To override one or several default visibility scopes for *new users*, use +``account_manager.default_property_scope`` (default: empty array): .. code-block:: php 'account_manager.default_property_scope' => [ \OCP\Accounts\IAccountManager::PROPERTY_PHONE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, - \OCP\Accounts\IAccountManager::PROPERTY_ROLE => \OCP\Accounts\IAccountManager::SCOPE_FEDERATED + \OCP\Accounts\IAccountManager::PROPERTY_ROLE => \OCP\Accounts\IAccountManager::SCOPE_FEDERATED, ] -In the above example, the phone and role properties are respectively overwritten to the private and federated scopes. Note that these changes will only apply to *new* users, not existing ones. +In the above example, phone and role are overwritten to ``Private`` and +``Federated`` respectively. + +.. note:: + Use ``\OCP\Accounts\IAccountManager`` constants for both property keys and scope values. From 3e7b5eb4ea8f6351997ed8f8bd98965c94412e05 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Wed, 11 Mar 2026 01:48:04 -0400 Subject: [PATCH 03/16] docs(admin): add FAQ for locking down property scopes Signed-off-by: Josh Richards --- .../profile_configuration.rst | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 676e6b756..2b6998ecf 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -253,3 +253,69 @@ In the above example, phone and role are overwritten to ``Private`` and .. note:: Use ``\OCP\Accounts\IAccountManager`` constants for both property keys and scope values. + +FAQ: How do I lock profile visibility down as tightly as possible? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your goal is maximum privacy: + +1. Disable profiles globally (strictest option): + + .. code-block:: php + + 'profile.enabled' => false, + + Effect: + + - Profile functionality is removed. + - Profile-based discoverability/usability features are reduced accordingly. + +2. If profiles must remain enabled, set restrictive defaults for new users: + + .. code-block:: php + + 'account_manager.default_property_scope' => [ + \OCP\Accounts\IAccountManager::PROPERTY_ADDRESS => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_PHONE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_WEBSITE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_TWITTER => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_BLUESKY => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_FEDIVERSE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_ORGANISATION => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_ROLE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_HEADLINE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_BIOGRAPHY => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_BIRTHDATE => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_PRONOUNS => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + \OCP\Accounts\IAccountManager::PROPERTY_AVATAR => \OCP\Accounts\IAccountManager::SCOPE_PRIVATE, + ] + + Notes: + + - ``PROPERTY_DISPLAYNAME`` and ``PROPERTY_EMAIL`` cannot be set to ``Private``; server-side enforcement requires at least ``Local``. + - Defaults apply to **new users**. Existing users keep stored scopes unless changed. + +What becomes limited when you lock it down? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +With more restrictive scopes (especially ``Private``), expect reduced visibility in: + +- User discovery/search/user cards +- Share dialogs and mention/autocomplete context +- Public-share pages showing owner/profile metadata +- Federated visibility of profile attributes +- Public lookup publication (only ``Published`` appears there) + +In short: tighter privacy reduces profile-based convenience and discoverability. + +Recommended rollout +^^^^^^^^^^^^^^^^^^^ + +- Test with staging accounts first (owner, local user, unauthenticated user, federated peer). +- Communicate behavior changes to users. +- Re-test after upgrades, because profile-consuming features can evolve. + +.. comment + - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) + - Since default visibility scope changes only apply to new users, perhaps we can cover whether their's a migration path for existing users? + - How do scopes interact with the system address book? From 8a3d3925cabe7147bc607c1731a9f1e7620ac598 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 08:47:41 -0400 Subject: [PATCH 04/16] docs(admin): improve Profile configuration section Signed-off-by: Josh Richards --- .../profile_configuration.rst | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 2b6998ecf..882b16b20 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -4,46 +4,64 @@ Profile configuration ===================== -The user profile presents the information of a user and is enabled by default -for all users. Users may individually enable or disable their profile in their -Personal info settings under the Personal settings section. -As an administrator you may change the default for new users and may also -disable profile globally to remove all profile functionality. +The user profile displays information about an account. +Profiles are enabled by default. -Profile properties are also written into the :ref:`system address book`. +Users can enable or disable their own profile in **Personal settings** under +**Personal info**. -.. note:: If not disabled, the profile is publicly visible. - The visibility of the individual profile attributes can be either controlled - by the assigned visibility scopes (e.g. "Private" will disable public access), - or by the user defined profile visibility. +As an administrator, you can: + +- set the default profile state for new users, and +- disable profiles globally. + +Profile data can also be used by other features (for example the +:ref:`system address book`), but what is exposed depends +on privacy controls. + + +.. note:: + Profile visibility is layered. + + - **Profile enablement** controls whether profile functionality is available. + - **Profile field visibility settings** control whether a field is shown. + - **Account property scopes** (for example ``private``, ``local``, ``federated``, + ``published``) define the intended audience for each property. + - **Discovery restrictions** (for example sharing/autocomplete enumeration rules) + can further reduce what other users can find or see. + + In short: effective visibility is the most restrictive result of all applicable controls. Configuration ------------- -To enable or disable profile by default for new users switch the toggle in -Basic settings under the Administration settings section. +Set profile default for new users +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Administration settings** → **Basic settings**, use the profile default toggle. .. figure:: ../images/profile_default_setting.png -You may also run the ``occ`` command below instead to change the default to ``false``: +You can also set this via ``occ``: :: occ config:app:set settings profile_enabled_by_default --value="0" -Please refer to :doc:`../occ_command` for all available -``occ`` commands. +Use ``--value="1"`` to enable it by default again. -To disable profile globally add the following line to your ``config.php`` +See :doc:`../occ_command` for more ``occ`` usage details. + +Disable profiles globally +^^^^^^^^^^^^^^^^^^^^^^^^^ + +To disable profile functionality for all users, add this to ``config.php``: :: 'profile.enabled' => false, -Please refer to :doc:`../configuration_server/config_sample_php_parameters` for -all available ``config.php`` options. - .. _profile-property-scopes: Property scopes @@ -86,7 +104,7 @@ The visibility scopes are: profile-visibility settings and property scope. Scope audience overview -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ +------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ | Scope | User themself (*) | Other users on same local instance | Public link/public | Trusted federation | Public lookup server | @@ -103,7 +121,7 @@ Scope audience overview (*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. Known-user relation (for ``Private``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For ``Private`` properties, Nextcloud may allow visibility on specific local feature paths only when the requester is considered a *known user* of the target user. @@ -113,7 +131,7 @@ directional (A known to B does not imply B known to A). Users are always known t themselves. What local users can see -~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^ A common question is what one user can see about another user on the same instance. @@ -130,7 +148,7 @@ For local users on the same instance: - ``Published``: visible on the local instance (and also federated + public lookup). Verification workflow for administrators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Because effective visibility can vary by feature path, administrators should verify scope behavior in their own deployment. @@ -176,7 +194,7 @@ Recommended test procedure: Keep one "scope matrix" test account in staging and re-run this checklist after upgrades. Scope defaults and precedence -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Visibility is determined per property using this order: @@ -193,7 +211,7 @@ Practical implications: validation/enforcement requires at least ``Local``. Default scope values -~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^ Default values are defined in server code and may change over time. The authoritative source is the ``DEFAULT_SCOPES`` constant in ``OC\Accounts\AccountManager``. The latest @@ -236,7 +254,7 @@ Example defaults (verify against your deployed version): +--------------+--------------------------+ Override defaults in ``config.php`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To override one or several default visibility scopes for *new users*, use ``account_manager.default_property_scope`` (default: empty array): @@ -255,7 +273,7 @@ In the above example, phone and role are overwritten to ``Private`` and Use ``\OCP\Accounts\IAccountManager`` constants for both property keys and scope values. FAQ: How do I lock profile visibility down as tightly as possible? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If your goal is maximum privacy: @@ -296,7 +314,7 @@ If your goal is maximum privacy: - Defaults apply to **new users**. Existing users keep stored scopes unless changed. What becomes limited when you lock it down? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ With more restrictive scopes (especially ``Private``), expect reduced visibility in: @@ -309,13 +327,13 @@ With more restrictive scopes (especially ``Private``), expect reduced visibility In short: tighter privacy reduces profile-based convenience and discoverability. Recommended rollout -^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~ - Test with staging accounts first (owner, local user, unauthenticated user, federated peer). - Communicate behavior changes to users. - Re-test after upgrades, because profile-consuming features can evolve. -.. comment +.. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - Since default visibility scope changes only apply to new users, perhaps we can cover whether their's a migration path for existing users? - How do scopes interact with the system address book? From 9036551d71ff3ecbbd14fa8feaef725f18573756 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 08:51:12 -0400 Subject: [PATCH 05/16] docs(admin): additional edits Signed-off-by: Josh Richards --- admin_manual/configuration_user/profile_configuration.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 882b16b20..dcc7fd457 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -326,13 +326,6 @@ With more restrictive scopes (especially ``Private``), expect reduced visibility In short: tighter privacy reduces profile-based convenience and discoverability. -Recommended rollout -~~~~~~~~~~~~~~~~~~~ - -- Test with staging accounts first (owner, local user, unauthenticated user, federated peer). -- Communicate behavior changes to users. -- Re-test after upgrades, because profile-consuming features can evolve. - .. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - Since default visibility scope changes only apply to new users, perhaps we can cover whether their's a migration path for existing users? From d9bafb758b06cbe9fd18ef64604a777231db2c8c Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 09:26:11 -0400 Subject: [PATCH 06/16] chore(admin): Profile chapter edits continued Signed-off-by: Josh Richards --- .../profile_configuration.rst | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index dcc7fd457..9604b03e1 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -4,7 +4,6 @@ Profile configuration ===================== - The user profile displays information about an account. Profiles are enabled by default. @@ -20,7 +19,6 @@ Profile data can also be used by other features (for example the :ref:`system address book`), but what is exposed depends on privacy controls. - .. note:: Profile visibility is layered. @@ -29,7 +27,7 @@ on privacy controls. - **Account property scopes** (for example ``private``, ``local``, ``federated``, ``published``) define the intended audience for each property. - **Discovery restrictions** (for example sharing/autocomplete enumeration rules) - can further reduce what other users can find or see. + can further reduce what other accounts can find or see. In short: effective visibility is the most restrictive result of all applicable controls. @@ -67,7 +65,7 @@ To disable profile functionality for all users, add this to ``config.php``: Property scopes --------------- -User properties (Full name, Address, Website, Role, …) have visibility scopes: +User properties (Display name, Address, Website, Role, etc.) have visibility scopes: Private, Local, Federated, Published. These scopes are evaluated per attribute. A profile being reachable does not imply @@ -80,18 +78,17 @@ The visibility scopes are: or the public lookup server. On local-instance user-to-user surfaces, ``Private`` data is not generally visible - to all local users. Visibility requires an authenticated requester and a + to all local users. Visibility may require an authenticated requester and a server-recognized known-user relationship with the target user. :Local: - Contact details visible on the local instance and through public share-links - (where profile/account attributes are inherently required - i.e. as file - owner/uploader metadata, etc.). Not shared to federated servers and not published - to the public lookup server. + Contact details visible on the local instance and in some public contexts where + profile/account attributes are required (for example owner/uploader metadata). + Not shared to federated servers and not published to the public lookup server. :Federated: - Contact details visible on the local instance, through local public-link contexts, + Contact details visible on the local instance, in relevant public contexts, and on trusted federated servers. :Published: - Contact details visible on the local instance, through local public-link contexts, + Contact details visible on the local instance, in relevant public contexts, on trusted federated servers, and published to the public lookup server. .. important:: @@ -106,17 +103,17 @@ The visibility scopes are: Scope audience overview ^^^^^^^^^^^^^^^^^^^^^^^ -+------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ -| Scope | User themself (*) | Other users on same local instance | Public link/public | Trusted federation | Public lookup server | -+============+===================+=============================================================+====================+=====================+======================+ -| Private | Yes | Limited: authenticated + known-user relation required | No | No | No | -+------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ -| Local | Yes | Yes | Yes | No | No | -+------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ -| Federated | Yes | Yes | Yes | Yes | No | -+------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ -| Published | Yes | Yes | Yes | Yes | Yes | -+------------+-------------------+-------------------------------------------------------------+--------------------+---------------------+----------------------+ ++------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ +| Scope | User themself (*) | Other users on same local instance | Public contexts (feature-dependent) | Trusted federation | Public lookup server | ++============+===================+=======================================================+======================================+=====================+======================+ +| Private | Yes | Limited: authenticated + known-user relation required | No | No | No | ++------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ +| Local | Yes | Yes | Yes (where applicable) | No | No | ++------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ +| Federated | Yes | Yes | Yes (where applicable) | Yes | No | ++------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ +| Published | Yes | Yes | Yes (where applicable) | Yes | Yes | ++------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ (*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. @@ -142,7 +139,8 @@ share dialogs, search, mentions, Contacts, and other integrations). For local users on the same instance: - ``Private``: not generally visible to all local users; visibility is restricted - to authenticated users that satisfy the known-user relation for that feature path. + on applicable paths to authenticated users that satisfy known-user relation and other + feature constraints. - ``Local``: visible on the local instance. - ``Federated``: visible on the local instance (and also shared with trusted federated servers). - ``Published``: visible on the local instance (and also federated + public lookup). @@ -190,9 +188,6 @@ Recommended test procedure: - Confirm new defaults apply only to newly initialized accounts. - Confirm existing users retain stored scopes unless explicitly changed. -.. tip:: - Keep one "scope matrix" test account in staging and re-run this checklist after upgrades. - Scope defaults and precedence ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -214,8 +209,8 @@ Default scope values ^^^^^^^^^^^^^^^^^^^^ Default values are defined in server code and may change over time. The authoritative -source is the ``DEFAULT_SCOPES`` constant in ``OC\Accounts\AccountManager``. The latest -version is `here `_). +source is the ``DEFAULT_SCOPES`` constant in ``OC\Accounts\AccountManager``: +`latest source `_. Example defaults (verify against your deployed version): @@ -326,7 +321,15 @@ With more restrictive scopes (especially ``Private``), expect reduced visibility In short: tighter privacy reduces profile-based convenience and discoverability. +.. note:: + System address book exposure is scope-aware and context-aware: + private/empty-scope properties are excluded from generated cards, and + federated reads strip local-scoped properties. + .. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - Since default visibility scope changes only apply to new users, perhaps we can cover whether their's a migration path for existing users? - - How do scopes interact with the system address book? + - define "public lookup server" + - better integrate (cross-link? separate out?) with chapters covering sharing and federation + - unify with User Manual + - Dev Manual coverage From 9631240d2e4489720cfb83508f655de8a5bf86be Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 09:35:33 -0400 Subject: [PATCH 07/16] chore(admin): additional minor edits to Profile config chapter Signed-off-by: Josh Richards --- .../profile_configuration.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 9604b03e1..c68943f33 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -108,14 +108,15 @@ Scope audience overview +============+===================+=======================================================+======================================+=====================+======================+ | Private | Yes | Limited: authenticated + known-user relation required | No | No | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Local | Yes | Yes | Yes (where applicable) | No | No | +| Local | Yes | Yes | Yes (where applicable**) | No | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Federated | Yes | Yes | Yes (where applicable) | Yes | No | +| Federated | Yes | Yes | Yes (where applicable**) | Yes | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Published | Yes | Yes | Yes (where applicable) | Yes | Yes | +| Published | Yes | Yes | Yes (where applicable**) | Yes | Yes | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ (*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. +(**) Public-context visibility depends on feature path; scope alone does not guarantee display. Known-user relation (for ``Private``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -123,7 +124,7 @@ Known-user relation (for ``Private``) For ``Private`` properties, Nextcloud may allow visibility on specific local feature paths only when the requester is considered a *known user* of the target user. -In practical terms, this relation is derived by server contact-matching logic and is +In practical terms, this relation is derived by server-side known-contact logic and is directional (A known to B does not imply B known to A). Users are always known to themselves. @@ -139,8 +140,8 @@ share dialogs, search, mentions, Contacts, and other integrations). For local users on the same instance: - ``Private``: not generally visible to all local users; visibility is restricted - on applicable paths to authenticated users that satisfy known-user relation and other - feature constraints. + on applicable paths to authenticated users that satisfy the known-user relation + and other feature constraints. - ``Local``: visible on the local instance. - ``Federated``: visible on the local instance (and also shared with trusted federated servers). - ``Published``: visible on the local instance (and also federated + public lookup). @@ -217,7 +218,7 @@ Example defaults (verify against your deployed version): +--------------+--------------------------+ | Property | Default visibility scope | +==============+==========================+ -| Full name | Federated | +| Display name | Federated | +--------------+--------------------------+ | Address | Local | +--------------+--------------------------+ @@ -328,7 +329,7 @@ In short: tighter privacy reduces profile-based convenience and discoverability. .. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - - Since default visibility scope changes only apply to new users, perhaps we can cover whether their's a migration path for existing users? + - Since default visibility scope changes only apply to new users, perhaps we can cover whether there's a migration path for existing users? - define "public lookup server" - better integrate (cross-link? separate out?) with chapters covering sharing and federation - unify with User Manual From 1707e0eeb79f88f8701b3e4c0015ac9825c4aa25 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 09:47:13 -0400 Subject: [PATCH 08/16] chore(admin): tweak Profiles chapter headings for clarity Signed-off-by: Josh Richards --- .../profile_configuration.rst | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index c68943f33..1c392537a 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -1,8 +1,8 @@ .. _profile: -===================== -Profile configuration -===================== +======== +Profiles +======== The user profile displays information about an account. Profiles are enabled by default. @@ -34,10 +34,10 @@ on privacy controls. Configuration ------------- -Set profile default for new users -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set the profile default for new users +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In **Administration settings** → **Basic settings**, use the profile default toggle. +In **Administration settings** -> **Basic settings**, use the profile default toggle. .. figure:: ../images/profile_default_setting.png @@ -62,8 +62,8 @@ To disable profile functionality for all users, add this to ``config.php``: .. _profile-property-scopes: -Property scopes ---------------- +Property visibility scopes +--------------------------- User properties (Display name, Address, Website, Role, etc.) have visibility scopes: Private, Local, Federated, Published. @@ -100,7 +100,7 @@ The visibility scopes are: On profile surfaces, the effective visibility is the more restrictive of profile-visibility settings and property scope. -Scope audience overview +Scope visibility matrix ^^^^^^^^^^^^^^^^^^^^^^^ +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ @@ -118,7 +118,7 @@ Scope audience overview (*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. (**) Public-context visibility depends on feature path; scope alone does not guarantee display. -Known-user relation (for ``Private``) +Known-user rule for ``Private`` scope ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For ``Private`` properties, Nextcloud may allow visibility on specific local feature @@ -146,8 +146,13 @@ For local users on the same instance: - ``Federated``: visible on the local instance (and also shared with trusted federated servers). - ``Published``: visible on the local instance (and also federated + public lookup). -Verification workflow for administrators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. note:: + System address book exposure is scope-aware and context-aware: + private/empty-scope properties are excluded from generated cards, and + federated reads strip local-scoped properties. + +How to verify scope behavior +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Because effective visibility can vary by feature path, administrators should verify scope behavior in their own deployment. @@ -206,8 +211,8 @@ Practical implications: - ``PROPERTY_DISPLAYNAME`` and ``PROPERTY_EMAIL`` cannot be ``Private``; server-side validation/enforcement requires at least ``Local``. -Default scope values -^^^^^^^^^^^^^^^^^^^^ +Default scope values (reference) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Default values are defined in server code and may change over time. The authoritative source is the ``DEFAULT_SCOPES`` constant in ``OC\Accounts\AccountManager``: @@ -249,8 +254,8 @@ Example defaults (verify against your deployed version): | Pronouns | Federated | +--------------+--------------------------+ -Override defaults in ``config.php`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Override default scopes in ``config.php`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To override one or several default visibility scopes for *new users*, use ``account_manager.default_property_scope`` (default: empty array): @@ -268,8 +273,8 @@ In the above example, phone and role are overwritten to ``Private`` and .. note:: Use ``\OCP\Accounts\IAccountManager`` constants for both property keys and scope values. -FAQ: How do I lock profile visibility down as tightly as possible? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FAQ: How to lock profile visibility down +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If your goal is maximum privacy: @@ -316,17 +321,12 @@ With more restrictive scopes (especially ``Private``), expect reduced visibility - User discovery/search/user cards - Share dialogs and mention/autocomplete context -- Public-share pages showing owner/profile metadata +- Public/share-related contexts where account metadata may be shown - Federated visibility of profile attributes - Public lookup publication (only ``Published`` appears there) In short: tighter privacy reduces profile-based convenience and discoverability. -.. note:: - System address book exposure is scope-aware and context-aware: - private/empty-scope properties are excluded from generated cards, and - federated reads strip local-scoped properties. - .. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - Since default visibility scope changes only apply to new users, perhaps we can cover whether there's a migration path for existing users? From d71e59a3466a43eac7d769ec248500b549450886 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 10:06:57 -0400 Subject: [PATCH 09/16] docs(admin): define public lookup server in Profile chapter Signed-off-by: Josh Richards --- .../profile_configuration.rst | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 1c392537a..35f9d8c94 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -22,7 +22,7 @@ on privacy controls. .. note:: Profile visibility is layered. - - **Profile enablement** controls whether profile functionality is available. + - **Profile enablement** determines if the profile feature is active at all. - **Profile field visibility settings** control whether a field is shown. - **Account property scopes** (for example ``private``, ``local``, ``federated``, ``published``) define the intended audience for each property. @@ -74,23 +74,29 @@ that all its attributes are visible. The visibility scopes are: :Private: - Most restrictive scope. Not exposed through public profile contexts, federation, - or the public lookup server. - On local-instance user-to-user surfaces, ``Private`` data is not generally visible - to all local users. Visibility may require an authenticated requester and a - server-recognized known-user relationship with the target user. + The most restrictive level. Data is hidden from public profiles, federation, and + public lookup. On the local server, it is only shown in specific features and + typically only to authenticated users who have a recognized relationship with the + account owner (for example, as a known contact). + :Local: Contact details visible on the local instance and in some public contexts where profile/account attributes are required (for example owner/uploader metadata). Not shared to federated servers and not published to the public lookup server. + :Federated: Contact details visible on the local instance, in relevant public contexts, and on trusted federated servers. + :Published: Contact details visible on the local instance, in relevant public contexts, on trusted federated servers, and published to the public lookup server. +.. note:: + **Public lookup server**: a public directory used to find users across Nextcloud instances. + Only profile fields marked Published may be exposed there. + .. important:: A reachable profile does not mean all attributes are public. Each attribute is filtered by its own scope, and effective visibility can also depend on the @@ -104,19 +110,21 @@ Scope visibility matrix ^^^^^^^^^^^^^^^^^^^^^^^ +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Scope | User themself (*) | Other users on same local instance | Public contexts (feature-dependent) | Trusted federation | Public lookup server | +| Scope | User themself [1] | Other users on same local instance | Public contexts (feature-dependent) | Trusted federation | Public lookup server | +============+===================+=======================================================+======================================+=====================+======================+ | Private | Yes | Limited: authenticated + known-user relation required | No | No | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Local | Yes | Yes | Yes (where applicable**) | No | No | +| Local | Yes | Yes | Yes (where applicable) [2] | No | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Federated | Yes | Yes | Yes (where applicable**) | Yes | No | +| Federated | Yes | Yes | Yes (where applicable) [2] | Yes | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -| Published | Yes | Yes | Yes (where applicable**) | Yes | Yes | +| Published | Yes | Yes | Yes (where applicable) [2] | Yes | Yes | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ -(*) Scope primarily governs exposure to others; owner access follows account/endpoint behavior. -(**) Public-context visibility depends on feature path; scope alone does not guarantee display. +Notes: + +1. Scope primarily governs exposure to others; owner access follows account/endpoint behavior. +2. Public-context visibility depends on feature path; scope alone does not guarantee display. Known-user rule for ``Private`` scope ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -125,8 +133,8 @@ For ``Private`` properties, Nextcloud may allow visibility on specific local fea paths only when the requester is considered a *known user* of the target user. In practical terms, this relation is derived by server-side known-contact logic and is -directional (A known to B does not imply B known to A). Users are always known to -themselves. +directional (e.g., Alice might be in Bob's contacts, but Bob isn't necessarily in +Alice's). Users are always known to themselves. What local users can see ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -330,7 +338,6 @@ In short: tighter privacy reduces profile-based convenience and discoverability. .. TODO/Future additions - Sharing settings + Mentions + Property Scope interactions (i.e. auto-completion, group/user-to-group/user sharing) - Since default visibility scope changes only apply to new users, perhaps we can cover whether there's a migration path for existing users? - - define "public lookup server" - better integrate (cross-link? separate out?) with chapters covering sharing and federation - unify with User Manual - Dev Manual coverage From d8b12588f25dd13f7ceb2cd8048c2b87966352b8 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 12 Mar 2026 10:19:50 -0400 Subject: [PATCH 10/16] chore(profiles): add follow-up items Signed-off-by: Josh Richards --- admin_manual/configuration_user/profile_configuration.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index 35f9d8c94..f98b4be7a 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -341,3 +341,9 @@ In short: tighter privacy reduces profile-based convenience and discoverability. - better integrate (cross-link? separate out?) with chapters covering sharing and federation - unify with User Manual - Dev Manual coverage + - better "known user context" description + - better "public contexts" description + - better "varies by feature/UI/API/app" description + - more definite statements; more direct statements + - simplify simplify simplify + From 8b2f6fea84af33292d949c5df9f0b5a7021d70d1 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 14 Mar 2026 11:12:04 -0400 Subject: [PATCH 11/16] refactor(profile): additional misc edits for clarity Signed-off-by: Josh --- .../profile_configuration.rst | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/admin_manual/configuration_user/profile_configuration.rst b/admin_manual/configuration_user/profile_configuration.rst index f98b4be7a..845432ef2 100644 --- a/admin_manual/configuration_user/profile_configuration.rst +++ b/admin_manual/configuration_user/profile_configuration.rst @@ -60,10 +60,38 @@ To disable profile functionality for all users, add this to ``config.php``: 'profile.enabled' => false, +Profile field visibility settings +--------------------------------- + +Each profile field has its own **profile visibility** setting (stored per user in the +profile configuration): + +- **Show to everyone** (``show``): visible to anyone, including unauthenticated visitors, + *subject to the field's property scope*. +- **Show to logged in accounts only** (``show_users_only``): visible only to authenticated + users, *subject to the field's property scope*. +- **Hide** (``hide``): never shown on profile surfaces regardless of property scope. + +These correspond to the visibility options in **Personal settings** -> **Personal info** +-> **Edit your Profile visibility**. + +.. important:: + Effective visibility is the most restrictive result of **both** controls: + the profile visibility setting and the property scope. + +Defaults +^^^^^^^^ + +By default, most profile fields are configured as **Show to everyone**, while some +contact-related fields default to **Show to logged in accounts only**. + +Administrators should note that these defaults are independent from the default +*property scopes* described below. + .. _profile-property-scopes: Property visibility scopes ---------------------------- +-------------------------- User properties (Display name, Address, Website, Role, etc.) have visibility scopes: Private, Local, Federated, Published. @@ -74,7 +102,6 @@ that all its attributes are visible. The visibility scopes are: :Private: - The most restrictive level. Data is hidden from public profiles, federation, and public lookup. On the local server, it is only shown in specific features and typically only to authenticated users who have a recognized relationship with the @@ -97,6 +124,13 @@ The visibility scopes are: **Public lookup server**: a public directory used to find users across Nextcloud instances. Only profile fields marked Published may be exposed there. +.. note:: + Not all fields are eligible for lookup publication even if their scope is set to + ``Published``. Some fields are intentionally never published (for example Biography, + Headline, Organisation, Role, Birthdate). + + In other words: ``Published`` is necessary but not always sufficient for lookup publication. + .. important:: A reachable profile does not mean all attributes are public. Each attribute is filtered by its own scope, and effective visibility can also depend on the @@ -112,7 +146,8 @@ Scope visibility matrix +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ | Scope | User themself [1] | Other users on same local instance | Public contexts (feature-dependent) | Trusted federation | Public lookup server | +============+===================+=======================================================+======================================+=====================+======================+ -| Private | Yes | Limited: authenticated + known-user relation required | No | No | No | +| Private | Yes | Limited on profile surfaces: | No | No | No | +| | | authenticated + known-user relation required [3] | | | | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ | Local | Yes | Yes | Yes (where applicable) [2] | No | No | +------------+-------------------+-------------------------------------------------------+--------------------------------------+---------------------+----------------------+ @@ -125,6 +160,12 @@ Notes: 1. Scope primarily governs exposure to others; owner access follows account/endpoint behavior. 2. Public-context visibility depends on feature path; scope alone does not guarantee display. +3. Some non-profile surfaces may exclude Private-scoped properties entirely (for example + generated system address book cards), even for authenticated users. + +.. note:: + The matrix describes **profile visibility behavior**. Other consuming features may apply + additional filtering and may not expose Private-scoped properties at all. Known-user rule for ``Private`` scope ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -132,9 +173,11 @@ Known-user rule for ``Private`` scope For ``Private`` properties, Nextcloud may allow visibility on specific local feature paths only when the requester is considered a *known user* of the target user. -In practical terms, this relation is derived by server-side known-contact logic and is -directional (e.g., Alice might be in Bob's contacts, but Bob isn't necessarily in -Alice's). Users are always known to themselves. +In practical terms, this relation is derived by server-side *known-contact matching*. +In current Nextcloud versions this matching is primarily established via **phone-number +matching** (for example through the Talk mobile contact integration), and it is directional +(e.g., Alice might be known to Bob, but Bob isn't necessarily known to Alice). +Users are always known to themselves. What local users can see ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +217,9 @@ Recommended test procedure: - ``charlie`` (second local user for control) 2. As ``alice``, set distinct test values for profile fields and assign different - scopes (Private, Local, Federated, Published). + scopes where possible (for example Private vs Local via the UI, and Federated/Published + via API/administrative tooling if supported in your deployment). + 3. Verify as ``alice``: - Confirm owner-visible values as expected. @@ -341,8 +386,12 @@ In short: tighter privacy reduces profile-based convenience and discoverability. - better integrate (cross-link? separate out?) with chapters covering sharing and federation - unify with User Manual - Dev Manual coverage + - better distinguish user facing profile field visibility settings from admin instance-level scope settings - better "known user context" description - better "public contexts" description + - better "lookup server" description/context + - https://github.com/nextcloud/lookup-server?tab=readme-ov-file#what-is-lookup-server + - https://github.com/nextcloud/lookup-server/blob/master/doc/architecture.md#overview - better "varies by feature/UI/API/app" description - more definite statements; more direct statements - simplify simplify simplify From a42c6f8a278fd2cb19b7ead6b1bb0f075133156a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 06:05:01 +0000 Subject: [PATCH 12/16] chore(deps): Bump charset-normalizer from 3.4.4 to 3.4.6 Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.4 to 3.4.6. - [Release notes](https://github.com/jawah/charset_normalizer/releases) - [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md) - [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.4...3.4.6) --- updated-dependencies: - dependency-name: charset-normalizer dependency-version: 3.4.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa0d079a8..cde3535da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ alabaster==1.0.0 Babel==2.18.0 certifi==2026.2.25 -charset-normalizer==3.4.4 +charset-normalizer==3.4.6 docutils==0.21.2 idna==3.11 imagesize==1.4.1 From a24b9ccfda520bc4c103eec5ebcb235aa4d4d2a8 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Thu, 19 Mar 2026 10:12:03 +0100 Subject: [PATCH 13/16] We do support nginx+php-fpm these days! Signed-off-by: Pablo Zmdl --- admin_manual/installation/nginx.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/admin_manual/installation/nginx.rst b/admin_manual/installation/nginx.rst index bf8fb8c3a..413c5bd87 100644 --- a/admin_manual/installation/nginx.rst +++ b/admin_manual/installation/nginx.rst @@ -4,10 +4,7 @@ NGINX configuration =================== -.. note:: - This page covers example NGINX configurations to run a Nextcloud server. - These configurations examples were originally provided by `@josh4trunks `_ - and are exclusively community-maintained. (Thank you contributors!) +This page covers how to run a Nextcloud server using NGINX backed by PHP-FPM, which is also an officially supported setup. - You need to insert the following code into **your Nginx configuration file**. Choose the appropriate example based on whether you are deploying :ref:`nginx_webroot_example` (i.e. :code:`https://cloud.example.com/`) or :ref:`nginx_subdir_example` (i.e. :code:`https://cloud.example.com/nextcloud`). From 241b4c48c92b3efa74e1a3cc9dc22df1333f52b0 Mon Sep 17 00:00:00 2001 From: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:25:15 +0100 Subject: [PATCH 14/16] docs: add documentation for OC-OwnerId and OC-Permissions headers (#13525) Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com> --- developer_manual/client_apis/WebDAV/basic.rst | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/developer_manual/client_apis/WebDAV/basic.rst b/developer_manual/client_apis/WebDAV/basic.rst index 2ff6a1934..2da5e5a51 100644 --- a/developer_manual/client_apis/WebDAV/basic.rst +++ b/developer_manual/client_apis/WebDAV/basic.rst @@ -550,12 +550,18 @@ You can set some special headers that Nextcloud will interpret. Response Headers ---------------- -+-----------+------------------------------------------------+-----------------------------------------+ -| Header | Description | Example | -+===========+================================================+=========================================+ -| OC-Etag | | On creation, move and copy, | ``"50ef2eba7b74aa84feff013efee2a5ef"`` | -| | | the response contain the etag of the file. | | -+-----------+------------------------------------------------+-----------------------------------------+ -| OC-FileId | | On creation, move and copy, | | Format: ````. | -| | | the response contain the fileid of the file. | | Example: ``00000259oczn5x60nrdu`` | -+-----------+------------------------------------------------+-----------------------------------------+ ++-------------------+-----------------------------------------------+-----------------------------------------+ +| Header | Description | Example | ++===================+===============================================+=========================================+ +|| OC-Etag || On creation, move and copy, || ``"50ef2eba7b74aa84feff013efee2a5ef"`` | +|| || the response contain the etag of the file. || | ++-------------------+-----------------------------------------------+-----------------------------------------+ +|| OC-FileId || On creation, move and copy, || Format: ````. | +|| || the response contain the fileid of the file. || Example: ``00000259oczn5x60nrdu`` | ++-------------------+-----------------------------------------------+-----------------------------------------+ +|| X-NC-OwnerId || On creation, the response contains the owner || Example: ``admin`` | +|| || ID. || | ++-------------------+-----------------------------------------------+-----------------------------------------+ +|| X-NC-Permissions || On creation, the response contains the || Example: ``RGDNVW`` | +|| || permissions string. || | ++-------------------+-----------------------------------------------+-----------------------------------------+ From e117beacddac9e78411422368cbbe33b794713c1 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 19 Mar 2026 08:58:07 -0400 Subject: [PATCH 15/16] docs(admin): fix webhook listener payload examples + add missing operators Signed-off-by: Josh --- admin_manual/webhook_listeners/index.rst | 345 ++++++++++++++--------- 1 file changed, 216 insertions(+), 129 deletions(-) diff --git a/admin_manual/webhook_listeners/index.rst b/admin_manual/webhook_listeners/index.rst index 8d5d75d53..f4d6238f8 100644 --- a/admin_manual/webhook_listeners/index.rst +++ b/admin_manual/webhook_listeners/index.rst @@ -70,11 +70,16 @@ escaped with two backslashes: once because you are inside a JSON string, and onc because you are inside a regular expression. You can also use additional comparison operators (``$e``, ``$ne``, ``$gt``, ``$gte``, -``$lt``, ``$lte``, ``$in``, ``$nin``) as well as logical operators (``$and``, ``$or``, -``$not``, ``$nor``). For example, use ``{ "time": { "$lt": 1711971024 } }`` to accept -only events prior to April 1st, 2024, and ``{ "time": { "$not": { "$lt": 1711971024 } }}`` +``$lt``, ``$lte``, ``$in``, ``$nin``, ``$all``, ``$exists``, ``$mod``) as well as +logical operators (``$and``, ``$or``, ``$not``, ``$nor``). + +For example, use ``{ "time": { "$lt": 1711971024 } }`` to accept +only events prior to April 1st, 2024, and ``{ "time": { "$not": { "$lt": 1711971024 } } }`` to accept events after April 1st, 2024. +Use ``{ "event.node.id": { "$exists": true } }`` to match only events where the node +still has an ID (i.e. is not a ``NonExistingFile``/``NonExistingFolder``). + Speeding up webhook dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -171,12 +176,42 @@ This is list of typically available events. It features the event ID and the ava In addition to the events listed below (which are provided by Nextcloud server and included apps), optional apps may register their own webhook-compatible events. The exact set of events available on your server will depend on your Nextcloud version and installed apps. If you need to discover available events, consult your app documentation or refer to your app source code or the Nextcloud developer documentation. -**Note:** Example payloads below are based on actual webhook HTTP POST payloads, using real JSON and data types. Field types are indicated in example objects by their value type: for example, ``"id": 123`` is an integer, not a string. +Payload envelope +~~~~~~~~~~~~~~~~ + +Every webhook HTTP POST body shares the same top-level structure: + +.. code-block:: json + + { + "event": { … }, + "user": { "uid": "alice", "displayName": "Alice" }, + "time": 1700100000 + } + +Where: + +- ``"event"`` is an object containing the event-specific data **plus** a ``"class"`` field + holding the fully-qualified PHP class name (e.g. + ``"OCP\\Files\\Events\\Node\\NodeCreatedEvent"``). +- ``"user"`` is the user who triggered the event (``null`` when no user session exists). + Contains ``"uid"`` (string) and ``"displayName"`` (string). +- ``"time"`` is an integer Unix timestamp of when the event was captured. + +Filters operate on this full envelope using dot-notation, e.g. ``event.node.path``, +``user.uid``, or ``time``. + +**Note:** Example payloads below are based on actual webhook HTTP POST payloads, using +real JSON and data types. Field types are indicated by their value type: for example, +``"id": 123`` is an integer, not a string. Node (Files / Folders) Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Node events include those that act on a single node as well as those that act on two nodes (such as copy, rename, and restore). In single-node events, the payload includes a single ``node`` object with information about the affected file or folder. In two-node events, the payload includes both a ``source`` object (the original node) and a ``target`` object (the resulting node after the operation). The examples below demonstrate these unique payload formats. +Node events include those that act on a single node as well as those that act on two +nodes (such as copy, rename, and restore). In single-node events, the ``event`` object +includes a ``node`` object. In two-node events, it includes both a ``source`` and a +``target`` object. The examples below show the **complete** payloads. **1. Single-node events** @@ -195,31 +230,53 @@ Applies to: .. code-block:: json { - "event": "NodeCreatedEvent", - "node": { - "id": 437, - "path": "/admin/files/test-webhook.txt" - } + "event": { + "class": "OCP\\Files\\Events\\Node\\NodeCreatedEvent", + "node": { + "id": 437, + "path": "/admin/files/test-webhook.txt" + } + }, + "user": { + "uid": "admin", + "displayName": "Admin" + }, + "time": 1700100000 } Where: -- ``"id"`` is an integer (unique node ID) -- ``"path"`` is a string (file/folder path) +- ``"event.class"`` is the fully-qualified event class name (string) +- ``"event.node.id"`` is an integer (unique node ID) — may be absent, see note below +- ``"event.node.path"`` is a string (file/folder path) .. note:: - In some events (such as ``NodeDeletedEvent`` or when the node refers to a non-existent file or folder), the ``node`` object may not have an ``id`` field. This happens when the node no longer exists in the filesystem/database, and is represented in Nextcloud by an internal ``NonExistingFile`` or ``NonExistingFolder``. In such cases, only the ``path`` field will be present. Always check for the presence of the ``id`` field in these payloads. Usually, the corresponding ``Before*`` event (such as ``BeforeNodeDeletedEvent``) can be used if you require the ``id`` of the node before deletion. + In some events (such as ``NodeDeletedEvent`` or when the node refers to a + non-existent file or folder), the ``node`` object may not have an ``id`` field. + This happens when the node no longer exists in the filesystem/database, and is + represented in Nextcloud by an internal ``NonExistingFile`` or ``NonExistingFolder``. + In such cases, only the ``path`` field will be present. Always check for the + presence of the ``id`` field in these payloads. Usually, the corresponding ``Before*`` + event (such as ``BeforeNodeDeletedEvent``) can be used if you require the ``id`` of + the node before deletion. Example of a deleted node webhook payload: .. code-block:: json - + { - "event": "NodeDeletedEvent", - "node": { - "path": "/user/files/oldfile.txt" - } + "event": { + "class": "OCP\\Files\\Events\\Node\\NodeDeletedEvent", + "node": { + "path": "/user/files/oldfile.txt" + } + }, + "user": { + "uid": "user", + "displayName": "User" + }, + "time": 1700100500 } **2. Two-node events** @@ -235,17 +292,24 @@ Applies to: .. code-block:: json - { - "event": "NodeRestoredEvent", - "source": { - "id": 399, - "path": "/admin/files/Deleted/myfile.txt" - }, - "target": { - "id": 437, - "path": "/admin/files/myfile.txt" - } - } + { + "event": { + "class": "OCP\\Files\\Events\\Node\\NodeRestoredEvent", + "source": { + "id": 399, + "path": "/admin/files_trashbin/files/myfile.txt" + }, + "target": { + "id": 437, + "path": "/admin/files/myfile.txt" + } + }, + "user": { + "uid": "admin", + "displayName": "Admin" + }, + "time": 1700100000 + } Where: @@ -261,15 +325,22 @@ Example of a two-node event (NodeRenamedEvent) where the source node is missing: .. code-block:: json { - "event": "NodeRenamedEvent", - "source": { - "path": "/user/files/previousname.txt" - }, - "target": { - "id": 599, - "path": "/user/files/newname.txt" - } - } + "event": { + "class": "OCP\\Files\\Events\\Node\\NodeRenamedEvent", + "source": { + "path": "/user/files/previousname.txt" + }, + "target": { + "id": 599, + "path": "/user/files/newname.txt" + } + }, + "user": { + "uid": "user", + "displayName": "Joe User" + }, + "time": 1700100000 + } .. note:: @@ -283,12 +354,19 @@ System Tag Events .. code-block:: json - { - "event": "TagAssignedEvent", - "objectType": "files", - "objectIds": ["437","438"], - "tagIds": [3,17] - } + { + "event": { + "class": "OCP\\SystemTag\\TagAssignedEvent", + "objectType": "files", + "objectIds": ["437", "438"], + "tagIds": [3, 17] + }, + "user": { + "uid": "admin", + "displayName": "Admin" + }, + "time": 1700100000 + } Calendar Object Events ~~~~~~~~~~~~~~~~~~~~~~ @@ -307,45 +385,45 @@ Applies to: .. code-block:: json - { - "event": "CalendarObjectCreatedEvent", - "user": { - "uid": "david", - "displayName": "David" - }, - "time": 1700100000, - "eventData": { - "calendarId": 9, - "calendarData": { - "id": 9, - "uri": "work", - "{http://calendarserver.org/ns/}getctag": "1736283", - "{http://sabredav.org/ns}sync-token": 9651, - "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": "VEVENT,VTODO", - "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": "opaque" - }, - "shares": [ - { - "href": "mailto:alice@example.com", - "commonName": "Alice", - "status": 2, - "readOnly": false, - "{http://owncloud.org/ns}principal": "principal:users/alice", - "{http://owncloud.org/ns}group-share": false - } - ], - "objectData": { - "id": 22, - "uri": "event-20251111T100000Z.ics", - "lastmodified": 1700099500, - "etag": "19fa45b394", - "calendarid": 9, - "size": 4096, - "component": "VEVENT", - "classification": 0 - } - } - } + { + "event": { + "class": "OCP\\Calendar\\Events\\CalendarObjectCreatedEvent", + "calendarId": 9, + "calendarData": { + "id": 9, + "uri": "work", + "{http://calendarserver.org/ns/}getctag": "1736283", + "{http://sabredav.org/ns}sync-token": 9651, + "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set": "VEVENT,VTODO", + "{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": "opaque" + }, + "shares": [ + { + "href": "mailto:alice@example.com", + "commonName": "Alice", + "status": 2, + "readOnly": false, + "{http://owncloud.org/ns}principal": "principal:users/alice", + "{http://owncloud.org/ns}group-share": false + } + ], + "objectData": { + "id": 22, + "uri": "event-20251111T100000Z.ics", + "lastmodified": 1700099500, + "etag": "19fa45b394", + "calendarid": 9, + "size": 4096, + "component": "VEVENT", + "classification": 0 + } + }, + "user": { + "uid": "david", + "displayName": "David" + }, + "time": 1700100000 + } **Distinct payload for two-calendar events** @@ -355,35 +433,45 @@ Applies to: .. code-block:: json - { - "event": "CalendarObjectMovedEvent", - "sourceCalendarId": 9, - "sourceCalendarData": { - "id": 9, - "uri": "work" - }, - "targetCalendarId": 11, - "targetCalendarData": { - "id": 11, - "uri": "meetings" - }, - "sourceShares": [ - { - "href": "mailto:alice@example.com", - "commonName": "Alice" - } - ], - "targetShares": [ - { - "href": "mailto:bob@example.com", - "commonName": "Bob" - } - ], - "objectData": { - "id": 22, - "uri": "event-20251111T100000Z.ics" - } - } + { + "event": { + "class": "OCP\\Calendar\\Events\\CalendarObjectMovedEvent", + "sourceCalendarId": 9, + "sourceCalendarData": { + "id": 9, + "uri": "work" + ... + }, + "targetCalendarId": 11, + "targetCalendarData": { + "id": 11, + "uri": "meetings" + ... + }, + "sourceShares": [ + { + "href": "mailto:alice@example.com", + "commonName": "Alice" + ... + } + ], + "targetShares": [ + { + "href": "mailto:bob@example.com", + "commonName": "Bob" + ... + } + ], + "objectData": { + "id": 22, + "uri": "event-20251111T100000Z.ics" + ... + }, + "user": { + ... + }, + "time": ... + } Forms App Events ~~~~~~~~~~~~~~~~ @@ -395,13 +483,7 @@ When the optional ``forms`` app is installed: .. code-block:: json { - "event": "FormSubmittedEvent", - "user": { - "uid": "bob", - "displayName": "Bob" - }, - "time": 1700001234, - "eventData": { + "event": { "class": "OCA\\Forms\\Events\\FormSubmittedEvent", "form": { "id": 51, @@ -427,7 +509,12 @@ When the optional ``forms`` app is installed: "userId": "bob", "timestamp": 1700001234 } - } + }, + "user": { + "uid": "bob", + "displayName": "Bob" + }, + "time": 1700001234, } Tables App Events @@ -442,13 +529,7 @@ When the optional ``tables`` app is installed: .. code-block:: json { - "event": "RowAddedEvent", - "user": { - "uid": "carol", - "displayName": "Carol" - }, - "time": 1700054321, - "eventData": { + "event": { "class": "OCA\\Tables\\Event\\RowAddedEvent", "tableId": 34, "rowId": 7, @@ -458,9 +539,15 @@ When the optional ``tables`` app is installed: "1": 2025, "2": "active" } - } + }, + "user": { + "uid": "carol", + "displayName": "Carol" + }, + "time": 1700054321, } .. note:: - For filtering or automation, always check the actual payload you receive, as it matches the JSON examples above, not PHPDoc or internal PHP array type style. + For filtering or automation, always check the actual payload you receive, as it matches + the JSON examples above, not PHPDoc or internal PHP array type style. From 8056a8b14165ed4e5153a462d75f9ffb5134f8c6 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Thu, 19 Mar 2026 09:26:24 -0400 Subject: [PATCH 16/16] docs(admin): clarify webhook dot notation + add list/occ cmd Signed-off-by: Josh Richards --- admin_manual/webhook_listeners/index.rst | 72 +++++++++++++++++------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/admin_manual/webhook_listeners/index.rst b/admin_manual/webhook_listeners/index.rst index f4d6238f8..530f71ed1 100644 --- a/admin_manual/webhook_listeners/index.rst +++ b/admin_manual/webhook_listeners/index.rst @@ -47,13 +47,46 @@ You can use the OCS API to add webhooks for specific events. See: Note: When authenticating with the OCS API to register webhooks, the account you use must have administrator rights or delegated administrator rights. +Listing registered webhooks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can list all currently registered webhook listeners from the command line: + +.. code-block:: bash + + occ webhook_listeners:list + +By default this prints a table. Use the ``--output`` option to change the format. +Each row contains the full configuration of a registered webhook + Filters ~~~~~~~ -When registering a webhook listener, you can specify a filter parameter. The value of -this parameter must be a JSON object whose properties represent filter conditions. -The ``{}`` object is an empty query, meaning no specific criteria are set, so all events -are matched. +When registering a webhook listener, you can specify a filter parameter (``eventFilter``). +The filter is evaluated against the **complete webhook payload envelope**, which has this +structure: + +.. code-block:: json + + { + "event": { "class": "…", "…event-specific fields…" }, + "user": { "uid": "…", "displayName": "…" }, + "time": 1700100000 + } + +Filter keys use **dot-notation** to traverse into nested objects. For example: + +- ``time`` — matches the top-level Unix timestamp +- ``user.uid`` — matches the ``uid`` field inside the ``user`` object +- ``event.class`` — matches the event's fully-qualified class name inside ``event`` +- ``event.node.path`` — matches the ``path`` field inside ``event`` → ``node`` + (for node events) +- ``event.calendarId`` — matches the ``calendarId`` field inside ``event`` + (for calendar events) + +The value of the filter parameter must be a JSON object whose properties represent filter +conditions. The ``{}`` object is an empty query, meaning no specific criteria are set, so +all events are matched. If you want to match events triggered by a specific user, you can pass ``{ "user.uid": "bob" }`` to match all events associated with the user ``bob``. @@ -69,7 +102,7 @@ inside the ``Special folder`` of any user. Note that the slashes in the path nee escaped with two backslashes: once because you are inside a JSON string, and once because you are inside a regular expression. -You can also use additional comparison operators (``$e``, ``$ne``, ``$gt``, ``$gte``, +You can also use comparison operators (``$e``, ``$ne``, ``$gt``, ``$gte``, ``$lt``, ``$lte``, ``$in``, ``$nin``, ``$all``, ``$exists``, ``$mod``) as well as logical operators (``$and``, ``$or``, ``$not``, ``$nor``). @@ -80,6 +113,9 @@ to accept events after April 1st, 2024. Use ``{ "event.node.id": { "$exists": true } }`` to match only events where the node still has an ID (i.e. is not a ``NonExistingFile``/``NonExistingFolder``). +Use ``{ "event.class": "OCP\\Files\\Events\\Node\\NodeCreatedEvent" }`` to match only +``NodeCreatedEvent`` events (backslashes must be escaped in JSON strings). + Speeding up webhook dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -184,7 +220,7 @@ Every webhook HTTP POST body shares the same top-level structure: .. code-block:: json { - "event": { … }, + "event": {}, "user": { "uid": "alice", "displayName": "Alice" }, "time": 1700100000 } @@ -198,9 +234,6 @@ Where: Contains ``"uid"`` (string) and ``"displayName"`` (string). - ``"time"`` is an integer Unix timestamp of when the event was captured. -Filters operate on this full envelope using dot-notation, e.g. ``event.node.path``, -``user.uid``, or ``time``. - **Note:** Example payloads below are based on actual webhook HTTP POST payloads, using real JSON and data types. Field types are indicated by their value type: for example, ``"id": 123`` is an integer, not a string. @@ -439,39 +472,36 @@ Applies to: "sourceCalendarId": 9, "sourceCalendarData": { "id": 9, - "uri": "work" - ... + "uri": "work", }, "targetCalendarId": 11, "targetCalendarData": { "id": 11, - "uri": "meetings" - ... + "uri": "meetings", }, "sourceShares": [ { "href": "mailto:alice@example.com", "commonName": "Alice" - ... } ], "targetShares": [ { "href": "mailto:bob@example.com", "commonName": "Bob" - ... } ], "objectData": { "id": 22, "uri": "event-20251111T100000Z.ics" - ... }, - "user": { - ... - }, - "time": ... - } + }, + "user": { + "uid": "david", + "displayName": "David" + }, + "time": 1700100000 + } Forms App Events ~~~~~~~~~~~~~~~~