Repository: matrix-org/synapse

Issue:
Disabling 3pid changes still sends validation email
I want to disable removing/adding email addresses, but only allow this during registration.

Reproduce the bug with:
- Set ``enable_3pid_changes: false`` on configuration file.
- On client side (e.g. Element Web) - try to add another email address to your account.
- Synapse **will** send you a verification mail.
- Go back to client side to proceed with adding the verified email.
- You'll only then receive:
![image](https://user-images.githubusercontent.com/63514368/159749874-31e6603f-0367-4264-872c-a79d9c81385a.png)

Synapse should deny the request at start, and not send verification email.



Discussion:
The process of adding an email during registration is:

1. Request an email via [`POST /_matrix/client/v3/register/email/requestToken`](https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3registeremailrequesttoken).
1. Verify that email by navigating via your browser to a URL provided by the homeserver.
1. Send a request to [`POST /_matrix/client/v3/register`](https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register) to complete the registration.

For adding an email to your account after it's already been created, the process is:

1. Request an email via [`POST  /_matrix/client/v3/account/3pid/email/requestToken`](https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3account3pidemailrequesttoken) (notice the difference in path)
1. Verify that email by navigating via your browser to a URL provided by the homeserver.
1. Send a request to [`POST /_matrix/client/v3/account/3pid/add`](https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3account3pidadd).

Currently the check behind `enable_3pid_changes` is employed in step 3 of adding an email to your account. I don't see a problem with additionally adding the check to step 1 as well. This would prevent an email from being sent out, and would not affect registration (or password resets).
@anoadragon453 I guess the check already exists in step 1.

This is the POST handler for the endpoint ```account/3pid/email/requestToken```
```
    async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
        if self.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF:
            if self.config.email.local_threepid_handling_disabled_due_to_email_config:
                logger.warning(
                    "Adding emails have been disabled due to lack of an email config"
                )
            raise SynapseError(
                400, "Adding an email to your account is disabled on this server"
            )
```
I would like to bump this issue as it is causing some usability questions in my institution as well. The two relevant points in synapse code are:

https://github.com/matrix-org/synapse/blob/1a1abdda42551dad3aadc04a169c25f4cc651a2c/synapse/rest/client/account.py#L340 ("step 1", as far as I can see the check is not performed in this function)

https://github.com/matrix-org/synapse/blob/1a1abdda42551dad3aadc04a169c25f4cc651a2c/synapse/rest/client/account.py#L645 ("step 3", here the check is performed)

If it somehow helps with the process I can of course post some merge request, but I assume since it's such a minor change it's easier for you (synapse devs) to just change it yourself (?)

Anyways, thanks for your great work!

Resolving patch:
diff --git a/synapse/rest/client/account.py b/synapse/rest/client/account.py
--- a/synapse/rest/client/account.py
+++ b/synapse/rest/client/account.py
@@ -338,6 +338,11 @@ def __init__(self, hs: "HomeServer"):
             )
 
     async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
+        if not self.hs.config.registration.enable_3pid_changes:
+            raise SynapseError(
+                400, "3PID changes are disabled on this server", Codes.FORBIDDEN
+            )
+
         if not self.config.email.can_verify_email:
             logger.warning(
                 "Adding emails have been disabled due to lack of an email config"


Test patch:
diff --git a/tests/rest/client/test_account.py b/tests/rest/client/test_account.py
--- a/tests/rest/client/test_account.py
+++ b/tests/rest/client/test_account.py
@@ -690,41 +690,21 @@ def test_add_email_if_disabled(self) -> None:
         self.hs.config.registration.enable_3pid_changes = False
 
         client_secret = "foobar"
-        session_id = self._request_token(self.email, client_secret)
-
-        self.assertEqual(len(self.email_attempts), 1)
-        link = self._get_link_from_email()
-
-        self._validate_token(link)
-
         channel = self.make_request(
             "POST",
-            b"/_matrix/client/unstable/account/3pid/add",
+            b"/_matrix/client/unstable/account/3pid/email/requestToken",
             {
                 "client_secret": client_secret,
-                "sid": session_id,
-                "auth": {
-                    "type": "m.login.password",
-                    "user": self.user_id,
-                    "password": "test",
-                },
+                "email": "test@example.com",
+                "send_attempt": 1,
             },
-            access_token=self.user_id_tok,
         )
+
         self.assertEqual(
             HTTPStatus.BAD_REQUEST, channel.code, msg=channel.result["body"]
         )
-        self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
 
-        # Get user
-        channel = self.make_request(
-            "GET",
-            self.url_3pid,
-            access_token=self.user_id_tok,
-        )
-
-        self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.result["body"])
-        self.assertFalse(channel.json_body["threepids"])
+        self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
 
     def test_delete_email(self) -> None:
         """Test deleting an email from profile"""