Browse Source

Merge pull request #293 from matrix-org/babolivier/delete_config

Add a config option to disable deleting tokens on bind
Brendan Abolivier 3 years ago
parent
commit
97f8d31842
4 changed files with 141 additions and 1 deletions
  1. 1 0
      changelog.d/293.feature
  2. 7 1
      sydent/sydent.py
  3. 4 0
      sydent/threepid/bind.py
  4. 129 0
      tests/test_invites.py

+ 1 - 0
changelog.d/293.feature

@@ -0,0 +1 @@
+Add a config option to disable deleting invite tokens on bind.

+ 7 - 1
sydent/sydent.py

@@ -18,6 +18,7 @@
 from __future__ import absolute_import
 
 from six.moves import configparser
+import copy
 import logging
 import logging.handlers
 import os
@@ -92,6 +93,7 @@ CONFIG_DEFAULTS = {
 
         # Whether clients and homeservers can register an association using v1 endpoints.
         'enable_v1_associations': 'true',
+        'delete_tokens_on_bind': 'true',
     },
     'db': {
         'db.file': 'sydent.db',
@@ -176,6 +178,10 @@ class Sydent:
             self.cfg.get("general", "enable_v1_associations")
         )
 
+        self.delete_tokens_on_bind = parse_cfg_bool(
+            self.cfg.get("general", "delete_tokens_on_bind")
+        )
+
         # See if a pepper already exists in the database
         # Note: This MUST be run before we start serving requests, otherwise lookups for
         # 3PID hashes may come in before we've completed generating them
@@ -291,7 +297,7 @@ def parse_config_dict(config_dict):
         config_dict (dict): the configuration dictionary to be parsed
     """
     # Build a config dictionary from the defaults merged with the given dictionary
-    config = CONFIG_DEFAULTS
+    config = copy.deepcopy(CONFIG_DEFAULTS)
     for section, section_dict in config_dict.items():
         if section not in config:
             config[section] = {}

+ 4 - 0
sydent/threepid/bind.py

@@ -160,6 +160,10 @@ class ThreepidBinder:
         else:
             logger.info("Successfully notified on bind for %s" % (mxid,))
 
+            # Skip the deletion step if instructed so by the config.
+            if not self.sydent.delete_tokens_on_bind:
+                return
+
             # Only remove sent tokens when they've been successfully sent.
             try:
                 joinTokenStore = JoinTokenStore(self.sydent)

+ 129 - 0
tests/test_invites.py

@@ -0,0 +1,129 @@
+from mock import Mock
+from sydent.http.httpclient import FederationHttpClient
+from sydent.db.invite_tokens import JoinTokenStore
+from tests.utils import make_sydent
+from twisted.web.client import Response
+from twisted.trial import unittest
+
+
+class ThreepidInvitesTestCase(unittest.TestCase):
+    """Tests features related to storing and delivering 3PID invites."""
+
+    def setUp(self):
+        # Create a new sydent
+        self.sydent = make_sydent()
+
+    def test_delete_on_bind(self):
+        """Tests that 3PID invite tokens are deleted upon delivery after a successful
+        bind.
+        """
+        self.sydent.run()
+
+        # The 3PID we're working with.
+        medium = "email"
+        address = "john@example.com"
+
+        # Mock post_json_get_nothing so the /onBind call doesn't fail.
+        def post_json_get_nothing(uri, post_json, opts):
+            return Response((b'HTTP', 1, 1), 200, b'OK', None, None)
+
+        FederationHttpClient.post_json_get_nothing = Mock(
+            side_effect=post_json_get_nothing,
+        )
+
+        # Manually insert an invite token, we'll check later that it's been deleted.
+        join_token_store = JoinTokenStore(self.sydent)
+        join_token_store.storeToken(
+            medium, address, "!someroom:example.com", "@jane:example.com",
+            "sometoken",
+        )
+
+        # Make sure the token still exists and can be retrieved.
+        tokens = join_token_store.getTokens(medium, address)
+        self.assertEqual(len(tokens), 1, tokens)
+
+        # Bind the 3PID
+        self.sydent.threepidBinder.addBinding(
+            medium, address, "@john:example.com",
+        )
+
+        # Give Sydent some time to call /onBind and delete the token.
+        self.sydent.reactor.advance(1000)
+
+        cur = self.sydent.db.cursor()
+
+        # Manually retrieve the tokens for this 3PID. We don't use getTokens because it
+        # filters out sent tokens, so would return nothing even if the token hasn't been
+        # deleted.
+        res = cur.execute(
+            "SELECT medium, address, room_id, sender, token FROM invite_tokens"
+            " WHERE medium = ? AND address = ?",
+            (medium, address,)
+        )
+        rows = res.fetchall()
+
+        # Check that we didn't get any result.
+        self.assertEqual(len(rows), 0, rows)
+
+
+class ThreepidInvitesNoDeleteTestCase(unittest.TestCase):
+    """Test that invite tokens are not deleted when that is disabled.
+    """
+
+    def setUp(self):
+        # Create a new sydent
+        config = {
+            "general": {
+                "delete_tokens_on_bind": "false"
+            }
+        }
+        self.sydent = make_sydent(test_config=config)
+
+    def test_no_delete_on_bind(self):
+        self.sydent.run()
+
+        # The 3PID we're working with.
+        medium = "email"
+        address = "john@example.com"
+
+        # Mock post_json_get_nothing so the /onBind call doesn't fail.
+        def post_json_get_nothing(uri, post_json, opts):
+            return Response((b'HTTP', 1, 1), 200, b'OK', None, None)
+
+        FederationHttpClient.post_json_get_nothing = Mock(
+            side_effect=post_json_get_nothing,
+        )
+
+        # Manually insert an invite token, we'll check later that it's been deleted.
+        join_token_store = JoinTokenStore(self.sydent)
+        join_token_store.storeToken(
+            medium, address, "!someroom:example.com", "@jane:example.com",
+            "sometoken",
+        )
+
+        # Make sure the token still exists and can be retrieved.
+        tokens = join_token_store.getTokens(medium, address)
+        self.assertEqual(len(tokens), 1, tokens)
+
+        # Bind the 3PID
+        self.sydent.threepidBinder.addBinding(
+            medium, address, "@john:example.com",
+        )
+
+        # Give Sydent some time to call /onBind and delete the token.
+        self.sydent.reactor.advance(1000)
+
+        cur = self.sydent.db.cursor()
+
+        # Manually retrieve the tokens for this 3PID. We don't use getTokens because it
+        # filters out sent tokens, so would return nothing even if the token hasn't been
+        # deleted.
+        res = cur.execute(
+            "SELECT medium, address, room_id, sender, token FROM invite_tokens"
+            " WHERE medium = ? AND address = ?",
+            (medium, address,)
+        )
+        rows = res.fetchall()
+
+        # Check that we didn't get any result.
+        self.assertEqual(len(rows), 1, rows)