Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit b406573

Browse files
Add module API callbacks for adding and deleting local 3PID associations (#15044
1 parent 4fc8875 commit b406573

File tree

12 files changed

+324
-48
lines changed

12 files changed

+324
-48
lines changed

changelog.d/15044.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add two new Third Party Rules module API callbacks: [`on_add_user_third_party_identifier`](https://matrix-org.github.io/synapse/v1.79/modules/third_party_rules_callbacks.html#on_add_user_third_party_identifier) and [`on_remove_user_third_party_identifier`](https://matrix-org.github.io/synapse/v1.79/modules/third_party_rules_callbacks.html#on_remove_user_third_party_identifier).

docs/modules/third_party_rules_callbacks.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ If multiple modules implement this callback, Synapse runs them all in order.
254254

255255
_First introduced in Synapse v1.56.0_
256256

257+
**<span style="color:red">
258+
This callback is deprecated in favour of the `on_add_user_third_party_identifier` callback, which
259+
features the same functionality. The only difference is in name.
260+
</span>**
261+
257262
```python
258263
async def on_threepid_bind(user_id: str, medium: str, address: str) -> None:
259264
```
@@ -268,6 +273,44 @@ server_.
268273

269274
If multiple modules implement this callback, Synapse runs them all in order.
270275

276+
### `on_add_user_third_party_identifier`
277+
278+
_First introduced in Synapse v1.79.0_
279+
280+
```python
281+
async def on_add_user_third_party_identifier(user_id: str, medium: str, address: str) -> None:
282+
```
283+
284+
Called after successfully creating an association between a user and a third-party identifier
285+
(email address, phone number). The module is given the Matrix ID of the user the
286+
association is for, as well as the medium (`email` or `msisdn`) and address of the
287+
third-party identifier (i.e. an email address).
288+
289+
Note that this callback is _not_ called if a user attempts to bind their third-party identifier
290+
to an identity server (via a call to [`POST
291+
/_matrix/client/v3/account/3pid/bind`](https://spec.matrix.org/v1.5/client-server-api/#post_matrixclientv3account3pidbind)).
292+
293+
If multiple modules implement this callback, Synapse runs them all in order.
294+
295+
### `on_remove_user_third_party_identifier`
296+
297+
_First introduced in Synapse v1.79.0_
298+
299+
```python
300+
async def on_remove_user_third_party_identifier(user_id: str, medium: str, address: str) -> None:
301+
```
302+
303+
Called after successfully removing an association between a user and a third-party identifier
304+
(email address, phone number). The module is given the Matrix ID of the user the
305+
association is for, as well as the medium (`email` or `msisdn`) and address of the
306+
third-party identifier (i.e. an email address).
307+
308+
Note that this callback is _not_ called if a user attempts to unbind their third-party
309+
identifier from an identity server (via a call to [`POST
310+
/_matrix/client/v3/account/3pid/unbind`](https://spec.matrix.org/v1.5/client-server-api/#post_matrixclientv3account3pidunbind)).
311+
312+
If multiple modules implement this callback, Synapse runs them all in order.
313+
271314
## Example
272315
273316
The example below is a module that implements the third-party rules callback
@@ -300,4 +343,4 @@ class EventCensorer:
300343
)
301344
event_dict["content"] = new_event_content
302345
return event_dict
303-
```
346+
```

docs/upgrade.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ process, for example:
8888
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
8989
```
9090
91+
# Upgrading to v1.79.0
92+
93+
## The `on_threepid_bind` module callback method has been deprecated
94+
95+
Synapse v1.79.0 deprecates the
96+
[`on_threepid_bind`](modules/third_party_rules_callbacks.md#on_threepid_bind)
97+
"third-party rules" Synapse module callback method in favour of a new module method,
98+
[`on_add_user_third_party_identifier`](modules/third_party_rules_callbacks.md#on_add_user_third_party_identifier).
99+
`on_threepid_bind` will be removed in a future version of Synapse. You should check whether any Synapse
100+
modules in use in your deployment are making use of `on_threepid_bind`, and update them where possible.
101+
102+
The arguments and functionality of the new method are the same.
103+
104+
The justification behind the name change is that the old method's name, `on_threepid_bind`, was
105+
misleading. A user is considered to "bind" their third-party ID to their Matrix ID only if they
106+
do so via an [identity server](https://spec.matrix.org/latest/identity-service-api/)
107+
(so that users on other homeservers may find them). But this method was not called in that case -
108+
it was only called when a user added a third-party identifier on the local homeserver.
109+
110+
Module developers may also be interested in the related
111+
[`on_remove_user_third_party_identifier`](modules/third_party_rules_callbacks.md#on_remove_user_third_party_identifier)
112+
module callback method that was also added in Synapse v1.79.0. This new method is called when a
113+
user removes a third-party identifier from their account.
114+
91115
# Upgrading to v1.78.0
92116

93117
## Deprecate the `/_synapse/admin/v1/media/<server_name>/delete` admin API

synapse/events/third_party_rules.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
ON_PROFILE_UPDATE_CALLBACK = Callable[[str, ProfileInfo, bool, bool], Awaitable]
4646
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK = Callable[[str, bool, bool], Awaitable]
4747
ON_THREEPID_BIND_CALLBACK = Callable[[str, str, str], Awaitable]
48+
ON_ADD_USER_THIRD_PARTY_IDENTIFIER_CALLBACK = Callable[[str, str, str], Awaitable]
49+
ON_REMOVE_USER_THIRD_PARTY_IDENTIFIER_CALLBACK = Callable[[str, str, str], Awaitable]
4850

4951

5052
def load_legacy_third_party_event_rules(hs: "HomeServer") -> None:
@@ -172,6 +174,12 @@ def __init__(self, hs: "HomeServer"):
172174
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
173175
] = []
174176
self._on_threepid_bind_callbacks: List[ON_THREEPID_BIND_CALLBACK] = []
177+
self._on_add_user_third_party_identifier_callbacks: List[
178+
ON_ADD_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
179+
] = []
180+
self._on_remove_user_third_party_identifier_callbacks: List[
181+
ON_REMOVE_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
182+
] = []
175183

176184
def register_third_party_rules_callbacks(
177185
self,
@@ -191,6 +199,12 @@ def register_third_party_rules_callbacks(
191199
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
192200
] = None,
193201
on_threepid_bind: Optional[ON_THREEPID_BIND_CALLBACK] = None,
202+
on_add_user_third_party_identifier: Optional[
203+
ON_ADD_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
204+
] = None,
205+
on_remove_user_third_party_identifier: Optional[
206+
ON_REMOVE_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
207+
] = None,
194208
) -> None:
195209
"""Register callbacks from modules for each hook."""
196210
if check_event_allowed is not None:
@@ -228,6 +242,11 @@ def register_third_party_rules_callbacks(
228242
if on_threepid_bind is not None:
229243
self._on_threepid_bind_callbacks.append(on_threepid_bind)
230244

245+
if on_add_user_third_party_identifier is not None:
246+
self._on_add_user_third_party_identifier_callbacks.append(
247+
on_add_user_third_party_identifier
248+
)
249+
231250
async def check_event_allowed(
232251
self,
233252
event: EventBase,
@@ -511,6 +530,9 @@ async def on_threepid_bind(self, user_id: str, medium: str, address: str) -> Non
511530
local homeserver, not when it's created on an identity server (and then kept track
512531
of so that it can be unbound on the same IS later on).
513532
533+
THIS MODULE CALLBACK METHOD HAS BEEN DEPRECATED. Please use the
534+
`on_add_user_third_party_identifier` callback method instead.
535+
514536
Args:
515537
user_id: the user being associated with the threepid.
516538
medium: the threepid's medium.
@@ -523,3 +545,44 @@ async def on_threepid_bind(self, user_id: str, medium: str, address: str) -> Non
523545
logger.exception(
524546
"Failed to run module API callback %s: %s", callback, e
525547
)
548+
549+
async def on_add_user_third_party_identifier(
550+
self, user_id: str, medium: str, address: str
551+
) -> None:
552+
"""Called when an association between a user's Matrix ID and a third-party ID
553+
(email, phone number) has successfully been registered on the homeserver.
554+
555+
Args:
556+
user_id: The User ID included in the association.
557+
medium: The medium of the third-party ID (email, msisdn).
558+
address: The address of the third-party ID (i.e. an email address).
559+
"""
560+
for callback in self._on_add_user_third_party_identifier_callbacks:
561+
try:
562+
await callback(user_id, medium, address)
563+
except Exception as e:
564+
logger.exception(
565+
"Failed to run module API callback %s: %s", callback, e
566+
)
567+
568+
async def on_remove_user_third_party_identifier(
569+
self, user_id: str, medium: str, address: str
570+
) -> None:
571+
"""Called when an association between a user's Matrix ID and a third-party ID
572+
(email, phone number) has been successfully removed on the homeserver.
573+
574+
This is called *after* any known bindings on identity servers for this
575+
association have been removed.
576+
577+
Args:
578+
user_id: The User ID included in the removed association.
579+
medium: The medium of the third-party ID (email, msisdn).
580+
address: The address of the third-party ID (i.e. an email address).
581+
"""
582+
for callback in self._on_remove_user_third_party_identifier_callbacks:
583+
try:
584+
await callback(user_id, medium, address)
585+
except Exception as e:
586+
logger.exception(
587+
"Failed to run module API callback %s: %s", callback, e
588+
)

synapse/handlers/auth.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,17 @@ async def delete_access_tokens_for_user(
15421542
async def add_threepid(
15431543
self, user_id: str, medium: str, address: str, validated_at: int
15441544
) -> None:
1545+
"""
1546+
Adds an association between a user's Matrix ID and a third-party ID (email,
1547+
phone number).
1548+
1549+
Args:
1550+
user_id: The ID of the user to associate.
1551+
medium: The medium of the third-party ID (email, msisdn).
1552+
address: The address of the third-party ID (i.e. an email address).
1553+
validated_at: The timestamp in ms of when the validation that the user owns
1554+
this third-party ID occurred.
1555+
"""
15451556
# check if medium has a valid value
15461557
if medium not in ["email", "msisdn"]:
15471558
raise SynapseError(
@@ -1566,42 +1577,44 @@ async def add_threepid(
15661577
user_id, medium, address, validated_at, self.hs.get_clock().time_msec()
15671578
)
15681579

1580+
# Inform Synapse modules that a 3PID association has been created.
1581+
await self._third_party_rules.on_add_user_third_party_identifier(
1582+
user_id, medium, address
1583+
)
1584+
1585+
# Deprecated method for informing Synapse modules that a 3PID association
1586+
# has successfully been created.
15691587
await self._third_party_rules.on_threepid_bind(user_id, medium, address)
15701588

1571-
async def delete_threepid(
1572-
self, user_id: str, medium: str, address: str, id_server: Optional[str] = None
1573-
) -> bool:
1574-
"""Attempts to unbind the 3pid on the identity servers and deletes it
1575-
from the local database.
1589+
async def delete_local_threepid(
1590+
self, user_id: str, medium: str, address: str
1591+
) -> None:
1592+
"""Deletes an association between a third-party ID and a user ID from the local
1593+
database. This method does not unbind the association from any identity servers.
1594+
1595+
If `medium` is 'email' and a pusher is associated with this third-party ID, the
1596+
pusher will also be deleted.
15761597
15771598
Args:
15781599
user_id: ID of user to remove the 3pid from.
15791600
medium: The medium of the 3pid being removed: "email" or "msisdn".
15801601
address: The 3pid address to remove.
1581-
id_server: Use the given identity server when unbinding
1582-
any threepids. If None then will attempt to unbind using the
1583-
identity server specified when binding (if known).
1584-
1585-
Returns:
1586-
Returns True if successfully unbound the 3pid on
1587-
the identity server, False if identity server doesn't support the
1588-
unbind API.
15891602
"""
1590-
15911603
# 'Canonicalise' email addresses as per above
15921604
if medium == "email":
15931605
address = canonicalise_email(address)
15941606

1595-
result = await self.hs.get_identity_handler().try_unbind_threepid(
1596-
user_id, medium, address, id_server
1607+
await self.store.user_delete_threepid(user_id, medium, address)
1608+
1609+
# Inform Synapse modules that a 3PID association has been deleted.
1610+
await self._third_party_rules.on_remove_user_third_party_identifier(
1611+
user_id, medium, address
15971612
)
15981613

1599-
await self.store.user_delete_threepid(user_id, medium, address)
16001614
if medium == "email":
16011615
await self.store.delete_pusher_by_app_id_pushkey_user_id(
16021616
app_id="m.email", pushkey=address, user_id=user_id
16031617
)
1604-
return result
16051618

16061619
async def hash(self, password: str) -> str:
16071620
"""Computes a secure hash of password.

synapse/handlers/deactivate_account.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,26 +100,28 @@ async def deactivate_account(
100100
# unbinding
101101
identity_server_supports_unbinding = True
102102

103-
# Retrieve the 3PIDs this user has bound to an identity server
104-
threepids = await self.store.user_get_bound_threepids(user_id)
105-
106-
for threepid in threepids:
103+
# Attempt to unbind any known bound threepids to this account from identity
104+
# server(s).
105+
bound_threepids = await self.store.user_get_bound_threepids(user_id)
106+
for threepid in bound_threepids:
107107
try:
108108
result = await self._identity_handler.try_unbind_threepid(
109109
user_id, threepid["medium"], threepid["address"], id_server
110110
)
111-
identity_server_supports_unbinding &= result
112111
except Exception:
113112
# Do we want this to be a fatal error or should we carry on?
114113
logger.exception("Failed to remove threepid from ID server")
115114
raise SynapseError(400, "Failed to remove threepid from ID server")
116-
await self.store.user_delete_threepid(
115+
116+
identity_server_supports_unbinding &= result
117+
118+
# Remove any local threepid associations for this account.
119+
local_threepids = await self.store.user_get_threepids(user_id)
120+
for threepid in local_threepids:
121+
await self._auth_handler.delete_local_threepid(
117122
user_id, threepid["medium"], threepid["address"]
118123
)
119124

120-
# Remove all 3PIDs this user has bound to the homeserver
121-
await self.store.user_delete_threepids(user_id)
122-
123125
# delete any devices belonging to the user, which will also
124126
# delete corresponding access tokens.
125127
await self._device_handler.delete_all_devices_for_user(user_id)

synapse/module_api/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@
6464
CHECK_EVENT_ALLOWED_CALLBACK,
6565
CHECK_THREEPID_CAN_BE_INVITED_CALLBACK,
6666
CHECK_VISIBILITY_CAN_BE_MODIFIED_CALLBACK,
67+
ON_ADD_USER_THIRD_PARTY_IDENTIFIER_CALLBACK,
6768
ON_CREATE_ROOM_CALLBACK,
6869
ON_NEW_EVENT_CALLBACK,
6970
ON_PROFILE_UPDATE_CALLBACK,
71+
ON_REMOVE_USER_THIRD_PARTY_IDENTIFIER_CALLBACK,
7072
ON_THREEPID_BIND_CALLBACK,
7173
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK,
7274
)
@@ -357,6 +359,12 @@ def register_third_party_rules_callbacks(
357359
ON_USER_DEACTIVATION_STATUS_CHANGED_CALLBACK
358360
] = None,
359361
on_threepid_bind: Optional[ON_THREEPID_BIND_CALLBACK] = None,
362+
on_add_user_third_party_identifier: Optional[
363+
ON_ADD_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
364+
] = None,
365+
on_remove_user_third_party_identifier: Optional[
366+
ON_REMOVE_USER_THIRD_PARTY_IDENTIFIER_CALLBACK
367+
] = None,
360368
) -> None:
361369
"""Registers callbacks for third party event rules capabilities.
362370
@@ -373,6 +381,8 @@ def register_third_party_rules_callbacks(
373381
on_profile_update=on_profile_update,
374382
on_user_deactivation_status_changed=on_user_deactivation_status_changed,
375383
on_threepid_bind=on_threepid_bind,
384+
on_add_user_third_party_identifier=on_add_user_third_party_identifier,
385+
on_remove_user_third_party_identifier=on_remove_user_third_party_identifier,
376386
)
377387

378388
def register_presence_router_callbacks(

synapse/rest/admin/users.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,20 @@ async def on_PUT(
304304
# remove old threepids
305305
for medium, address in del_threepids:
306306
try:
307-
await self.auth_handler.delete_threepid(
308-
user_id, medium, address, None
307+
# Attempt to remove any known bindings of this third-party ID
308+
# and user ID from identity servers.
309+
await self.hs.get_identity_handler().try_unbind_threepid(
310+
user_id, medium, address, id_server=None
309311
)
310312
except Exception:
311313
logger.exception("Failed to remove threepids")
312314
raise SynapseError(500, "Failed to remove threepids")
313315

316+
# Delete the local association of this user ID and third-party ID.
317+
await self.auth_handler.delete_local_threepid(
318+
user_id, medium, address
319+
)
320+
314321
# add new threepids
315322
current_time = self.hs.get_clock().time_msec()
316323
for medium, address in add_threepids:

synapse/rest/client/account.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,9 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
768768
user_id = requester.user.to_string()
769769

770770
try:
771-
ret = await self.auth_handler.delete_threepid(
771+
# Attempt to remove any known bindings of this third-party ID
772+
# and user ID from identity servers.
773+
ret = await self.hs.get_identity_handler().try_unbind_threepid(
772774
user_id, body.medium, body.address, body.id_server
773775
)
774776
except Exception:
@@ -783,6 +785,11 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
783785
else:
784786
id_server_unbind_result = "no-support"
785787

788+
# Delete the local association of this user ID and third-party ID.
789+
await self.auth_handler.delete_local_threepid(
790+
user_id, body.medium, body.address
791+
)
792+
786793
return 200, {"id_server_unbind_result": id_server_unbind_result}
787794

788795

0 commit comments

Comments
 (0)