From e3a14a461dc17a4a6334ae310183bb8e8184a9fe Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Tue, 3 Nov 2026 09:00:00 +0000 Subject: [PATCH 01/10] docs(ocm): define contact removal Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++ spec.yaml | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/IETF-OCM.md b/IETF-OCM.md index 44a647e..a5367ce 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -715,6 +715,9 @@ contain the following information about its OCM API: capability. Example: `["exchange-token", "protocol-object"]`. The array MAY include one or more of the following items: + - `"contact-removal-v1"` - to indicate that this OCM Server + processes "CONTACT_REMOVED" notifications as specified in + [Contact Removal](#contact-removal). - `"enforce-mfa"` - to indicate that this OCM Server can apply a Sending Server's MFA requirements for a Share on their behalf. - `"exchange-token"` - to indicate that this OCM Server supports the @@ -1570,6 +1573,64 @@ Further, the `notification` object MUST include the following fields: * REQUIRED groupId (string) - identifier of the group to be removed from the target OCM Server. +The "USER_REMOVED" notification identifies the removed user only, and +not which users of the target OCM Server it concerns. A Sending +Server MUST NOT use it to inform that one of its users removed a +single contact: the "CONTACT_REMOVED" notification is defined for +that purpose (see [Contact Removal](#contact-removal)). + +## Contact Removal + +A notification MAY be sent to inform a target OCM Server that a user +of the Sending Server removed a single user of the target OCM Server +from their contacts, e.g. a contact established through a previous +successful Invitation. Unlike "USER_REMOVED", this notification +identifies both users of the contact: the user who removed the +contact, called the actor, and the user who was removed, called the +counterpart. + +For this case, the `notificationType` MUST be "CONTACT_REMOVED", the +`resourceType` MUST be omitted, the `shareType` MUST be `user`, and +the `notification` object MUST include the following fields: +* OPTIONAL message (string) - an optional human-readable message that + describes the event. +* REQUIRED user (object) - an object containing the details of the + contact, including: + * REQUIRED userId (string) - OCM Address of the actor, i.e. the user + of the Sending Server who removed the contact. Its host part MUST + be the `senderDomain`. + * REQUIRED counterpartUserId (string) - OCM Address of the + counterpart, i.e. the user of the target OCM Server who was + removed from the contacts of the actor. + +A Sending Server MUST send a "CONTACT_REMOVED" notification only to an +OCM Server that advertises the `contact-removal-v1` capability in its +[Discovery](#ocm-api-discovery) response. If the target OCM Server +does not advertise it, the Sending Server MUST NOT send a +"USER_REMOVED" notification instead, because the target OCM Server +could then remove the actor from the contacts of all of its users. +An OCM Server MUST NOT advertise the `contact-removal-v1` capability +unless it processes "CONTACT_REMOVED" notifications as follows: + +* A payload that lacks a REQUIRED field, or whose `userId` or + `counterpartUserId` is not a valid OCM Address, MUST be rejected + with HTTP 400. +* If the host part of `userId` is not the `senderDomain`, if the + counterpart is not a user of the Receiving Server, or if the + counterpart has no contact relationship with the actor, the + notification has no effect, and the Receiving Server SHOULD respond + with HTTP 201. The same applies to a repeated notification. +* Otherwise, the Receiving Server MAY reciprocally remove the actor + from the contacts of the counterpart. It MUST NOT remove the actor + from the contacts of any other user, and MUST NOT remove any other + contact of the counterpart. +* The Receiving Server MUST NOT send a "CONTACT_REMOVED" notification + back to the Sending Server as a consequence. + +Removing a contact concerns the contact relationship only: it does not +by itself delete any Share, and it does not revoke access tokens that +were already issued, which remain valid until they expire. + # Resource Access @@ -2101,6 +2162,7 @@ payload. | REQUEST_SHARE | Resource | This document | | USER_REMOVED | Recipient | This document | | GROUP_REMOVED | Recipient | This document | + | CONTACT_REMOVED | Recipient | This document | +===========================+===========+===============+ ~~~ diff --git a/spec.yaml b/spec.yaml index de37e05..a872bd3 100644 --- a/spec.yaml +++ b/spec.yaml @@ -210,6 +210,18 @@ paths: application/json: schema: $ref: "#/components/schemas/NewNotification" + examples: + contactWasRemoved: + summary: A user removed a single contact + value: + notificationType: CONTACT_REMOVED + shareType: user + senderDomain: alice.example.org + notification: + message: Alice removed Bob from her contacts + user: + userId: alice@alice.example.org + counterpartUserId: bob@bob.example.org description: The JSON object with a new notification required: true responses: @@ -567,6 +579,7 @@ components: items: type: string enum: + - contact-removal-v1 - enforce-mfa - exchange-token - http-sig @@ -1137,6 +1150,17 @@ components: `providerId` of the underlying resource, and in case of `user`, `group`, or `federation` share types, it MUST contain at least the `userId`, `groupId`, or `mlsGroupId` of the underlying recipient, respectively. + allOf: + - description: > + A CONTACT_REMOVED notification MUST satisfy its specific payload + requirements. + anyOf: + - properties: + notificationType: + not: + enum: + - CONTACT_REMOVED + - $ref: "#/components/schemas/ContactRemovedNotification" example: legacyShareWasAccepted: notificationType: SHARE_ACCEPTED @@ -1180,6 +1204,52 @@ components: mlsGroupId: 51dc30ddc473d43a6011e9ebba6ca770 userId: bob@cloud.example.org content: base64 MLS welcome + ContactRemovedNotification: + type: object + description: > + Payload requirements of the CONTACT_REMOVED notification, which + omits the `resourceType` and identifies both users of the removed + contact. It is only sent to servers advertising the + `contact-removal-v1` capability. + required: + - notificationType + - shareType + - notification + properties: + notificationType: + type: string + enum: + - CONTACT_REMOVED + shareType: + type: string + enum: + - user + notification: + type: object + required: + - user + properties: + user: + type: object + required: + - userId + - counterpartUserId + properties: + userId: + type: string + pattern: "^.+@[^@]+$" + description: > + OCM Address of the user of the sending server who removed + the contact. Its host part is the `senderDomain`. + counterpartUserId: + type: string + pattern: "^.+@[^@]+$" + description: > + OCM Address of the user of the receiving server who was + removed from the contacts. + not: + required: + - resourceType AcceptedInvite: type: object required: From 7c8a0a4204d7d2a665a8cc5e3a557d836db27397 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Tue, 3 Nov 2026 13:00:00 +0000 Subject: [PATCH 02/10] docs(ocm): use versionless contact capability Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 13 +++++++------ spec.yaml | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index a5367ce..7cc7e83 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -715,7 +715,7 @@ contain the following information about its OCM API: capability. Example: `["exchange-token", "protocol-object"]`. The array MAY include one or more of the following items: - - `"contact-removal-v1"` - to indicate that this OCM Server + - `"contact-removal"` - to indicate that this OCM Server processes "CONTACT_REMOVED" notifications as specified in [Contact Removal](#contact-removal). - `"enforce-mfa"` - to indicate that this OCM Server can apply a @@ -1604,17 +1604,18 @@ the `notification` object MUST include the following fields: removed from the contacts of the actor. A Sending Server MUST send a "CONTACT_REMOVED" notification only to an -OCM Server that advertises the `contact-removal-v1` capability in its +OCM Server that advertises the `contact-removal` capability in its [Discovery](#ocm-api-discovery) response. If the target OCM Server does not advertise it, the Sending Server MUST NOT send a "USER_REMOVED" notification instead, because the target OCM Server could then remove the actor from the contacts of all of its users. -An OCM Server MUST NOT advertise the `contact-removal-v1` capability +An OCM Server MUST NOT advertise the `contact-removal` capability unless it processes "CONTACT_REMOVED" notifications as follows: -* A payload that lacks a REQUIRED field, or whose `userId` or - `counterpartUserId` is not a valid OCM Address, MUST be rejected - with HTTP 400. +* A payload that lacks a REQUIRED field, that includes a + `resourceType` (even if null or empty), that has a field of the + wrong type, or whose `userId` or `counterpartUserId` is not a valid + OCM Address, MUST be rejected with HTTP 400. * If the host part of `userId` is not the `senderDomain`, if the counterpart is not a user of the Receiving Server, or if the counterpart has no contact relationship with the actor, the diff --git a/spec.yaml b/spec.yaml index a872bd3..9da328c 100644 --- a/spec.yaml +++ b/spec.yaml @@ -579,7 +579,7 @@ components: items: type: string enum: - - contact-removal-v1 + - contact-removal - enforce-mfa - exchange-token - http-sig @@ -1210,7 +1210,7 @@ components: Payload requirements of the CONTACT_REMOVED notification, which omits the `resourceType` and identifies both users of the removed contact. It is only sent to servers advertising the - `contact-removal-v1` capability. + `contact-removal` capability. required: - notificationType - shareType @@ -1236,20 +1236,28 @@ components: - counterpartUserId properties: userId: - type: string - pattern: "^.+@[^@]+$" description: > OCM Address of the user of the sending server who removed the contact. Its host part is the `senderDomain`. + allOf: + - $ref: "#/components/schemas/OCMAddress" counterpartUserId: - type: string - pattern: "^.+@[^@]+$" description: > OCM Address of the user of the receiving server who was removed from the contacts. + allOf: + - $ref: "#/components/schemas/OCMAddress" not: required: - resourceType + OCMAddress: + type: string + description: > + An OCM Address, `identifier "@" host [":" port]`. The identifier is + an opaque, case-sensitive string separated from the host by the last + "@". The host is an IP literal, an IPv4 address or a registered name + and cannot contain a path, query, fragment or whitespace. + pattern: '^.+@(\[(v[0-9A-Fa-f]+\.[A-Za-z0-9._~!$&''()*+,;=:-]+|[0-9A-Fa-f:.]+)\]|([A-Za-z0-9._~!$&''()*+,;=-]|%[0-9A-Fa-f]{2})+)(:[0-9]+)?$' AcceptedInvite: type: object required: From 23d31ed255e17fe688ee0a17805cedf77afbf06b Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Tue, 3 Nov 2026 17:00:00 +0000 Subject: [PATCH 03/10] docs(ocm): validate notifications and addresses Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 3 ++- spec.yaml | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index 7cc7e83..b1ba432 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -1598,7 +1598,8 @@ the `notification` object MUST include the following fields: contact, including: * REQUIRED userId (string) - OCM Address of the actor, i.e. the user of the Sending Server who removed the contact. Its host part MUST - be the `senderDomain`. + be the `senderDomain`, compared as a whole host without regard to + case; a port is never removed to make them match. * REQUIRED counterpartUserId (string) - OCM Address of the counterpart, i.e. the user of the target OCM Server who was removed from the contacts of the actor. diff --git a/spec.yaml b/spec.yaml index 9da328c..916d79d 100644 --- a/spec.yaml +++ b/spec.yaml @@ -1101,6 +1101,7 @@ components: properties: notificationType: type: string + pattern: '\S' description: > A notification type that is understandable for both humans and machines, providing information about an event. @@ -1108,12 +1109,14 @@ components: MAY be used here. Implementers MAY define additional custom types, provided that they are registered in the OCM IANA Registry. senderDomain: - type: string description: > The FQDN of the sending OCM server, in order for the recipient to validate its key material when verifying the HTTP Signature. + allOf: + - $ref: "#/components/schemas/SenderDomain" resourceType: type: string + minLength: 1 description: > Resource type, according to the OCM IANA Registry for resource types. E.g. file, folder, calendar, contact, etc. Implementers MAY define @@ -1121,6 +1124,7 @@ components: respective OCM IANA Registry. shareType: type: string + minLength: 1 description: > Share type, according to the OCM IANA Registry for share types. E.g. user, group, federation, etc. Implementers MAY define @@ -1141,6 +1145,11 @@ components: type: string description: > An optional human-readable message that describes the event. + sharedSecret: + type: string + description: > + An optional secret related to the underlying share. It does not + authenticate the sender. additionalProperties: type: object description: > @@ -1151,6 +1160,14 @@ components: or `federation` share types, it MUST contain at least the `userId`, `groupId`, or `mlsGroupId` of the underlying recipient, respectively. allOf: + - description: > + A notification MUST include either the `resourceType` or the + `shareType`, or both when relevant. + anyOf: + - required: + - resourceType + - required: + - shareType - description: > A CONTACT_REMOVED notification MUST satisfy its specific payload requirements. @@ -1250,14 +1267,23 @@ components: not: required: - resourceType + SenderDomain: + type: string + description: > + A DNS name of at least two labels of ASCII letters, digits and + interior hyphens, at most 63 bytes each and 253 bytes in total, + without a port, a trailing dot or an IPv4 address. + pattern: '^(?!(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?![\s\S]))(?=[\s\S]{1,253}(?![\s\S]))[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+(?![\s\S])' OCMAddress: type: string description: > An OCM Address, `identifier "@" host [":" port]`. The identifier is an opaque, case-sensitive string separated from the host by the last - "@". The host is an IP literal, an IPv4 address or a registered name - and cannot contain a path, query, fragment or whitespace. - pattern: '^.+@(\[(v[0-9A-Fa-f]+\.[A-Za-z0-9._~!$&''()*+,;=:-]+|[0-9A-Fa-f:.]+)\]|([A-Za-z0-9._~!$&''()*+,;=-]|%[0-9A-Fa-f]{2})+)(:[0-9]+)?$' + "@". The host follows RFC 3986 section 3.2.2: an IPv6 or IPvFuture + literal in brackets, an IPv4 address or a registered name. The + IPv6 alternative spells out the RFC grammar, and the input ends + with the host or port, so a trailing newline is not accepted. + pattern: '^[\s\S]+@(?:\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){1}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[vV][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&''()*+,;=:]+)\]|(?:[A-Za-z0-9\-._~!$&''()*+,;=]|%[0-9A-Fa-f]{2})+)(?::[0-9]*)?(?![\s\S])' AcceptedInvite: type: object required: From fbc3ccb4280494bb5ef65321c1fa6f93b6091be0 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Wed, 4 Nov 2026 10:00:00 +0000 Subject: [PATCH 04/10] docs(ocm): identify senders by host and port Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 33 ++++++++++++++++++++++++--------- spec.yaml | 35 ++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index b1ba432..ff404e9 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -368,8 +368,10 @@ Acceptance Request MUST be a HTTP POST request: * using `application/json` as the `Content-Type` HTTP request header * its request body containing a JSON document representing an object with the following string fields: - - REQUIRED: `recipientProvider` - FQDN of the Invite Receiver OCM - Server. + - REQUIRED: `recipientProvider` - The server part, + `host [":" port]`, of the Invite Receiver's OCM Addresses, with + the same grammar and comparison as the `senderDomain` field of a + Notification. - REQUIRED: `token` - The Invite Token. The Invite Sender OCM Server SHOULD recall which Invite Sender OCM Address this token was linked to. @@ -1401,10 +1403,19 @@ request: * REQUIRED notificationType (string) - it MUST be one of the registered values listed in the "OCM Notification Types" registry (see [IANA Considerations](#iana-considerations)). -* REQUIRED senderDomain (string) - the FQDN of the sender. A Receiving - Server SHOULD use this information to verify the HTTP message - signature on the request, and it SHOULD also use it to validate the - underlying share or resource the notification is about. +* REQUIRED senderDomain (string) - the server part of the sender's + OCM Addresses, `host [":" port]`, with the host and port grammar of + an OCM Address, for example `cloud.example.org`, + `cloud.example.org:8443`, `192.0.2.1` or `[2001:db8::1]:8443`. A + Sending Server MUST send the complete host and port, if any, that + its OCM Addresses use. Two values name the same server only when + they are equal after lowercasing; a Receiving Server MUST NOT remove + or assume a port, resolve a name, or use the endpoint found by + discovery to identify the sender, as that endpoint is only used to + reach it. A Receiving Server SHOULD use this information to verify + the HTTP message signature on the request, and it SHOULD also use + it to validate the underlying share or resource the notification + is about. * OPTIONAL resourceType (string) - the type of the resource this notification is about (e.g., `file`). * OPTIONAL shareType (string) - the type of recipient this notification @@ -1602,7 +1613,9 @@ the `notification` object MUST include the following fields: case; a port is never removed to make them match. * REQUIRED counterpartUserId (string) - OCM Address of the counterpart, i.e. the user of the target OCM Server who was - removed from the contacts of the actor. + removed from the contacts of the actor. Its host part is the + complete host of the target OCM Server, as in its own OCM + Addresses, compared in the same way. A Sending Server MUST send a "CONTACT_REMOVED" notification only to an OCM Server that advertises the `contact-removal` capability in its @@ -1617,8 +1630,10 @@ unless it processes "CONTACT_REMOVED" notifications as follows: `resourceType` (even if null or empty), that has a field of the wrong type, or whose `userId` or `counterpartUserId` is not a valid OCM Address, MUST be rejected with HTTP 400. -* If the host part of `userId` is not the `senderDomain`, if the - counterpart is not a user of the Receiving Server, or if the +* If the host part of `userId` is not the `senderDomain`, if the host + part of `counterpartUserId` is not the complete host of the + Receiving Server, if the counterpart is not a user of the Receiving + Server, or if the counterpart has no contact relationship with the actor, the notification has no effect, and the Receiving Server SHOULD respond with HTTP 201. The same applies to a repeated notification. diff --git a/spec.yaml b/spec.yaml index 916d79d..f05d820 100644 --- a/spec.yaml +++ b/spec.yaml @@ -1101,7 +1101,9 @@ components: properties: notificationType: type: string - pattern: '\S' + pattern: "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\ + \\u1680\\u2000-\\u200A\\u2028\\u2029\ + \\u202F\\u205F\\u3000]" description: > A notification type that is understandable for both humans and machines, providing information about an event. @@ -1110,10 +1112,11 @@ components: provided that they are registered in the OCM IANA Registry. senderDomain: description: > - The FQDN of the sending OCM server, in order for the recipient - to validate its key material when verifying the HTTP Signature. + The complete host and port, if any, of the sending OCM server, + as in the server part of its OCM Addresses. It identifies the + sender; the endpoint found by discovery is only used to reach it. allOf: - - $ref: "#/components/schemas/SenderDomain" + - $ref: "#/components/schemas/OCMHost" resourceType: type: string minLength: 1 @@ -1267,13 +1270,17 @@ components: not: required: - resourceType - SenderDomain: + OCMHost: type: string description: > - A DNS name of at least two labels of ASCII letters, digits and - interior hyphens, at most 63 bytes each and 253 bytes in total, - without a port, a trailing dot or an IPv4 address. - pattern: '^(?!(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?![\s\S]))(?=[\s\S]{1,253}(?![\s\S]))[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+(?![\s\S])' + The server part of an OCM Address, `host [":" port]`, with the host + and port grammar of the OCMAddress schema, for example + `cloud.example.org`, `cloud.example.org:8443`, `192.0.2.1` or + `[2001:db8::1]:8443`. It has no identifier, scheme, path, query or + fragment. Two values name the same server only when they are equal + after lowercasing; a port is never removed or assumed. + example: cloud.example.org:8443 + pattern: '^(?:\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){1}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[vV][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&''()*+,;=:]+)\]|(?:[A-Za-z0-9\-._~!$&''()*+,;=]|%[0-9A-Fa-f]{2})+)(?::[0-9]*)?(?![\s\S])' OCMAddress: type: string description: > @@ -1294,10 +1301,12 @@ components: - name properties: recipientProvider: - type: string - format: fqdn - description: FQDN of the receiver OCM service. - example: receiver.org + description: > + The complete host and port, if any, of the receiver OCM server, + as in the server part of its OCM Addresses. + example: receiver.org:8443 + allOf: + - $ref: "#/components/schemas/OCMHost" token: type: string description: Token received in the invite From 3a278e1244890f68f022ac0ff37a6bd8985e9e93 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Wed, 4 Nov 2026 16:00:00 +0000 Subject: [PATCH 05/10] docs(ocm): require durable pair grant revocation Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 164 +++++++++++++++++++++++++++++++--------------------- spec.yaml | 15 +++-- 2 files changed, 110 insertions(+), 69 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index ff404e9..168c37a 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -1549,40 +1549,18 @@ such as HTTP 404. ## Recipient Removal -A notification MAY be sent to inform a target OCM Server that a share -recipient (e.g. a user or group) was removed from the list of trusted -users, following a previous successful Invitation, or otherwise left -the realm of the sending OCM Server. That recipient may have been -previously known to the target server because of existing shares whose -shareType matched it. The recipient of such notification SHOULD -reciprocally remove that peer from the list of trusted users, along -with any related shares. - -For these cases, a notification payload is to be formed such that the -`resourceType` MUST be omitted, the `shareType` is set to the affected -shareType being removed, such as `user` or `group`, and the -`notificationType` MUST be one of: -- "CONTACT_REMOVED", to inform that a single user contact was removed - from the list of trusted contacts by the peer user. -- "USER_REMOVED", to inform that a user left or was removed from the - realm of the sending OCM Server, and is therefore no longer reachable - as a contact. -- "GROUP_REMOVED", to inform about a removed group of users. -Further, the `notification` object MUST include the following fields: -* OPTIONAL message (string) - an optional human-readable message that - describes the event. -* REQUIRED user or group (object) - an object containing the details - of the recipient to be removed. In case of `user`, it MUST include: - * REQUIRED userId (string) - OCM Address of the user that performed - the removal, or that left. - * REQUIRED targetUserId (string) - only for "CONTACT_REMOVED": OCM - Address of the user, local at the target server, that was removed - by the above peer contact. It MUST be omitted for "USER_REMOVED", - where all contacts and shares related to `userId` at the target - server are to be removed. - Whereas in case of `group`, it MUST include: - * REQUIRED groupId (string) - identifier of the group to be removed - from the target OCM Server. +A notification MAY be sent when a user leaves the realm of the +Sending Server or a group is removed. The Receiving Server SHOULD +remove the departed recipient from its trusted users and related +shares. A single contact removal uses CONTACT_REMOVED instead. + +The `resourceType` MUST be omitted. The `shareType` MUST be `user` +for "USER_REMOVED" or `group` for "GROUP_REMOVED". The +`notification` object MUST contain a `user` object with a REQUIRED +`userId` OCM Address, or a `group` object with a REQUIRED `groupId` +string, respectively. An OPTIONAL `message` string describes the +event. USER_REMOVED identifies the departed user; it does not infer +departure from an omitted contact counterpart. The "USER_REMOVED" notification identifies the removed user only, and not which users of the target OCM Server it concerns. A Sending @@ -1617,37 +1595,93 @@ the `notification` object MUST include the following fields: complete host of the target OCM Server, as in its own OCM Addresses, compared in the same way. -A Sending Server MUST send a "CONTACT_REMOVED" notification only to an -OCM Server that advertises the `contact-removal` capability in its -[Discovery](#ocm-api-discovery) response. If the target OCM Server -does not advertise it, the Sending Server MUST NOT send a -"USER_REMOVED" notification instead, because the target OCM Server -could then remove the actor from the contacts of all of its users. -An OCM Server MUST NOT advertise the `contact-removal` capability -unless it processes "CONTACT_REMOVED" notifications as follows: - -* A payload that lacks a REQUIRED field, that includes a - `resourceType` (even if null or empty), that has a field of the - wrong type, or whose `userId` or `counterpartUserId` is not a valid - OCM Address, MUST be rejected with HTTP 400. -* If the host part of `userId` is not the `senderDomain`, if the host - part of `counterpartUserId` is not the complete host of the - Receiving Server, if the counterpart is not a user of the Receiving - Server, or if the - counterpart has no contact relationship with the actor, the - notification has no effect, and the Receiving Server SHOULD respond - with HTTP 201. The same applies to a repeated notification. -* Otherwise, the Receiving Server MAY reciprocally remove the actor - from the contacts of the counterpart. It MUST NOT remove the actor - from the contacts of any other user, and MUST NOT remove any other - contact of the counterpart. -* The Receiving Server MUST NOT send a "CONTACT_REMOVED" notification - back to the Sending Server as a consequence. - -Removing a contact concerns the contact relationship only: it does not -by itself delete any Share, and it does not revoke access tokens that -were already issued, which remain valid until they expire. - +A Sending Server MUST send a "CONTACT_REMOVED" notification only when +the target advertises both notifications and the versionless +`contact-removal` capability in [Discovery](#ocm-api-discovery). It +MUST NOT fall back to USER_REMOVED. Advertising contact-removal +commits to the complete validation, grant-denial and local acceptance +contract below across every enabled serving path. Unsupported driver, +token, application or transfer configurations MUST NOT advertise it. + +A payload lacking a REQUIRED field, containing `resourceType` even +when null or empty, containing a wrong-type field, or containing an +invalid OCM Address MUST receive HTTP 400 before effects. Extra +`localUserId` or other fields have no selection authority and MUST NOT +replace or override `counterpartUserId`. The actor host MUST match +`senderDomain` and the counterpart host MUST match the Receiving +Server's complete OCM host, including any port, lowercasing hosts +only. A syntactically valid host-binding mismatch has no effect and +SHOULD receive bodyless HTTP 201 for privacy. Syntax and binding do +not authenticate the sender; existing authentication, authorization +and provider-policy denials still apply. + +For an authorized, correctly bound removal, each server MUST revoke +every local OCM Share grant whose actual sender/creator and individual +user recipient are the actor and counterpart, in EITHER direction. +This includes pending and accepted grants, all resources and +protocols, and affected incoming recipient projections. Resource +ownership alone does not select a grant. Alice resharing Mahdi's +resource to Bob is an Alice/Bob grant; Mahdi's independent grant to +Bob is unaffected. Other pairs, groups, providers and ports, other +recipients' projections of the same grant, underlying resources, +accounts and account-wide credentials MUST remain intact. + +Both the initiating server before reporting local success and a +capable Receiving Server before accepting the event MUST durably deny +the affected grants at their controlled enforcement points. Every new +authorization after that local security commit MUST reject affected +old secrets, codes, bearer tokens, cached sessions and derived grant +authority. Existing scope restrictions still apply. Removing a contact +row, hiding a share, or waiting for token expiry is insufficient. +Physical cleanup MAY finish later while authorization remains denied. +Requests authorized before the commit may finish; tracked transfers +SHOULD receive best-effort cancellation. Already delivered bytes and +downloaded copies cannot be recalled. + +Missing contact or directory entries MUST NOT skip grant +reconciliation. The server MUST reconcile authoritative exact stored +pair, grant, projection and pending-cleanup bindings. Directory +transport errors, non-NOT_FOUND errors and malformed OK-with-nil-user +responses are not proof of absence and MUST NOT justify successful +no-op acceptance. Even true NOT_FOUND with residual bindings requires +selective durable denial, or a retryable server failure if safe +identity mapping or enforcement cannot be established. Servers MUST +NOT guess a local IdP or infer identity from owners, credentials or +endpoints. Only proven absence of affected grants, projections, +pending cleanup and required contact mutation permits a genuine no-op +bodyless HTTP 201. + +The Receiving Server MAY reciprocally delete the exact contact after +grant denial. It MUST preserve all other contacts and MUST NOT reflect +CONTACT_REMOVED or synthesize per-share SHARE_UNSHARED notifications. +Failures before durable denial MUST return a retryable server error, +never HTTP 201; unsupported handling MAY return HTTP 501. An accepted +CONTACT_REMOVED MUST receive bodyless HTTP 201. That receipt requires +the local denial precondition, not physical cleanup or global effects. +Unknown-type and genuine no-op receipts do not imply mutation. + +Unchanged repeats are idempotent and grant no new send entitlement. +Ordinary invitation-based reconnect MUST remain available; newly +authorized grants use fresh identities and MUST NOT revive revoked +grant IDs or projections. Concurrent and delayed local writers and +cleanup MUST respect the durable removal boundary. An internal local +epoch MAY serialize those operations. There is no public relationship +identifier, permanent pair block or cross-server ordering guarantee. +An event applies to the exact pair at its local processing boundary. + +After local enforcement, a newly completed local removal with contact +deletion OR newly denied grants permits at most ONE best-effort +notification attempt. A missing contact MUST NOT suppress a grant-only +first attempt. Genuine no-ops, unchanged repeats, inbound events and +local recovery MUST NOT send again. There MUST NOT be a delivery +retry, outbox, persistent outbound queue or restart send. Local +interrupted cleanup may recover without reopening grants or scheduling +delivery. An unavailable peer, lost send or lost response leaves peer +enforcement unconfirmed; there is no globally instantaneous revocation +or automatic remote convergence. Each peer enforces only after +processing the event. The versionless capability and a receipt alone +do not prove compatible peer semantics; interoperable enforcement +requires compatible peers. # Resource Access diff --git a/spec.yaml b/spec.yaml index f05d820..b17c59f 100644 --- a/spec.yaml +++ b/spec.yaml @@ -227,9 +227,12 @@ paths: responses: "201": description: > - Receiver succesfully received the notification. The response body - MAY contain a JSON object with response data, depending on - the actual notification. + An accepted CONTACT_REMOVED has a bodyless 201 response only after + durable local denial of affected exact-pair grants. Failure to + establish safe identity or enforcement is a retryable server error, + not acceptance. A genuine authoritative no-op may receive bodyless + 201. This does not promise physical cleanup or global completion. + For other notification types the response MAY contain response data. "400": description: > Bad request due to invalid parameters, e.g. when `notificationType` is @@ -1230,7 +1233,11 @@ components: Payload requirements of the CONTACT_REMOVED notification, which omits the `resourceType` and identifies both users of the removed contact. It is only sent to servers advertising the - `contact-removal` capability. + `contact-removal` capability, which commits to durable local revocation + of both directions of exact sender/creator-to-user-recipient grants and + their affected credentials before acceptance. Other pairs, projections, + resources and account-wide credentials remain intact. Missing directory + or contact entries do not establish authoritative absence of grants. required: - notificationType - shareType From 084907aaa2b2224bc8e93b049136bffa8e2e8d22 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Thu, 5 Nov 2026 09:00:00 +0000 Subject: [PATCH 06/10] docs(ocm): use canonical contact example Signed-off-by: Mahdi Baghbani From b6f9f6ebd0da6225359b32c66a614eff500570b2 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Thu, 5 Nov 2026 13:00:00 +0000 Subject: [PATCH 07/10] docs(ocm): keep grants after contact removal Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 108 ++++++++++++++++------------------------------------ spec.yaml | 26 ++++++------- 2 files changed, 45 insertions(+), 89 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index 168c37a..6d7df72 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -1576,7 +1576,13 @@ from their contacts, e.g. a contact established through a previous successful Invitation. Unlike "USER_REMOVED", this notification identifies both users of the contact: the user who removed the contact, called the actor, and the user who was removed, called the -counterpart. +counterpart. "CONTACT_REMOVED" informs the Receiving Server that the +user identified by `notification.user.userId` removed the user +identified by `notification.user.targetUserId` from their contacts. +The Receiving Server MAY remove the corresponding contact. It MUST +NOT treat this notification alone as a withdrawal or rejection of a +Share: existing Shares in either direction remain governed by their +own permissions, expiration and explicit lifecycle operations. For this case, the `notificationType` MUST be "CONTACT_REMOVED", the `resourceType` MUST be omitted, the `shareType` MUST be `user`, and @@ -1589,7 +1595,7 @@ the `notification` object MUST include the following fields: of the Sending Server who removed the contact. Its host part MUST be the `senderDomain`, compared as a whole host without regard to case; a port is never removed to make them match. - * REQUIRED counterpartUserId (string) - OCM Address of the + * REQUIRED targetUserId (string) - OCM Address of the counterpart, i.e. the user of the target OCM Server who was removed from the contacts of the actor. Its host part is the complete host of the target OCM Server, as in its own OCM @@ -1598,16 +1604,17 @@ the `notification` object MUST include the following fields: A Sending Server MUST send a "CONTACT_REMOVED" notification only when the target advertises both notifications and the versionless `contact-removal` capability in [Discovery](#ocm-api-discovery). It -MUST NOT fall back to USER_REMOVED. Advertising contact-removal -commits to the complete validation, grant-denial and local acceptance -contract below across every enabled serving path. Unsupported driver, -token, application or transfer configurations MUST NOT advertise it. +MUST NOT fall back to USER_REMOVED. Advertising `contact-removal` +means support for the contact operation and its binding and validation +as specified here; it is not a commitment to withdraw or reject +Shares across every enabled application. A payload lacking a REQUIRED field, containing `resourceType` even when null or empty, containing a wrong-type field, or containing an invalid OCM Address MUST receive HTTP 400 before effects. Extra -`localUserId` or other fields have no selection authority and MUST NOT -replace or override `counterpartUserId`. The actor host MUST match +fields have no selection authority and MUST NOT replace or override +`targetUserId`; old target spellings such as `localUserId` or +`counterpartUserId` are not aliases. The actor host MUST match `senderDomain` and the counterpart host MUST match the Receiving Server's complete OCM host, including any port, lowercasing hosts only. A syntactically valid host-binding mismatch has no effect and @@ -1615,73 +1622,24 @@ SHOULD receive bodyless HTTP 201 for privacy. Syntax and binding do not authenticate the sender; existing authentication, authorization and provider-policy denials still apply. -For an authorized, correctly bound removal, each server MUST revoke -every local OCM Share grant whose actual sender/creator and individual -user recipient are the actor and counterpart, in EITHER direction. -This includes pending and accepted grants, all resources and -protocols, and affected incoming recipient projections. Resource -ownership alone does not select a grant. Alice resharing Mahdi's -resource to Bob is an Alice/Bob grant; Mahdi's independent grant to -Bob is unaffected. Other pairs, groups, providers and ports, other -recipients' projections of the same grant, underlying resources, -accounts and account-wide credentials MUST remain intact. - -Both the initiating server before reporting local success and a -capable Receiving Server before accepting the event MUST durably deny -the affected grants at their controlled enforcement points. Every new -authorization after that local security commit MUST reject affected -old secrets, codes, bearer tokens, cached sessions and derived grant -authority. Existing scope restrictions still apply. Removing a contact -row, hiding a share, or waiting for token expiry is insufficient. -Physical cleanup MAY finish later while authorization remains denied. -Requests authorized before the commit may finish; tracked transfers -SHOULD receive best-effort cancellation. Already delivered bytes and -downloaded copies cannot be recalled. - -Missing contact or directory entries MUST NOT skip grant -reconciliation. The server MUST reconcile authoritative exact stored -pair, grant, projection and pending-cleanup bindings. Directory -transport errors, non-NOT_FOUND errors and malformed OK-with-nil-user -responses are not proof of absence and MUST NOT justify successful -no-op acceptance. Even true NOT_FOUND with residual bindings requires -selective durable denial, or a retryable server failure if safe -identity mapping or enforcement cannot be established. Servers MUST -NOT guess a local IdP or infer identity from owners, credentials or -endpoints. Only proven absence of affected grants, projections, -pending cleanup and required contact mutation permits a genuine no-op -bodyless HTTP 201. - -The Receiving Server MAY reciprocally delete the exact contact after -grant denial. It MUST preserve all other contacts and MUST NOT reflect -CONTACT_REMOVED or synthesize per-share SHARE_UNSHARED notifications. -Failures before durable denial MUST return a retryable server error, -never HTTP 201; unsupported handling MAY return HTTP 501. An accepted -CONTACT_REMOVED MUST receive bodyless HTTP 201. That receipt requires -the local denial precondition, not physical cleanup or global effects. -Unknown-type and genuine no-op receipts do not imply mutation. - -Unchanged repeats are idempotent and grant no new send entitlement. -Ordinary invitation-based reconnect MUST remain available; newly -authorized grants use fresh identities and MUST NOT revive revoked -grant IDs or projections. Concurrent and delayed local writers and -cleanup MUST respect the durable removal boundary. An internal local -epoch MAY serialize those operations. There is no public relationship -identifier, permanent pair block or cross-server ordering guarantee. -An event applies to the exact pair at its local processing boundary. - -After local enforcement, a newly completed local removal with contact -deletion OR newly denied grants permits at most ONE best-effort -notification attempt. A missing contact MUST NOT suppress a grant-only -first attempt. Genuine no-ops, unchanged repeats, inbound events and -local recovery MUST NOT send again. There MUST NOT be a delivery -retry, outbox, persistent outbound queue or restart send. Local -interrupted cleanup may recover without reopening grants or scheduling -delivery. An unavailable peer, lost send or lost response leaves peer -enforcement unconfirmed; there is no globally instantaneous revocation -or automatic remote convergence. Each peer enforces only after -processing the event. The versionless capability and a receipt alone -do not prove compatible peer semantics; interoperable enforcement -requires compatible peers. +Each user MAY independently withdraw Shares they issued to the other +user or reject Shares they received from them. Such actions use the +corresponding per-share notifications, for example "SHARE_UNSHARED" +and "SHARE_DECLINED", and remain explicit separate local decisions. +Removing one contact MUST NOT affect other users' contacts, other +grant recipients, Resources or account credentials. + +The Receiving Server MAY reciprocally delete the exact contact of the +actor and counterpart pair. It MUST preserve all other contacts and +MUST NOT reflect "CONTACT_REMOVED" back to the sender or synthesize +per-share notifications merely because it received this event. +Errors of the local contact operation MUST be reported as errors +rather than falsely claiming success; unsupported contact handling MAY +return HTTP 501. An accepted "CONTACT_REMOVED" receives bodyless HTTP +201; that receipt confirms acceptance of the event, not any Share +mutation, remote user choice or global completion. Delivery is one +best-effort attempt per committed contact transition; there is no +retry, outbox or cross-server ordering guarantee. # Resource Access diff --git a/spec.yaml b/spec.yaml index b17c59f..acda289 100644 --- a/spec.yaml +++ b/spec.yaml @@ -221,18 +221,16 @@ paths: message: Alice removed Bob from her contacts user: userId: alice@alice.example.org - counterpartUserId: bob@bob.example.org + targetUserId: bob@bob.example.org description: The JSON object with a new notification required: true responses: "201": description: > - An accepted CONTACT_REMOVED has a bodyless 201 response only after - durable local denial of affected exact-pair grants. Failure to - establish safe identity or enforcement is a retryable server error, - not acceptance. A genuine authoritative no-op may receive bodyless - 201. This does not promise physical cleanup or global completion. - For other notification types the response MAY contain response data. + An accepted notification receives a bodyless 201 response. The + receipt confirms acceptance of the event, not any Share mutation, + remote user choice or global completion. For other notification + types the response MAY contain response data. "400": description: > Bad request due to invalid parameters, e.g. when `notificationType` is @@ -1233,11 +1231,11 @@ components: Payload requirements of the CONTACT_REMOVED notification, which omits the `resourceType` and identifies both users of the removed contact. It is only sent to servers advertising the - `contact-removal` capability, which commits to durable local revocation - of both directions of exact sender/creator-to-user-recipient grants and - their affected credentials before acceptance. Other pairs, projections, - resources and account-wide credentials remain intact. Missing directory - or contact entries do not establish authoritative absence of grants. + `contact-removal` capability, which means support for the contact + operation and its binding and validation. The notification concerns + the contact relationship only; Shares in either direction remain + governed by their own permissions, expiration and explicit + lifecycle operations. required: - notificationType - shareType @@ -1260,7 +1258,7 @@ components: type: object required: - userId - - counterpartUserId + - targetUserId properties: userId: description: > @@ -1268,7 +1266,7 @@ components: the contact. Its host part is the `senderDomain`. allOf: - $ref: "#/components/schemas/OCMAddress" - counterpartUserId: + targetUserId: description: > OCM Address of the user of the receiving server who was removed from the contacts. From 437546e7cff96786696c6315290a8641d52d3d10 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Thu, 5 Nov 2026 17:00:00 +0000 Subject: [PATCH 08/10] docs(ocm): clarify contact removal requirements Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 15 ++++++++++----- spec.yaml | 35 ++++++++++++++++------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index 6d7df72..0d3c250 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -1403,6 +1403,9 @@ request: * REQUIRED notificationType (string) - it MUST be one of the registered values listed in the "OCM Notification Types" registry (see [IANA Considerations](#iana-considerations)). + The value MUST contain at least one character outside the Unicode + White_Space property and MUST be compared exactly as sent, without + trimming or case folding. * REQUIRED senderDomain (string) - the server part of the sender's OCM Addresses, `host [":" port]`, with the host and port grammar of an OCM Address, for example `cloud.example.org`, @@ -1426,6 +1429,10 @@ request: * OPTIONAL notification (object) - optional additional parameters, depending on the notification and the resource type. +Receivers MUST validate string syntax and identity bindings before +applying a notification. The OpenAPI schemas express structural +constraints and do not perform all of these checks. + A Notification payload MUST include either the `resourceType` or the `shareType`, or both when relevant. Multiple Notification types are defined for different purposes, and additional types MAY be defined @@ -1613,8 +1620,7 @@ A payload lacking a REQUIRED field, containing `resourceType` even when null or empty, containing a wrong-type field, or containing an invalid OCM Address MUST receive HTTP 400 before effects. Extra fields have no selection authority and MUST NOT replace or override -`targetUserId`; old target spellings such as `localUserId` or -`counterpartUserId` are not aliases. The actor host MUST match +`targetUserId`. The actor host MUST match `senderDomain` and the counterpart host MUST match the Receiving Server's complete OCM host, including any port, lowercasing hosts only. A syntactically valid host-binding mismatch has no effect and @@ -1637,9 +1643,8 @@ Errors of the local contact operation MUST be reported as errors rather than falsely claiming success; unsupported contact handling MAY return HTTP 501. An accepted "CONTACT_REMOVED" receives bodyless HTTP 201; that receipt confirms acceptance of the event, not any Share -mutation, remote user choice or global completion. Delivery is one -best-effort attempt per committed contact transition; there is no -retry, outbox or cross-server ordering guarantee. +mutation, remote user choice or global completion. This +notification provides no cross-server ordering guarantee. # Resource Access diff --git a/spec.yaml b/spec.yaml index acda289..0d535ba 100644 --- a/spec.yaml +++ b/spec.yaml @@ -1102,12 +1102,11 @@ components: properties: notificationType: type: string - pattern: "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\ - \\u1680\\u2000-\\u200A\\u2028\\u2029\ - \\u202F\\u205F\\u3000]" + minLength: 1 description: > - A notification type that is understandable for both humans and - machines, providing information about an event. + A non-blank notification type, compared exactly as sent without + trimming or case folding. Values containing only Unicode + White_Space are invalid. Any value registered in the OCM IANA Registry for notification types MAY be used here. Implementers MAY define additional custom types, provided that they are registered in the OCM IANA Registry. @@ -1277,25 +1276,23 @@ components: - resourceType OCMHost: type: string + minLength: 1 description: > - The server part of an OCM Address, `host [":" port]`, with the host - and port grammar of the OCMAddress schema, for example - `cloud.example.org`, `cloud.example.org:8443`, `192.0.2.1` or - `[2001:db8::1]:8443`. It has no identifier, scheme, path, query or - fragment. Two values name the same server only when they are equal - after lowercasing; a port is never removed or assumed. + The server part of an OCM Address, `host [":" port]`. + The host and port follow RFC 3986 sections 3.2.2 and 3.2.3. + IPv6 and IPvFuture literals use brackets. No identifier, scheme, + path, query or fragment is allowed. Compare the complete value + after lowercasing; never remove or assume a port. example: cloud.example.org:8443 - pattern: '^(?:\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){1}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[vV][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&''()*+,;=:]+)\]|(?:[A-Za-z0-9\-._~!$&''()*+,;=]|%[0-9A-Fa-f]{2})+)(?::[0-9]*)?(?![\s\S])' OCMAddress: type: string + minLength: 1 description: > - An OCM Address, `identifier "@" host [":" port]`. The identifier is - an opaque, case-sensitive string separated from the host by the last - "@". The host follows RFC 3986 section 3.2.2: an IPv6 or IPvFuture - literal in brackets, an IPv4 address or a registered name. The - IPv6 alternative spells out the RFC grammar, and the input ends - with the host or port, so a trailing newline is not accepted. - pattern: '^[\s\S]+@(?:\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){1}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[vV][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&''()*+,;=:]+)\]|(?:[A-Za-z0-9\-._~!$&''()*+,;=]|%[0-9A-Fa-f]{2})+)(?::[0-9]*)?(?![\s\S])' + An OCM Address, `identifier "@" host [":" port]`, as defined + in the Internet-Draft. The identifier is non-empty, opaque + and case-sensitive, separated from the host by the last "@". + The host and port follow the OCMHost definition. + example: alice@cloud.example.org:8443 AcceptedInvite: type: object required: From 5510bbd10101e48fde1f5e220f70305d3854f2da Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Fri, 6 Nov 2026 10:00:00 +0000 Subject: [PATCH 09/10] docs(ocm): keep contact removal focused Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 156 +++++++++++++++------------------------------------- spec.yaml | 139 ++++------------------------------------------ 2 files changed, 55 insertions(+), 240 deletions(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index 0d3c250..411d975 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -368,10 +368,8 @@ Acceptance Request MUST be a HTTP POST request: * using `application/json` as the `Content-Type` HTTP request header * its request body containing a JSON document representing an object with the following string fields: - - REQUIRED: `recipientProvider` - The server part, - `host [":" port]`, of the Invite Receiver's OCM Addresses, with - the same grammar and comparison as the `senderDomain` field of a - Notification. + - REQUIRED: `recipientProvider` - FQDN of the Invite Receiver OCM + Server. - REQUIRED: `token` - The Invite Token. The Invite Sender OCM Server SHOULD recall which Invite Sender OCM Address this token was linked to. @@ -717,9 +715,6 @@ contain the following information about its OCM API: capability. Example: `["exchange-token", "protocol-object"]`. The array MAY include one or more of the following items: - - `"contact-removal"` - to indicate that this OCM Server - processes "CONTACT_REMOVED" notifications as specified in - [Contact Removal](#contact-removal). - `"enforce-mfa"` - to indicate that this OCM Server can apply a Sending Server's MFA requirements for a Share on their behalf. - `"exchange-token"` - to indicate that this OCM Server supports the @@ -1403,22 +1398,10 @@ request: * REQUIRED notificationType (string) - it MUST be one of the registered values listed in the "OCM Notification Types" registry (see [IANA Considerations](#iana-considerations)). - The value MUST contain at least one character outside the Unicode - White_Space property and MUST be compared exactly as sent, without - trimming or case folding. -* REQUIRED senderDomain (string) - the server part of the sender's - OCM Addresses, `host [":" port]`, with the host and port grammar of - an OCM Address, for example `cloud.example.org`, - `cloud.example.org:8443`, `192.0.2.1` or `[2001:db8::1]:8443`. A - Sending Server MUST send the complete host and port, if any, that - its OCM Addresses use. Two values name the same server only when - they are equal after lowercasing; a Receiving Server MUST NOT remove - or assume a port, resolve a name, or use the endpoint found by - discovery to identify the sender, as that endpoint is only used to - reach it. A Receiving Server SHOULD use this information to verify - the HTTP message signature on the request, and it SHOULD also use - it to validate the underlying share or resource the notification - is about. +* REQUIRED senderDomain (string) - the FQDN of the sender. A Receiving + Server SHOULD use this information to verify the HTTP message + signature on the request, and it SHOULD also use it to validate the + underlying share or resource the notification is about. * OPTIONAL resourceType (string) - the type of the resource this notification is about (e.g., `file`). * OPTIONAL shareType (string) - the type of recipient this notification @@ -1429,10 +1412,6 @@ request: * OPTIONAL notification (object) - optional additional parameters, depending on the notification and the resource type. -Receivers MUST validate string syntax and identity bindings before -applying a notification. The OpenAPI schemas express structural -constraints and do not perform all of these checks. - A Notification payload MUST include either the `resourceType` or the `shareType`, or both when relevant. Multiple Notification types are defined for different purposes, and additional types MAY be defined @@ -1556,95 +1535,46 @@ such as HTTP 404. ## Recipient Removal -A notification MAY be sent when a user leaves the realm of the -Sending Server or a group is removed. The Receiving Server SHOULD +A notification MAY report a single contact removal, a user leaving +the Sending Server's realm, or a group removal. + +For "CONTACT_REMOVED", the Receiving Server MAY reciprocally remove +the contact between the two identified users. This notification alone +MUST NOT withdraw or reject existing Shares in either direction, or +affect other users' contacts. Each user MAY separately withdraw Shares +they issued or decline Shares they received, using the corresponding +per-share notifications. + +For "USER_REMOVED" and "GROUP_REMOVED", the Receiving Server SHOULD remove the departed recipient from its trusted users and related -shares. A single contact removal uses CONTACT_REMOVED instead. - -The `resourceType` MUST be omitted. The `shareType` MUST be `user` -for "USER_REMOVED" or `group` for "GROUP_REMOVED". The -`notification` object MUST contain a `user` object with a REQUIRED -`userId` OCM Address, or a `group` object with a REQUIRED `groupId` -string, respectively. An OPTIONAL `message` string describes the -event. USER_REMOVED identifies the departed user; it does not infer -departure from an omitted contact counterpart. - -The "USER_REMOVED" notification identifies the removed user only, and -not which users of the target OCM Server it concerns. A Sending -Server MUST NOT use it to inform that one of its users removed a -single contact: the "CONTACT_REMOVED" notification is defined for -that purpose (see [Contact Removal](#contact-removal)). - -## Contact Removal - -A notification MAY be sent to inform a target OCM Server that a user -of the Sending Server removed a single user of the target OCM Server -from their contacts, e.g. a contact established through a previous -successful Invitation. Unlike "USER_REMOVED", this notification -identifies both users of the contact: the user who removed the -contact, called the actor, and the user who was removed, called the -counterpart. "CONTACT_REMOVED" informs the Receiving Server that the -user identified by `notification.user.userId` removed the user -identified by `notification.user.targetUserId` from their contacts. -The Receiving Server MAY remove the corresponding contact. It MUST -NOT treat this notification alone as a withdrawal or rejection of a -Share: existing Shares in either direction remain governed by their -own permissions, expiration and explicit lifecycle operations. - -For this case, the `notificationType` MUST be "CONTACT_REMOVED", the -`resourceType` MUST be omitted, the `shareType` MUST be `user`, and -the `notification` object MUST include the following fields: +shares. A single contact removal MUST NOT be reported as user departure. + +For these cases, a notification payload is to be formed such that the +`resourceType` MUST be omitted, the `shareType` is set to the affected +shareType being removed, such as `user` or `group`, and the +`notificationType` MUST be one of: +- "CONTACT_REMOVED", to inform that a single user contact was removed + from the list of trusted contacts by the peer user. +- "USER_REMOVED", to inform that a user left or was removed from the + realm of the sending OCM Server, and is therefore no longer reachable + as a contact. +- "GROUP_REMOVED", to inform about a removed group of users. +Further, the `notification` object MUST include the following fields: * OPTIONAL message (string) - an optional human-readable message that describes the event. -* REQUIRED user (object) - an object containing the details of the - contact, including: - * REQUIRED userId (string) - OCM Address of the actor, i.e. the user - of the Sending Server who removed the contact. Its host part MUST - be the `senderDomain`, compared as a whole host without regard to - case; a port is never removed to make them match. - * REQUIRED targetUserId (string) - OCM Address of the - counterpart, i.e. the user of the target OCM Server who was - removed from the contacts of the actor. Its host part is the - complete host of the target OCM Server, as in its own OCM - Addresses, compared in the same way. - -A Sending Server MUST send a "CONTACT_REMOVED" notification only when -the target advertises both notifications and the versionless -`contact-removal` capability in [Discovery](#ocm-api-discovery). It -MUST NOT fall back to USER_REMOVED. Advertising `contact-removal` -means support for the contact operation and its binding and validation -as specified here; it is not a commitment to withdraw or reject -Shares across every enabled application. - -A payload lacking a REQUIRED field, containing `resourceType` even -when null or empty, containing a wrong-type field, or containing an -invalid OCM Address MUST receive HTTP 400 before effects. Extra -fields have no selection authority and MUST NOT replace or override -`targetUserId`. The actor host MUST match -`senderDomain` and the counterpart host MUST match the Receiving -Server's complete OCM host, including any port, lowercasing hosts -only. A syntactically valid host-binding mismatch has no effect and -SHOULD receive bodyless HTTP 201 for privacy. Syntax and binding do -not authenticate the sender; existing authentication, authorization -and provider-policy denials still apply. - -Each user MAY independently withdraw Shares they issued to the other -user or reject Shares they received from them. Such actions use the -corresponding per-share notifications, for example "SHARE_UNSHARED" -and "SHARE_DECLINED", and remain explicit separate local decisions. -Removing one contact MUST NOT affect other users' contacts, other -grant recipients, Resources or account credentials. - -The Receiving Server MAY reciprocally delete the exact contact of the -actor and counterpart pair. It MUST preserve all other contacts and -MUST NOT reflect "CONTACT_REMOVED" back to the sender or synthesize -per-share notifications merely because it received this event. -Errors of the local contact operation MUST be reported as errors -rather than falsely claiming success; unsupported contact handling MAY -return HTTP 501. An accepted "CONTACT_REMOVED" receives bodyless HTTP -201; that receipt confirms acceptance of the event, not any Share -mutation, remote user choice or global completion. This -notification provides no cross-server ordering guarantee. +* REQUIRED user or group (object) - an object containing the details + of the recipient to be removed. In case of `user`, it MUST include: + * REQUIRED userId (string) - OCM Address of the user that performed + the removal, or that left. + * REQUIRED targetUserId (string) - only for "CONTACT_REMOVED": OCM + Address of the user, local at the target server, that was removed + by the above peer contact. It MUST be omitted for "USER_REMOVED", + where all contacts and shares related to `userId` at the target + server are to be removed. + Whereas in case of `group`, it MUST include: + * REQUIRED groupId (string) - identifier of the group to be removed + from the target OCM Server. + # Resource Access diff --git a/spec.yaml b/spec.yaml index 0d535ba..de37e05 100644 --- a/spec.yaml +++ b/spec.yaml @@ -210,27 +210,14 @@ paths: application/json: schema: $ref: "#/components/schemas/NewNotification" - examples: - contactWasRemoved: - summary: A user removed a single contact - value: - notificationType: CONTACT_REMOVED - shareType: user - senderDomain: alice.example.org - notification: - message: Alice removed Bob from her contacts - user: - userId: alice@alice.example.org - targetUserId: bob@bob.example.org description: The JSON object with a new notification required: true responses: "201": description: > - An accepted notification receives a bodyless 201 response. The - receipt confirms acceptance of the event, not any Share mutation, - remote user choice or global completion. For other notification - types the response MAY contain response data. + Receiver succesfully received the notification. The response body + MAY contain a JSON object with response data, depending on + the actual notification. "400": description: > Bad request due to invalid parameters, e.g. when `notificationType` is @@ -580,7 +567,6 @@ components: items: type: string enum: - - contact-removal - enforce-mfa - exchange-token - http-sig @@ -1102,24 +1088,19 @@ components: properties: notificationType: type: string - minLength: 1 description: > - A non-blank notification type, compared exactly as sent without - trimming or case folding. Values containing only Unicode - White_Space are invalid. + A notification type that is understandable for both humans and + machines, providing information about an event. Any value registered in the OCM IANA Registry for notification types MAY be used here. Implementers MAY define additional custom types, provided that they are registered in the OCM IANA Registry. senderDomain: + type: string description: > - The complete host and port, if any, of the sending OCM server, - as in the server part of its OCM Addresses. It identifies the - sender; the endpoint found by discovery is only used to reach it. - allOf: - - $ref: "#/components/schemas/OCMHost" + The FQDN of the sending OCM server, in order for the recipient + to validate its key material when verifying the HTTP Signature. resourceType: type: string - minLength: 1 description: > Resource type, according to the OCM IANA Registry for resource types. E.g. file, folder, calendar, contact, etc. Implementers MAY define @@ -1127,7 +1108,6 @@ components: respective OCM IANA Registry. shareType: type: string - minLength: 1 description: > Share type, according to the OCM IANA Registry for share types. E.g. user, group, federation, etc. Implementers MAY define @@ -1148,11 +1128,6 @@ components: type: string description: > An optional human-readable message that describes the event. - sharedSecret: - type: string - description: > - An optional secret related to the underlying share. It does not - authenticate the sender. additionalProperties: type: object description: > @@ -1162,25 +1137,6 @@ components: `providerId` of the underlying resource, and in case of `user`, `group`, or `federation` share types, it MUST contain at least the `userId`, `groupId`, or `mlsGroupId` of the underlying recipient, respectively. - allOf: - - description: > - A notification MUST include either the `resourceType` or the - `shareType`, or both when relevant. - anyOf: - - required: - - resourceType - - required: - - shareType - - description: > - A CONTACT_REMOVED notification MUST satisfy its specific payload - requirements. - anyOf: - - properties: - notificationType: - not: - enum: - - CONTACT_REMOVED - - $ref: "#/components/schemas/ContactRemovedNotification" example: legacyShareWasAccepted: notificationType: SHARE_ACCEPTED @@ -1224,75 +1180,6 @@ components: mlsGroupId: 51dc30ddc473d43a6011e9ebba6ca770 userId: bob@cloud.example.org content: base64 MLS welcome - ContactRemovedNotification: - type: object - description: > - Payload requirements of the CONTACT_REMOVED notification, which - omits the `resourceType` and identifies both users of the removed - contact. It is only sent to servers advertising the - `contact-removal` capability, which means support for the contact - operation and its binding and validation. The notification concerns - the contact relationship only; Shares in either direction remain - governed by their own permissions, expiration and explicit - lifecycle operations. - required: - - notificationType - - shareType - - notification - properties: - notificationType: - type: string - enum: - - CONTACT_REMOVED - shareType: - type: string - enum: - - user - notification: - type: object - required: - - user - properties: - user: - type: object - required: - - userId - - targetUserId - properties: - userId: - description: > - OCM Address of the user of the sending server who removed - the contact. Its host part is the `senderDomain`. - allOf: - - $ref: "#/components/schemas/OCMAddress" - targetUserId: - description: > - OCM Address of the user of the receiving server who was - removed from the contacts. - allOf: - - $ref: "#/components/schemas/OCMAddress" - not: - required: - - resourceType - OCMHost: - type: string - minLength: 1 - description: > - The server part of an OCM Address, `host [":" port]`. - The host and port follow RFC 3986 sections 3.2.2 and 3.2.3. - IPv6 and IPvFuture literals use brackets. No identifier, scheme, - path, query or fragment is allowed. Compare the complete value - after lowercasing; never remove or assume a port. - example: cloud.example.org:8443 - OCMAddress: - type: string - minLength: 1 - description: > - An OCM Address, `identifier "@" host [":" port]`, as defined - in the Internet-Draft. The identifier is non-empty, opaque - and case-sensitive, separated from the host by the last "@". - The host and port follow the OCMHost definition. - example: alice@cloud.example.org:8443 AcceptedInvite: type: object required: @@ -1303,12 +1190,10 @@ components: - name properties: recipientProvider: - description: > - The complete host and port, if any, of the receiver OCM server, - as in the server part of its OCM Addresses. - example: receiver.org:8443 - allOf: - - $ref: "#/components/schemas/OCMHost" + type: string + format: fqdn + description: FQDN of the receiver OCM service. + example: receiver.org token: type: string description: Token received in the invite From 4e1cef721595826e1655e17368a9df2c411fa62d Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Fri, 6 Nov 2026 16:00:00 +0000 Subject: [PATCH 10/10] docs(ocm): correct contact event changelog name Signed-off-by: Mahdi Baghbani --- IETF-OCM.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IETF-OCM.md b/IETF-OCM.md index 411d975..a3fd380 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -2930,7 +2930,7 @@ The complete changelog is updated in the OCM-API GitHub repository. * Introduced an `apps` array in the `webapp` protocol payload, listing the available web applications each with the media types it can handle. It replaces the `appName` and `mediaTypes` properties. -* Introduced a CONTACT_REMOVAL notification. +* Introduced a CONTACT_REMOVED notification. * Added reference to the OpenAPI protocol specification. ## Version 07