From c4bfd2feb84bb69e43f067209a11dd42ec183f9f Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Tue, 12 Aug 2025 12:17:57 +0100 Subject: [PATCH] Spec for MSC4133: Update profile endpoints to support extended fields (#2071) Signed-off-by: Tom Foster Signed-off-by: Johannes Marbach Co-authored-by: Patrick Cloke Co-authored-by: Johannes Marbach Co-authored-by: Richard van der Hoff --- .../newsfragments/2071.deprecation | 1 + .../client_server/newsfragments/2071.feature | 1 + content/client-server-api/_index.md | 69 ++++ .../client-server-api/modules/guest_access.md | 3 +- data/api/client-server/capabilities.yaml | 59 +++- data/api/client-server/profile.yaml | 312 ++++++++++-------- 6 files changed, 310 insertions(+), 135 deletions(-) create mode 100644 changelogs/client_server/newsfragments/2071.deprecation create mode 100644 changelogs/client_server/newsfragments/2071.feature diff --git a/changelogs/client_server/newsfragments/2071.deprecation b/changelogs/client_server/newsfragments/2071.deprecation new file mode 100644 index 00000000..34beb7a7 --- /dev/null +++ b/changelogs/client_server/newsfragments/2071.deprecation @@ -0,0 +1 @@ +Deprecate `m.set_avatar_url` and `m.set_displayname` capabilities, as per [MSC4133](https://github.com/matrix-org/matrix-spec-proposals/pull/4133). diff --git a/changelogs/client_server/newsfragments/2071.feature b/changelogs/client_server/newsfragments/2071.feature new file mode 100644 index 00000000..94b77218 --- /dev/null +++ b/changelogs/client_server/newsfragments/2071.feature @@ -0,0 +1 @@ +Update user profile endpoints to handle custom fields, and add a new `m.profile_fields` capability,as per [MSC4133](https://github.com/matrix-org/matrix-spec-proposals/pull/4133). diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 33e611c2..173769c9 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -2491,8 +2491,66 @@ using an `unstable` version. When this capability is not listed, clients should use `"1"` as the default and only stable `available` room version. +### `m.profile_fields` capability + +{{% added-in v="1.16" %}} + +This capability defines which [profile](#profiles) fields the user is +able to change. + +The capability value has a required flag, `enabled`, and two optional lists, `allowed` and +`disallowed`. + +When `enabled` is `false`, all profile fields are managed by the server +and the client is not permitted to make any changes. + +When `enabled` is `true`, clients are permitted to modify profile fields, +subject to the restrictions implied by the OPTIONAL lists `allowed` and +`disallowed`. + +If `allowed` is present, clients can modify only the fields +listed. They SHOULD assume all other fields to be managed by +the server. In this case, `disallowed` has no meaning and should be ignored. + +If `disallowed` is present (and `allowed` is not), clients SHOULD assume +that the listed fields are managed by the server. Clients may modify any +fields that are *not* listed, provided `enabled` is `true`. + +If neither `allowed` nor `disallowed` is present, clients can modify all fields +without restrictions, provided `enabled` is `true`. + +When this capability is not listed, clients SHOULD assume the user is able to change +profile fields without any restrictions, provided the homeserver +advertises a specification version that includes the `m.profile_fields` +capability in the [`/versions`](/client-server-api/#get_matrixclientversions) +response. + +An example of the capability API's response for this capability is: + +```json +{ + "capabilities": { + "m.profile_fields": { + "enabled": true, + "disallowed": ["displayname"] + } + } +} +``` + ### `m.set_displayname` capability +{{% boxes/note %}} +{{% changed-in v="1.16" %}} +This capability is now deprecated. Clients SHOULD use the +[`m.profile_fields`](/client-server-api/#mprofile_fields-capability) +capability instead. + +For backwards compatibility, servers that forbid setting the +`displayname` profile field in the `m.profile_fields` capability +MUST still present this capability with `"enabled": false`. +{{% /boxes/note %}} + This capability has a single flag, `enabled`, to denote whether the user is able to change their own display name via profile endpoints. Cases for disabling might include users mapped from external identity/directory @@ -2517,6 +2575,17 @@ An example of the capability API's response for this capability is: ### `m.set_avatar_url` capability +{{% boxes/note %}} +{{% changed-in v="1.16" %}} +This capability is now deprecated. Clients SHOULD use the +[`m.profile_fields`](/client-server-api/#mprofile_fields-capability) +capability instead. + +For backwards compatibility, servers that forbid setting the +`avatar_url` profile field in the `m.profile_fields` capability +MUST still present this capability with `"enabled": false`. +{{% /boxes/note %}} + This capability has a single flag, `enabled`, to denote whether the user is able to change their own avatar via profile endpoints. Cases for disabling might include users mapped from external identity/directory diff --git a/content/client-server-api/modules/guest_access.md b/content/client-server-api/modules/guest_access.md index ada9e71e..2e5ee3a0 100644 --- a/content/client-server-api/modules/guest_access.md +++ b/content/client-server-api/modules/guest_access.md @@ -63,7 +63,8 @@ for sending events: The following API endpoints are allowed to be accessed by guest accounts for their own account maintenance: -* [PUT /profile/{userId}/displayname](#put_matrixclientv3profileuseriddisplayname) +* [PUT /profile/{userId}/displayname](#put_matrixclientv3profileuseridkeyname). Guest users may only modify their display name; other profile fields may not be changed. +* {{% added-in v="1.16" %}} [DELETE /profile/{userId}/displayname](#delete_matrixclientv3profileuseridkeyname). Again, guest users may delete their display name but not other profile fields. * [GET /devices](#get_matrixclientv3devices) * [GET /devices/{deviceId}](#get_matrixclientv3devicesdeviceid) * [PUT /devices/{deviceId}](#put_matrixclientv3devicesdeviceid) diff --git a/data/api/client-server/capabilities.yaml b/data/api/client-server/capabilities.yaml index 523c6091..2e8c8849 100644 --- a/data/api/client-server/capabilities.yaml +++ b/data/api/client-server/capabilities.yaml @@ -73,11 +73,25 @@ paths: - default - available m.set_displayname: + deprecated: true $ref: '#/components/schemas/booleanCapability' - description: Capability to indicate if the user can change their display name. + description: | + **Deprecated:** Capability to indicate if the user can change their display name. + Refer to `m.profile_fields` for extended profile management. + + For backwards compatibility, servers that directly or indirectly include the + `displayname` profile field in the `m.profile_fields` capability MUST also + set this capability accordingly. m.set_avatar_url: + deprecated: true $ref: '#/components/schemas/booleanCapability' - description: Capability to indicate if the user can change their avatar. + description: | + **Deprecated:** Capability to indicate if the user can change their avatar. + Refer to `m.profile_fields` for extended profile management. + + For backwards compatibility, servers that directly or indirectly include the + `avatar_url` profile field in the `m.profile_fields` capability MUST also + set this capability accordingly. m.3pid_changes: $ref: '#/components/schemas/booleanCapability' description: Capability to indicate if the user can change 3PID associations @@ -86,6 +100,47 @@ paths: $ref: '#/components/schemas/booleanCapability' description: Capability to indicate if the user can generate tokens to log further clients into their account. + m.profile_fields: + x-addedInMatrixVersion: "1.16" + type: object + title: ProfileFieldsCapability + description: Capability to indicate if the user can set or modify extended profile fields via + [`PUT /_matrix/client/v3/profile/{userId}/{keyName}`](/client-server-api/#put_matrixclientv3profileuseridkeyname). + If absent, clients SHOULD assume custom profile fields are supported, provided the + homeserver advertises a specification version that includes `m.profile_fields` in the + [`/versions`](/client-server-api/#get_matrixclientversions) response. + properties: + allowed: + type: array + description: | + If present, a list of profile fields that clients are allowed to create, modify or delete, + provided `enabled` is `true`; no other profile fields may be changed. + + If absent, clients may set all profile fields except those forbidden by the `disallowed` + list, where present. + items: + type: string + example: + - "m.example_field" + - "org.example.job_title" + disallowed: + type: array + description: | + This property has no meaning if `allowed` is also specified. + + Otherwise, if present, a list of profile fields that clients are _not_ allowed to create, modify or delete. + Provided `enabled` is `true`, clients MAY assume that they can set any profile field which is not + included in this list. + items: + type: string + example: + - "org.example.managed_field" + enabled: + type: boolean + description: "`true` if the user can create, update or delete any profile fields, `false` otherwise." + example: true + required: + - enabled examples: response: value: { diff --git a/data/api/client-server/profile.yaml b/data/api/client-server/profile.yaml index 6e588ae3..dbbc1950 100644 --- a/data/api/client-server/profile.yaml +++ b/data/api/client-server/profile.yaml @@ -16,48 +16,117 @@ info: title: Matrix Client-Server Profile API version: 1.0.0 paths: - "/profile/{userId}/displayname": + "/profile/{userId}/{keyName}": put: - summary: Set the user's display name. + x-changedInMatrixVersion: + "1.16": This endpoint now accepts a variable `keyName` parameter. Previously only `displayname` and `avatar_url` were accepted. + summary: Set a profile field for a user. description: |- - This API sets the given user's display name. You must have permission to - set this user's display name, e.g. you need to have their `access_token`. - operationId: setDisplayName + Set or update a profile field for a user. Must be authenticated with an + access token authorised to make changes. Servers MAY impose size limits + on individual fields, and the total profile MUST be under 64 KiB. + + Servers MAY reject `null` values. Servers that accept `null` values SHOULD store + them rather than treating `null` as a deletion request. Clients that want to delete a + field, including its key and value, SHOULD use the `DELETE` endpoint instead. + operationId: setProfileField security: - accessTokenQuery: [] - accessTokenBearer: [] parameters: - in: path name: userId - description: The user whose display name to set. + description: The user whose profile field should be set. required: true example: "@alice:example.com" schema: type: string + - in: path + name: keyName + description: The name of the profile field to set. This MUST be either + `avatar_url`, `displayname`, or a custom field following the + [Common Namespaced Identifier Grammar](/appendices/#common-namespaced-identifier-grammar). + required: true + example: "displayname" + schema: + type: string + pattern: '^(avatar_url|displayname|[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+)$' requestBody: + description: A JSON object containing the property whose name matches + the `keyName` specified in the URL. See `additionalProperties` for + further details. + required: true content: application/json: schema: type: object - example: { - "displayname": "Alice Margatroid" - } - properties: - displayname: - type: string - description: The new display name for this user. - description: The new display name information. - required: true + minProperties: 1 + description: | + An object which contains exactly one property. The key + of that property MUST match the `keyName` specified in the URL. + + For `avatar_url`, the value MUST be an MXC URI string. + + For `displayname`, the value MUST be a string. + + For custom keys, any JSON type is allowed. Servers MAY not validate + these values, but clients SHOULD follow the format defined for that key. + additionalProperties: true + example: { "displayname": "Alice Wonderland" } responses: "200": - description: The display name was set. + description: The profile field was set. content: application/json: schema: - type: object # empty json object + type: object # empty JSON object examples: response: value: {} + "400": + description: | + The input was invalid in some way. This can include one + of the following error codes: + + - `M_BAD_JSON`: The provided value is not valid JSON. + - `M_MISSING_PARAM`: The required `keyName` property is + missing from the request body. + - `M_PROFILE_TOO_LARGE`: Storing the supplied value would + make the profile exceed its maximum allowed size of 64 KiB. + - `M_KEY_TOO_LARGE`: The supplied profile key exceeds the + maximum allowed key length of 255 bytes. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + bad_json: + value: + { + "errcode": "M_BAD_JSON", + "error": "Malformed JSON payload.", + } + invalid_key: + value: + { + "errcode": "M_INVALID_PARAM", + "error": "Invalid profile key.", + } + "403": + description: The server is unwilling to perform the operation, either + due to insufficient permissions or because profile modifications + are disabled. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + forbidden: + value: + { + "errcode": "M_FORBIDDEN", + "error": "Profile modification is not permitted.", + } "429": description: This request was rate-limited. content: @@ -67,163 +136,138 @@ paths: tags: - User data get: - summary: Get the user's display name. - description: |- - Get the user's display name. This API may be used to fetch the user's - own displayname or to query the name of other users; either locally or - on remote homeservers. - operationId: getDisplayName + x-changedInMatrixVersion: + "1.16": This endpoint now accepts a variable `keyName` parameter. Previously only `displayname` and `avatar_url` were accepted. + summary: Get a profile field for a user. + description: Get the value of a profile field for a user. + operationId: getProfileField parameters: - in: path name: userId - description: The user whose display name to get. + description: The user whose profile field should be returned. required: true example: "@alice:example.com" schema: type: string + - in: path + name: keyName + description: The name of the profile field to retrieve. + required: true + example: "displayname" + schema: + type: string + pattern: '^(avatar_url|displayname|[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+)$' responses: "200": - description: The display name for this user. + description: The profile field value was retrieved. content: application/json: schema: type: object - properties: - displayname: - type: string - description: The user's display name if they have set one, otherwise not - present. + description: | + An object with one property, whose key matches the `keyName` specified + in the URL, and whose value is the current setting of that profile field. + additionalProperties: true examples: response: - value: { - "displayname": "Alice Margatroid" - } + value: { "displayname": "Alice" } "403": x-addedInMatrixVersion: "1.12" - description: The server is unwilling to disclose whether the user exists and/or - has a display name. + description: The server is unwilling to disclose whether the user + exists and/or has the specified profile field. content: application/json: schema: $ref: definitions/errors/error.yaml examples: response: - value: { - "errcode": "M_FORBIDDEN", - "error": "Profile lookup is disabled on this homeserver" - } + value: + { + "errcode": "M_FORBIDDEN", + "error": "Profile lookup is disabled on this homeserver", + } "404": - description: There is no display name for this user or this user does not exist. + description: There is no profile field with this key for this user, or + the user does not exist. tags: - User data - "/profile/{userId}/avatar_url": - put: - summary: Set the user's avatar URL. - description: |- - This API sets the given user's avatar URL. You must have permission to - set this user's avatar URL, e.g. you need to have their `access_token`. - operationId: setAvatarUrl + delete: + x-addedInMatrixVersion: "1.16" + summary: Remove a profile field from a user. + description: Remove a specific field from a user's profile. + operationId: deleteProfileField security: - accessTokenQuery: [] - accessTokenBearer: [] parameters: - in: path name: userId - description: The user whose avatar URL to set. + description: The user whose profile field should be deleted. required: true example: "@alice:example.com" schema: type: string - requestBody: - content: - application/json: - schema: - type: object - example: { - "avatar_url": "mxc://matrix.org/wefh34uihSDRGhw34" - } - properties: - avatar_url: - type: string - format: uri - description: The new avatar URL for this user. - description: The new avatar information. - required: true - responses: - "200": - description: The avatar URL was set. - content: - application/json: - schema: - type: object # empty json object - examples: - response: - value: {} - "429": - description: This request was rate-limited. - content: - application/json: - schema: - $ref: definitions/errors/rate_limited.yaml - tags: - - User data - get: - summary: Get the user's avatar URL. - description: |- - Get the user's avatar URL. This API may be used to fetch the user's - own avatar URL or to query the URL of other users; either locally or - on remote homeservers. - operationId: getAvatarUrl - parameters: - in: path - name: userId - description: The user whose avatar URL to get. + name: keyName + description: The name of the profile field to delete. required: true - example: "@alice:example.com" + example: "displayname" schema: type: string + pattern: '^(avatar_url|displayname|[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+)$' responses: "200": - description: The avatar URL for this user. + description: The profile field was deleted or it doesn't exist. content: application/json: schema: type: object - properties: - avatar_url: - type: string - format: uri - description: The user's avatar URL if they have set one, otherwise not present. examples: response: - value: { - "avatar_url": "mxc://matrix.org/SDGdghriugerRg" - } + value: {} + "400": + description: The request is malformed, contains invalid JSON, or + specifies an invalid key. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + bad_json: + value: + { "errcode": "M_BAD_JSON", "error": "Malformed request." } + invalid_key: + value: + { + "errcode": "M_INVALID_PARAM", + "error": "Invalid profile key.", + } "403": - x-addedInMatrixVersion: "1.12" - description: The server is unwilling to disclose whether the user exists and/or - has an avatar URL. + description: The user is not authorised to delete this profile field. content: application/json: schema: $ref: definitions/errors/error.yaml examples: - response: - value: { - "errcode": "M_FORBIDDEN", - "error": "Profile lookup is disabled on this homeserver" - } - "404": - description: There is no avatar URL for this user or this user does not exist. + forbidden: + value: + { + "errcode": "M_FORBIDDEN", + "error": "Profile deletion is not permitted.", + } + "429": + description: This request was rate-limited. + content: + application/json: + schema: + $ref: definitions/errors/rate_limited.yaml tags: - User data "/profile/{userId}": get: - summary: Get this user's profile information. + summary: Get all profile information for a user. description: |- - Get the combined profile information for this user. This API may be used - to fetch the user's own profile information or other users; either - locally or on remote homeservers. + Get the complete profile for a user. operationId: getUserProfile parameters: - in: path @@ -243,45 +287,49 @@ paths: properties: avatar_url: type: string - format: uri + format: mx-mxc-uri description: The user's avatar URL if they have set one, otherwise not present. displayname: type: string description: The user's display name if they have set one, otherwise not present. + additionalProperties: + x-addedInMatrixVersion: "1.16" + description: Additional profile fields. examples: response: - value: { - "avatar_url": "mxc://matrix.org/SDGdghriugerRg", - "displayname": "Alice Margatroid" - } + value: + { + "avatar_url": "mxc://matrix.org/SDGdghriugerRg", + "displayname": "Alice Margatroid", + "m.example_field": "custom_value", + } "403": x-addedInMatrixVersion: "1.2" - description: The server is unwilling to disclose whether the user exists and/or - has profile information. + description: The server is unwilling to disclose whether the user + exists and/or has profile information. content: application/json: schema: $ref: definitions/errors/error.yaml examples: response: - value: { - "errcode": "M_FORBIDDEN", - "error": "Profile lookup is disabled on this homeserver" - } + value: + { + "errcode": "M_FORBIDDEN", + "error": "Profile lookup is disabled on this homeserver", + } "404": - description: There is no profile information for this user or this user does not - exist. + description: There is no profile information for this user or this + user does not exist. content: application/json: schema: $ref: definitions/errors/error.yaml examples: response: - value: { - "errcode": "M_NOT_FOUND", - "error": "Profile not found" - } + value: + { "errcode": "M_NOT_FOUND", "error": "Profile not found" } tags: - User data servers: