From 4fd6f1a83a70dc8fc7ee1fa82fc57101cd8ed338 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 12:26:24 -0400 Subject: [PATCH 1/7] Add proposal for per-room/per-space profile data Signed-off-by: Robin Townsend --- .../3189-per-room-per-space-profile-data.md | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 proposals/3189-per-room-per-space-profile-data.md diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md new file mode 100644 index 0000000000..b693548f0f --- /dev/null +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -0,0 +1,112 @@ +# MSC3189: Per-room/per-space profile data + +People frequently have different identities in different communities. In the +context of Matrix, users may therefore want their display name or avatar to +appear differently in certain social contexts, such as within a room or a space. + +While most clients technically already support per-room display names (by +getting profile data from a user's membership events in a room), this feature +suffers from a lack of documentation and server-side support. This proposal +attempts to improve on per-room/per-space profile data in the following ways: + +1. Documenting the current de-facto mechanism for per-room profile data +2. Keeping global profile changes from overwriting per-room profile data (if desired) +3. Allowing clients to set profile data for an entire space in a single request rather than sending membership events in bulk + +## Proposal + +### Per-room profile data + +First, the existing behavior: When showing a user's display name or avatar in a +room, clients should reference the `displayname` and `avatar_url` attributes of +the user's `m.room.member` state. Thus, to set a display name or avatar in a +specific room, clients should modify these attributes via the relevant state +APIs. + +In order to prevent per-room profile data from being overwritten when the user +updates their global profile, an optional query parameter named `force` of type +`boolean` is added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` +and `PUT /_matrix/client/r0/profile/{userId}/displayname` endpoints. + +If `force` is `true`, the profile change is propogated to all of the user's +rooms by adding, updating, or removing the relevant attribute of the user's +`m.room.member` state (and only that attribute) as needed. Unlike the current +behavior, updating `displayname` *must not* cause the user's `avatar_url` to +change in any rooms, and vice versa. + +If `force` is `false` (the default value), the profile change is only propogated +to rooms in which the relevant attribute (`displayname` or `avatar_url`) is +equal to that of the user's global profile before the update. This ensures that +by default, custom per-room profile data will not be overwritten. + +### Per-space profile data + +Per-space profile data is communicated in the same way as global and per-room +profile data, by updating the relevant `m.room.member` attributes in the +space-room and all of its children, recursively. To make this a simple operation +for clients, another optional query parameter named `space` of type `string` is +added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` and `PUT +/_matrix/client/r0/profile/{userId}/displayname` endpoints. + +If specified, `space` must be a valid ID of a room of which the user is a member +(regardless of whether it is of type `m.space`), and its effect is to limit the +scope of the profile change to the given space. This is achieved by first +updating the per-room profile data for the given space-room, and then recursing +into all `m.space.child` rooms of which the user is a member. + +The `space` parameter obeys `?force=false` as well, by only overwriting an +`m.room.member` attribute if it matches the previous profile data of the root +space *or* the user's global profile. If this is not the case, meaning a room +with a different per-room profile has been found, the profile change stops there +and does not continue recursing into the room's space children. Additionally, +servers must take care to handle cycles in the space graph and not recurse +infinitely (e.g. by tracking which rooms it has visited). + +## Potential issues + +This proposal assumes that having "one true display name per room" is a +desirable feature, since it minimizes complexity for clients and is compatible +with how most implementations already determine profile data. However, since +rooms can belong to multiple spaces, possibly with conflicting profile data, +this causes a certain degree of arbitrariness in what profile data gets set for +such rooms (depending purely on the order in which the user sets their per-space +profiles, and whether `force` is set). If this matters, users can always drill +down to room-level profile settings, though, and clients may assist them e.g. by +displaying a list of applicable per-space profiles to switch between. + +Arguably, per-space profile data should be a more first-class feature, with +server support for things like inheriting profile data from parent spaces on +join. This proposal leaves it up to clients to implement such "inheritance" +behavior as they see fit, by altering individual `m.room.member` states when +rooms are joined, added to spaces, etc. If desired, servers could be changed to +automate some of this behavior in the future, though arguably this should be +left to clients, since they have more context for e.g. which parent space the +user was viewing a room from when they joined it. + +## Alternatives + +An alternative would be to store per-room/per-space profile data as a part of +[extensible profiles](https://github.com/matrix-org/matrix-doc/pull/1769), +essentially keeping a public mapping of room IDs → profile data in a single +location. While altering `m.room.member` state gives us per-room and per-space +profile data for free, this alternative would require more action from clients +to implement. It would also leak data about users' profiles in private rooms, +which is a significant privacy concern, and it is unclear how conflicting +profiles would affect the "one source of truth" given by `m.room.member` state. +Furthermore, extensible profiles seem unlikely to land anytime soon, while +per-room/per-space profile data is arguably a more urgent feature, and should +not depend on it. + +## Security considerations + +None that I am aware of. + +## Unstable prefix + +During development of this feature the versions of the profile APIs augmented +with `force` and `space` will be available at unstable endpoints: + +```text +PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/avatar_url +PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/displayname +``` From 7d015093a308d35086b742f08cf52aa504facea5 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 13:19:48 -0400 Subject: [PATCH 2/7] Don't overwrite rooms using global profile when setting space profile Signed-off-by: Robin Townsend --- proposals/3189-per-room-per-space-profile-data.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md index b693548f0f..178f82e6b5 100644 --- a/proposals/3189-per-room-per-space-profile-data.md +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -56,11 +56,11 @@ into all `m.space.child` rooms of which the user is a member. The `space` parameter obeys `?force=false` as well, by only overwriting an `m.room.member` attribute if it matches the previous profile data of the root -space *or* the user's global profile. If this is not the case, meaning a room -with a different per-room profile has been found, the profile change stops there -and does not continue recursing into the room's space children. Additionally, -servers must take care to handle cycles in the space graph and not recurse -infinitely (e.g. by tracking which rooms it has visited). +space. If this is not the case, meaning a room with a different per-room profile +has been found, the profile change stops there and does not continue recursing +into the room's space children. Additionally, servers must take care to handle +cycles in the space graph and not recurse infinitely (e.g. by tracking which +rooms it has visited). ## Potential issues From 2479921cf776c65e1f449fa07b2cbacc03e396c0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 15:57:38 -0400 Subject: [PATCH 3/7] Allow propagation of profile changes to happen in the background Signed-off-by: Robin Townsend --- .../3189-per-room-per-space-profile-data.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md index 178f82e6b5..cd7fbcd956 100644 --- a/proposals/3189-per-room-per-space-profile-data.md +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -62,6 +62,24 @@ into the room's space children. Additionally, servers must take care to handle cycles in the space graph and not recurse infinitely (e.g. by tracking which rooms it has visited). +### Profile API semantics + +Unfortunately, there is a small issue in how the profile APIs function: When a +user sets their display name or avatar, the request may take a long time to +complete if the server has to update a large number of rooms. During this time, +the client's connection could easily time out, resulting in a state where only a +subset of rooms have received the profile change. This is not a new issue, but +it is especially problematic for this proposal, since future attempts at +changing profile data without specifying `?force=true` would interpret the rooms +that weren't updated as all having custom per-room profiles. + +To address this, it is proposed to change the meaning of a `200` response to a +`PUT /_matrix/client/r0/profile/{userId}/avatar_url` or `PUT +/_matrix/client/r0/profile/{userId}/displayname` request. Instead of indicating +that the profile data was changed, a `200` indicates that the profile change has +been acknowledged, and that if it has not already propogated to all relevant +rooms, it is being processed in the background. + ## Potential issues This proposal assumes that having "one true display name per room" is a From d1aa8d34483585c4164ac62494bbb5c7c554df28 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 17:24:27 -0400 Subject: [PATCH 4/7] Fix spelling of propagate Signed-off-by: Robin Townsend --- proposals/3189-per-room-per-space-profile-data.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md index cd7fbcd956..3415e49124 100644 --- a/proposals/3189-per-room-per-space-profile-data.md +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -28,13 +28,13 @@ updates their global profile, an optional query parameter named `force` of type `boolean` is added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` and `PUT /_matrix/client/r0/profile/{userId}/displayname` endpoints. -If `force` is `true`, the profile change is propogated to all of the user's +If `force` is `true`, the profile change is propagated to all of the user's rooms by adding, updating, or removing the relevant attribute of the user's `m.room.member` state (and only that attribute) as needed. Unlike the current behavior, updating `displayname` *must not* cause the user's `avatar_url` to change in any rooms, and vice versa. -If `force` is `false` (the default value), the profile change is only propogated +If `force` is `false` (the default value), the profile change is only propagated to rooms in which the relevant attribute (`displayname` or `avatar_url`) is equal to that of the user's global profile before the update. This ensures that by default, custom per-room profile data will not be overwritten. @@ -77,7 +77,7 @@ To address this, it is proposed to change the meaning of a `200` response to a `PUT /_matrix/client/r0/profile/{userId}/avatar_url` or `PUT /_matrix/client/r0/profile/{userId}/displayname` request. Instead of indicating that the profile data was changed, a `200` indicates that the profile change has -been acknowledged, and that if it has not already propogated to all relevant +been acknowledged, and that if it has not already propagated to all relevant rooms, it is being processed in the background. ## Potential issues From a77d7aeed8061a87b3014cd6191a816289e35232 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 18:21:27 -0400 Subject: [PATCH 5/7] Leave non-blocking profile APIs for a future MSC Signed-off-by: Robin Townsend --- .../3189-per-room-per-space-profile-data.md | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md index 3415e49124..0ea7beebe7 100644 --- a/proposals/3189-per-room-per-space-profile-data.md +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -62,24 +62,6 @@ into the room's space children. Additionally, servers must take care to handle cycles in the space graph and not recurse infinitely (e.g. by tracking which rooms it has visited). -### Profile API semantics - -Unfortunately, there is a small issue in how the profile APIs function: When a -user sets their display name or avatar, the request may take a long time to -complete if the server has to update a large number of rooms. During this time, -the client's connection could easily time out, resulting in a state where only a -subset of rooms have received the profile change. This is not a new issue, but -it is especially problematic for this proposal, since future attempts at -changing profile data without specifying `?force=true` would interpret the rooms -that weren't updated as all having custom per-room profiles. - -To address this, it is proposed to change the meaning of a `200` response to a -`PUT /_matrix/client/r0/profile/{userId}/avatar_url` or `PUT -/_matrix/client/r0/profile/{userId}/displayname` request. Instead of indicating -that the profile data was changed, a `200` indicates that the profile change has -been acknowledged, and that if it has not already propagated to all relevant -rooms, it is being processed in the background. - ## Potential issues This proposal assumes that having "one true display name per room" is a @@ -101,6 +83,15 @@ automate some of this behavior in the future, though arguably this should be left to clients, since they have more context for e.g. which parent space the user was viewing a room from when they joined it. +Finally, there is a pre-existing issue with the profile APIs: If the server has +to propagate a profile change to a large number of rooms, it could take a long +time, and the client could easily time out, potentially leaving some rooms +without the updated profile data. This is nothing new, but this proposal would +make it an even more broken state, since future attempts at changing profile +data without `?force=true` would interpret the rooms that weren't updated as all +having custom per-room profiles. It is expected that a future MSC will address +this by making the profile APIs non-blocking. + ## Alternatives An alternative would be to store per-room/per-space profile data as a part of From 96c8c036c469634451efb07d432ac5406ac0f882 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 11 May 2021 18:48:23 -0400 Subject: [PATCH 6/7] Clarify why space parameter doesn't care about room type Signed-off-by: Robin Townsend --- proposals/3189-per-room-per-space-profile-data.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md index 0ea7beebe7..c8cd087eb7 100644 --- a/proposals/3189-per-room-per-space-profile-data.md +++ b/proposals/3189-per-room-per-space-profile-data.md @@ -49,10 +49,11 @@ added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` and `PUT /_matrix/client/r0/profile/{userId}/displayname` endpoints. If specified, `space` must be a valid ID of a room of which the user is a member -(regardless of whether it is of type `m.space`), and its effect is to limit the -scope of the profile change to the given space. This is achieved by first -updating the per-room profile data for the given space-room, and then recursing -into all `m.space.child` rooms of which the user is a member. +(regardless of whether it is of type `m.space`[1](#f1)), and +its effect is to limit the scope of the profile change to the given space. This +is achieved by first updating the per-room profile data for the given +space-room, and then recursing into all `m.space.child` rooms of which the user +is a member. The `space` parameter obeys `?force=false` as well, by only overwriting an `m.room.member` attribute if it matches the previous profile data of the root @@ -119,3 +120,9 @@ with `force` and `space` will be available at unstable endpoints: PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/avatar_url PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/displayname ``` + +## Footnotes + +[1]: Room type is ignored primarily to be consistent with how the +space summary API handles room IDs, and also since rooms can technically have +`m.space.child` rooms without being of type `m.space` themselves. [↩](#a1) From fb6c4a2a8961fd03c864a30affe908ba380693d0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 23 May 2021 19:54:45 -0400 Subject: [PATCH 7/7] Make per-room/per-space profiles more first-class This completely reworks MSC3189. Signed-off-by: Robin Townsend --- .../3189-per-room-per-space-profile-data.md | 128 -------------- proposals/3189-per-room-per-space-profiles.md | 159 ++++++++++++++++++ 2 files changed, 159 insertions(+), 128 deletions(-) delete mode 100644 proposals/3189-per-room-per-space-profile-data.md create mode 100644 proposals/3189-per-room-per-space-profiles.md diff --git a/proposals/3189-per-room-per-space-profile-data.md b/proposals/3189-per-room-per-space-profile-data.md deleted file mode 100644 index c8cd087eb7..0000000000 --- a/proposals/3189-per-room-per-space-profile-data.md +++ /dev/null @@ -1,128 +0,0 @@ -# MSC3189: Per-room/per-space profile data - -People frequently have different identities in different communities. In the -context of Matrix, users may therefore want their display name or avatar to -appear differently in certain social contexts, such as within a room or a space. - -While most clients technically already support per-room display names (by -getting profile data from a user's membership events in a room), this feature -suffers from a lack of documentation and server-side support. This proposal -attempts to improve on per-room/per-space profile data in the following ways: - -1. Documenting the current de-facto mechanism for per-room profile data -2. Keeping global profile changes from overwriting per-room profile data (if desired) -3. Allowing clients to set profile data for an entire space in a single request rather than sending membership events in bulk - -## Proposal - -### Per-room profile data - -First, the existing behavior: When showing a user's display name or avatar in a -room, clients should reference the `displayname` and `avatar_url` attributes of -the user's `m.room.member` state. Thus, to set a display name or avatar in a -specific room, clients should modify these attributes via the relevant state -APIs. - -In order to prevent per-room profile data from being overwritten when the user -updates their global profile, an optional query parameter named `force` of type -`boolean` is added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` -and `PUT /_matrix/client/r0/profile/{userId}/displayname` endpoints. - -If `force` is `true`, the profile change is propagated to all of the user's -rooms by adding, updating, or removing the relevant attribute of the user's -`m.room.member` state (and only that attribute) as needed. Unlike the current -behavior, updating `displayname` *must not* cause the user's `avatar_url` to -change in any rooms, and vice versa. - -If `force` is `false` (the default value), the profile change is only propagated -to rooms in which the relevant attribute (`displayname` or `avatar_url`) is -equal to that of the user's global profile before the update. This ensures that -by default, custom per-room profile data will not be overwritten. - -### Per-space profile data - -Per-space profile data is communicated in the same way as global and per-room -profile data, by updating the relevant `m.room.member` attributes in the -space-room and all of its children, recursively. To make this a simple operation -for clients, another optional query parameter named `space` of type `string` is -added to the `PUT /_matrix/client/r0/profile/{userId}/avatar_url` and `PUT -/_matrix/client/r0/profile/{userId}/displayname` endpoints. - -If specified, `space` must be a valid ID of a room of which the user is a member -(regardless of whether it is of type `m.space`[1](#f1)), and -its effect is to limit the scope of the profile change to the given space. This -is achieved by first updating the per-room profile data for the given -space-room, and then recursing into all `m.space.child` rooms of which the user -is a member. - -The `space` parameter obeys `?force=false` as well, by only overwriting an -`m.room.member` attribute if it matches the previous profile data of the root -space. If this is not the case, meaning a room with a different per-room profile -has been found, the profile change stops there and does not continue recursing -into the room's space children. Additionally, servers must take care to handle -cycles in the space graph and not recurse infinitely (e.g. by tracking which -rooms it has visited). - -## Potential issues - -This proposal assumes that having "one true display name per room" is a -desirable feature, since it minimizes complexity for clients and is compatible -with how most implementations already determine profile data. However, since -rooms can belong to multiple spaces, possibly with conflicting profile data, -this causes a certain degree of arbitrariness in what profile data gets set for -such rooms (depending purely on the order in which the user sets their per-space -profiles, and whether `force` is set). If this matters, users can always drill -down to room-level profile settings, though, and clients may assist them e.g. by -displaying a list of applicable per-space profiles to switch between. - -Arguably, per-space profile data should be a more first-class feature, with -server support for things like inheriting profile data from parent spaces on -join. This proposal leaves it up to clients to implement such "inheritance" -behavior as they see fit, by altering individual `m.room.member` states when -rooms are joined, added to spaces, etc. If desired, servers could be changed to -automate some of this behavior in the future, though arguably this should be -left to clients, since they have more context for e.g. which parent space the -user was viewing a room from when they joined it. - -Finally, there is a pre-existing issue with the profile APIs: If the server has -to propagate a profile change to a large number of rooms, it could take a long -time, and the client could easily time out, potentially leaving some rooms -without the updated profile data. This is nothing new, but this proposal would -make it an even more broken state, since future attempts at changing profile -data without `?force=true` would interpret the rooms that weren't updated as all -having custom per-room profiles. It is expected that a future MSC will address -this by making the profile APIs non-blocking. - -## Alternatives - -An alternative would be to store per-room/per-space profile data as a part of -[extensible profiles](https://github.com/matrix-org/matrix-doc/pull/1769), -essentially keeping a public mapping of room IDs → profile data in a single -location. While altering `m.room.member` state gives us per-room and per-space -profile data for free, this alternative would require more action from clients -to implement. It would also leak data about users' profiles in private rooms, -which is a significant privacy concern, and it is unclear how conflicting -profiles would affect the "one source of truth" given by `m.room.member` state. -Furthermore, extensible profiles seem unlikely to land anytime soon, while -per-room/per-space profile data is arguably a more urgent feature, and should -not depend on it. - -## Security considerations - -None that I am aware of. - -## Unstable prefix - -During development of this feature the versions of the profile APIs augmented -with `force` and `space` will be available at unstable endpoints: - -```text -PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/avatar_url -PUT /_matrix/client/unstable/org.matrix.msc3189/profile/{userId}/displayname -``` - -## Footnotes - -[1]: Room type is ignored primarily to be consistent with how the -space summary API handles room IDs, and also since rooms can technically have -`m.space.child` rooms without being of type `m.space` themselves. [↩](#a1) diff --git a/proposals/3189-per-room-per-space-profiles.md b/proposals/3189-per-room-per-space-profiles.md new file mode 100644 index 0000000000..44b4fe2e72 --- /dev/null +++ b/proposals/3189-per-room-per-space-profiles.md @@ -0,0 +1,159 @@ +# MSC3189: Per-room/per-space profiles + +People frequently have different identities in different communities. In the +context of Matrix, users may therefore want their display name or avatar to +appear differently in certain social contexts, such as within a room or a space. + +While most clients technically already support per-room display names (by +getting profile data from a user's membership events in a room), this feature +is undocumented and lacks server-side support. Thus, this proposal introduces a +more robust concept of per-room/per-space profiles along with a dedicated API to +manage them. + +## Proposal + +First, the existing behavior: When showing a user's display name or avatar in a +room, clients should reference the `displayname` and `avatar_url` attributes of +the user's `m.room.member` state. In the past, some clients have taken advantage +of this behavior by modifying `m.room.member` state directly in order to set +per-room profiles, however this is deprecated in favor of the more robust system +of profile management proposed in this MSC. As such, clients must be aware that +direct changes to `m.room.member` state may be overwritten without warning. + +### Profile scope and inheritance + +While `m.room.member` state in individual rooms is sufficient for communicating +how users should appear, we would like per-room/per-space profiles to be a more +first-class concept, so that managing them is simple for clients. For this +purpose, an optional query parameter `scope` which takes a room ID is added to +all `/_matrix/client/r0/profile` endpoints. When specified, `scope` changes the +profile endpoints to interact not with the user's global profile, but with the +profile specific to the given space/room. + +With regards to profile data, a space/room can be in one of two states: either +it has a custom profile set via the above `scope` API, in which case it is known +as a *profile root*, or it *inherits* its profile data from another profile root +(i.e. one of its ancestor spaces or the user's global profile). By default, all +rooms and spaces are set to inherit from the global profile. Thus, when profile +updates happen, the server uses this inheritance data to determine which rooms +the update affects, and updates their `m.room.member` state accordingly. + +Inheritance is represented in the profile APIs by the optional property +`inherits_from` in request and response bodies. In profile `GET` requests, it +accompanies the relevant profile data for the space/room to indicate where this +data is coming from (either `global` or an ancestor space ID): + +``` +GET /_matrix/client/r0/profile/@me:example.org?scope=!space1:example.org + +{ + "inherits_from": "global", + "avatar_url": "mxc://example.org/myglobalavatar", + "displayname": "My global display name" +} +``` + +In `PUT` requests, `inherits_from` can be specified instead of +`avatar_url`/`displayname` to set which of a room's ancestor spaces (or the +global profile) to inherit from. Note that even though there are separate `PUT` +endpoints for `avatar_url` and `displayname`, setting `inherits_from` on either +of them will affect both, since inheritance applies to the entire profile. + +``` +PUT /_matrix/client/r0/profile/@me:example.org/avatar_url?scope=!space1:example.org + +{ + "inherits_from": "!space2:example.org" +} +``` + +On the other hand, if a `PUT` request is made without `inherits_from`, it turns +the space/room into a profile root, by copying whatever the previous profile was +and then updating the relevant `avatar_url`/`displayname` property. + +Finally, if a profile `PUT` request does not specify `scope`, it behaves as +global profile updates currently do, except it only affects the `m.room.member` +state of rooms and spaces that inherit from `global`. This ensures that global +profile updates will not overwrite per-room/per-space profiles. + +### Propagating inheritance + +In order to give per-space profiles the desired semantics, whenever a space's +profile settings are updated, its children must be updated as well to inherit +from the right place. The following table outlines the transformations that may +occur for a space A, and how they affect its children: + +||from `inherits_from` B|from profile root| +|-|-|-| +|to `inherits_from` C|All children of A that inherited from B recursively changed to inherit from C|All children of A that inherited from A recursively changed to inherit from C| +|to profile root|All children of A that inherited from B recursively changed to inherit from A|No change in inheritance| + +### Inheritance restrictions + +In addition, there are some restrictions on what a space/room may inherit from. +If a space/room A inherits from a space B, then B must be an ancestor of A +(determined by following `m.space.child` links), and there must be a direct path +in the space-child graph from B to A that only passes through children which the +user has joined and which are *not* profile roots. This is to prevent +undesirable situations such as a space A having a subspace B which has a child +room C, where B is a profile root and yet C somehow inherits from A. + +### Automatic inheritance changes + +Per-space profiles are only really useful if they automatically propagate to +newly joined/added rooms and subspaces. Also, servers generally need to keep +track of when rooms and subspaces are left/removed in order to ensure that the +above inheritance conditions are upheld. For these reasons, servers implementing +per-room/per-space profiles must apply the following rules: + +- When the user joins a space/room, perform a breadth-first search for an ancestor space that is a profile root, by following `m.space.child` links backwards through spaces the user has joined. Break any ties by selecting the first in a lexicographic ordering of room IDs, and then set the joined space/room to inherit from this profile root, or the global profile if none is found. This profile data should go immediately into the initial `join` state, rather than being updated after the join is complete. +- When the user leaves a space A, if A was a profile root, reset every space/room that inherited from A to inherit from `global`. Otherwise if A inherited from a space B, reset every child of A that inherited from B to inherit from `global`. +- When the user adds a space/room A to a space B, if A inherited from `global` and B is a profile root, set A to inherit from B. Otherwise if A inherited from `global` and B inherits from a space C, set A to inherit from C. +- When a space/room A is removed from a space B (whether by the user or someone else), if A inherited from a space C, check whether A is still allowed to inherit from C by [the above rules](#inheritance-restrictions). If this is no longer the case, then reset A to inherit from `global`. + +### Errors + +Given that this proposal expands the surface of the profile APIs, there are some +new ways in which they can fail: + +- The `scope` profile APIs are only for interacting with one's own profiles. Thus if a user attempts to set/get another user's profile for a given `scope`, the server must return a 403 with `M_FORBIDDEN`. +- Similarly, if a user attempts to set/get their profile for a `scope` which they have not joined / might not exist, the server must return a 403 with `M_FORBIDDEN`. +- If the user attempts to set `inherits_from` to an [invalid value](#inheritance-restrictions), the server must return a 400 with `M_UNKNOWN` along with a more specific explanation. + +## Potential issues + +There is a pre-existing issue with the profile APIs, namely that updating one's +profile is an O(n) operation with the number of rooms it affects, often taking +multiple minutes to complete on larger accounts. Arguably this should be solved +as part of this proposal by linking through to +[extensible profile rooms](https://github.com/matrix-org/matrix-doc/pull/1769) +in `m.room.member` state, which would allow the most common use-case of profile +updates to be O(1). While this is not undertaken here due to the significant +added complexity, this proposal is structured in a way to hopefully be +compatible with any future changes in this direction. + +## Alternatives + +An alternative would be to store all per-room/per-space profile data in a single +global [extensible profile](https://github.com/matrix-org/matrix-doc/pull/1769), +essentially keeping a public mapping of room IDs → profile data. However, this +alternative would leak data about users' profiles in private rooms, which is a +significant privacy concern, and it is unclear how conflicting profiles would +affect the "one source of truth" given by `m.room.member` state. + +## Security considerations + +None that I am aware of. + +## Unstable prefix + +During development of this feature the versions of the profile APIs augmented +with `scope` will be available at unstable endpoints: + +```text +GET /_matrix/client/unstable/town.robin.msc3189/profile/{userId} +GET /_matrix/client/unstable/town.robin.msc3189/profile/{userId}/avatar_url +PUT /_matrix/client/unstable/town.robin.msc3189/profile/{userId}/avatar_url +GET /_matrix/client/unstable/town.robin.msc3189/profile/{userId}/displayname +PUT /_matrix/client/unstable/town.robin.msc3189/profile/{userId}/displayname +```