mirror of
				https://git.anonymousland.org/anonymousland/synapse.git
				synced 2025-10-31 12:48:58 -04:00 
			
		
		
		
	Merge remote-tracking branch 'origin/develop' into jaywink/admin-forward-extremities
This commit is contained in:
		
						commit
						fe18882bb5
					
				
					 13 changed files with 276 additions and 24 deletions
				
			
		
							
								
								
									
										17
									
								
								CHANGES.md
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGES.md
									
										
									
									
									
								
							|  | @ -1,3 +1,20 @@ | |||
| Synapse 1.26.0rc2 (2021-01-25) | ||||
| ============================== | ||||
| 
 | ||||
| Bugfixes | ||||
| -------- | ||||
| 
 | ||||
| - Fix receipts and account data not being sent down sync. Introduced in v1.26.0rc1. ([\#9193](https://github.com/matrix-org/synapse/issues/9193), [\#9195](https://github.com/matrix-org/synapse/issues/9195)) | ||||
| - Fix chain cover update to handle events with duplicate auth events. Introduced in v1.26.0rc1. ([\#9210](https://github.com/matrix-org/synapse/issues/9210)) | ||||
| 
 | ||||
| 
 | ||||
| Internal Changes | ||||
| ---------------- | ||||
| 
 | ||||
| - Add an `oidc-` prefix to any `idp_id`s which are given in the `oidc_providers` configuration. ([\#9189](https://github.com/matrix-org/synapse/issues/9189)) | ||||
| - Bump minimum `psycopg2` version to v2.8. ([\#9204](https://github.com/matrix-org/synapse/issues/9204)) | ||||
| 
 | ||||
| 
 | ||||
| Synapse 1.26.0rc1 (2021-01-20) | ||||
| ============================== | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										1
									
								
								changelog.d/9165.bugfix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelog.d/9165.bugfix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| Fix a long-standing bug where invalid data could cause errors when calculating the presentable room name for push. | ||||
|  | @ -1 +0,0 @@ | |||
| Add an `oidc-` prefix to any `idp_id`s which are given in the `oidc_providers` configuration. | ||||
|  | @ -1 +0,0 @@ | |||
| Fix receipts or account data not being sent down sync. Introduced in v1.26.0rc1. | ||||
|  | @ -1 +0,0 @@ | |||
| Fix receipts or account data not being sent down sync. Introduced in v1.26.0rc1. | ||||
|  | @ -48,7 +48,7 @@ try: | |||
| except ImportError: | ||||
|     pass | ||||
| 
 | ||||
| __version__ = "1.26.0rc1" | ||||
| __version__ = "1.26.0rc2" | ||||
| 
 | ||||
| if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)): | ||||
|     # We import here so that we don't have to install a bunch of deps when | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import logging | |||
| import re | ||||
| from typing import TYPE_CHECKING, Dict, Iterable, Optional | ||||
| 
 | ||||
| from synapse.api.constants import EventTypes | ||||
| from synapse.api.constants import EventTypes, Membership | ||||
| from synapse.events import EventBase | ||||
| from synapse.types import StateMap | ||||
| 
 | ||||
|  | @ -63,7 +63,7 @@ async def calculate_room_name( | |||
|         m_room_name = await store.get_event( | ||||
|             room_state_ids[(EventTypes.Name, "")], allow_none=True | ||||
|         ) | ||||
|         if m_room_name and m_room_name.content and m_room_name.content["name"]: | ||||
|         if m_room_name and m_room_name.content and m_room_name.content.get("name"): | ||||
|             return m_room_name.content["name"] | ||||
| 
 | ||||
|     # does it have a canonical alias? | ||||
|  | @ -74,15 +74,11 @@ async def calculate_room_name( | |||
|         if ( | ||||
|             canon_alias | ||||
|             and canon_alias.content | ||||
|             and canon_alias.content["alias"] | ||||
|             and canon_alias.content.get("alias") | ||||
|             and _looks_like_an_alias(canon_alias.content["alias"]) | ||||
|         ): | ||||
|             return canon_alias.content["alias"] | ||||
| 
 | ||||
|     # at this point we're going to need to search the state by all state keys | ||||
|     # for an event type, so rearrange the data structure | ||||
|     room_state_bytype_ids = _state_as_two_level_dict(room_state_ids) | ||||
| 
 | ||||
|     if not fallback_to_members: | ||||
|         return None | ||||
| 
 | ||||
|  | @ -94,7 +90,7 @@ async def calculate_room_name( | |||
| 
 | ||||
|     if ( | ||||
|         my_member_event is not None | ||||
|         and my_member_event.content["membership"] == "invite" | ||||
|         and my_member_event.content.get("membership") == Membership.INVITE | ||||
|     ): | ||||
|         if (EventTypes.Member, my_member_event.sender) in room_state_ids: | ||||
|             inviter_member_event = await store.get_event( | ||||
|  | @ -111,6 +107,10 @@ async def calculate_room_name( | |||
|         else: | ||||
|             return "Room Invite" | ||||
| 
 | ||||
|     # at this point we're going to need to search the state by all state keys | ||||
|     # for an event type, so rearrange the data structure | ||||
|     room_state_bytype_ids = _state_as_two_level_dict(room_state_ids) | ||||
| 
 | ||||
|     # we're going to have to generate a name based on who's in the room, | ||||
|     # so find out who is in the room that isn't the user. | ||||
|     if EventTypes.Member in room_state_bytype_ids: | ||||
|  | @ -120,8 +120,8 @@ async def calculate_room_name( | |||
|         all_members = [ | ||||
|             ev | ||||
|             for ev in member_events.values() | ||||
|             if ev.content["membership"] == "join" | ||||
|             or ev.content["membership"] == "invite" | ||||
|             if ev.content.get("membership") == Membership.JOIN | ||||
|             or ev.content.get("membership") == Membership.INVITE | ||||
|         ] | ||||
|         # Sort the member events oldest-first so the we name people in the | ||||
|         # order the joined (it should at least be deterministic rather than | ||||
|  | @ -194,11 +194,7 @@ def descriptor_from_member_events(member_events: Iterable[EventBase]) -> str: | |||
| 
 | ||||
| 
 | ||||
| def name_from_member_event(member_event: EventBase) -> str: | ||||
|     if ( | ||||
|         member_event.content | ||||
|         and "displayname" in member_event.content | ||||
|         and member_event.content["displayname"] | ||||
|     ): | ||||
|     if member_event.content and member_event.content.get("displayname"): | ||||
|         return member_event.content["displayname"] | ||||
|     return member_event.state_key | ||||
| 
 | ||||
|  |  | |||
|  | @ -86,8 +86,8 @@ REQUIREMENTS = [ | |||
| 
 | ||||
| CONDITIONAL_REQUIREMENTS = { | ||||
|     "matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"], | ||||
|     # we use execute_batch, which arrived in psycopg 2.7. | ||||
|     "postgres": ["psycopg2>=2.7"], | ||||
|     # we use execute_values with the fetch param, which arrived in psycopg 2.8. | ||||
|     "postgres": ["psycopg2>=2.8"], | ||||
|     # ACME support is required to provision TLS certificates from authorities | ||||
|     # that use the protocol, such as Let's Encrypt. | ||||
|     "acme": [ | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ def sorted_topologically( | |||
|         if node not in degree_map: | ||||
|             continue | ||||
| 
 | ||||
|         for edge in edges: | ||||
|         for edge in set(edges): | ||||
|             if edge in degree_map: | ||||
|                 degree_map[node] += 1 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										229
									
								
								tests/push/test_presentable_names.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								tests/push/test_presentable_names.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,229 @@ | |||
| #  Copyright 2021 The Matrix.org Foundation C.I.C. | ||||
| # | ||||
| #  Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| #  you may not use this file except in compliance with the License. | ||||
| #  You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| #  Unless required by applicable law or agreed to in writing, software | ||||
| #  distributed under the License is distributed on an "AS IS" BASIS, | ||||
| #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| #  See the License for the specific language governing permissions and | ||||
| #  limitations under the License. | ||||
| 
 | ||||
| from typing import Iterable, Optional, Tuple | ||||
| 
 | ||||
| from synapse.api.constants import EventTypes, Membership | ||||
| from synapse.api.room_versions import RoomVersions | ||||
| from synapse.events import FrozenEvent | ||||
| from synapse.push.presentable_names import calculate_room_name | ||||
| from synapse.types import StateKey, StateMap | ||||
| 
 | ||||
| from tests import unittest | ||||
| 
 | ||||
| 
 | ||||
| class MockDataStore: | ||||
|     """ | ||||
|     A fake data store which stores a mapping of state key to event content. | ||||
|     (I.e. the state key is used as the event ID.) | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, events: Iterable[Tuple[StateKey, dict]]): | ||||
|         """ | ||||
|         Args: | ||||
|             events: A state map to event contents. | ||||
|         """ | ||||
|         self._events = {} | ||||
| 
 | ||||
|         for i, (event_id, content) in enumerate(events): | ||||
|             self._events[event_id] = FrozenEvent( | ||||
|                 { | ||||
|                     "event_id": "$event_id", | ||||
|                     "type": event_id[0], | ||||
|                     "sender": "@user:test", | ||||
|                     "state_key": event_id[1], | ||||
|                     "room_id": "#room:test", | ||||
|                     "content": content, | ||||
|                     "origin_server_ts": i, | ||||
|                 }, | ||||
|                 RoomVersions.V1, | ||||
|             ) | ||||
| 
 | ||||
|     async def get_event( | ||||
|         self, event_id: StateKey, allow_none: bool = False | ||||
|     ) -> Optional[FrozenEvent]: | ||||
|         assert allow_none, "Mock not configured for allow_none = False" | ||||
| 
 | ||||
|         return self._events.get(event_id) | ||||
| 
 | ||||
|     async def get_events(self, event_ids: Iterable[StateKey]): | ||||
|         # This is cheating since it just returns all events. | ||||
|         return self._events | ||||
| 
 | ||||
| 
 | ||||
| class PresentableNamesTestCase(unittest.HomeserverTestCase): | ||||
|     USER_ID = "@test:test" | ||||
|     OTHER_USER_ID = "@user:test" | ||||
| 
 | ||||
|     def _calculate_room_name( | ||||
|         self, | ||||
|         events: StateMap[dict], | ||||
|         user_id: str = "", | ||||
|         fallback_to_members: bool = True, | ||||
|         fallback_to_single_member: bool = True, | ||||
|     ): | ||||
|         # This isn't 100% accurate, but works with MockDataStore. | ||||
|         room_state_ids = {k[0]: k[0] for k in events} | ||||
| 
 | ||||
|         return self.get_success( | ||||
|             calculate_room_name( | ||||
|                 MockDataStore(events), | ||||
|                 room_state_ids, | ||||
|                 user_id or self.USER_ID, | ||||
|                 fallback_to_members, | ||||
|                 fallback_to_single_member, | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|     def test_name(self): | ||||
|         """A room name event should be used.""" | ||||
|         events = [ | ||||
|             ((EventTypes.Name, ""), {"name": "test-name"}), | ||||
|         ] | ||||
|         self.assertEqual("test-name", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # Check if the event content has garbage. | ||||
|         events = [((EventTypes.Name, ""), {"foo": 1})] | ||||
|         self.assertEqual("Empty Room", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         events = [((EventTypes.Name, ""), {"name": 1})] | ||||
|         self.assertEqual(1, self._calculate_room_name(events)) | ||||
| 
 | ||||
|     def test_canonical_alias(self): | ||||
|         """An canonical alias should be used.""" | ||||
|         events = [ | ||||
|             ((EventTypes.CanonicalAlias, ""), {"alias": "#test-name:test"}), | ||||
|         ] | ||||
|         self.assertEqual("#test-name:test", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # Check if the event content has garbage. | ||||
|         events = [((EventTypes.CanonicalAlias, ""), {"foo": 1})] | ||||
|         self.assertEqual("Empty Room", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         events = [((EventTypes.CanonicalAlias, ""), {"alias": "test-name"})] | ||||
|         self.assertEqual("Empty Room", self._calculate_room_name(events)) | ||||
| 
 | ||||
|     def test_invite(self): | ||||
|         """An invite has special behaviour.""" | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}), | ||||
|             ((EventTypes.Member, self.OTHER_USER_ID), {"displayname": "Other User"}), | ||||
|         ] | ||||
|         self.assertEqual("Invite from Other User", self._calculate_room_name(events)) | ||||
|         self.assertIsNone( | ||||
|             self._calculate_room_name(events, fallback_to_single_member=False) | ||||
|         ) | ||||
|         # Ensure this logic is skipped if we don't fallback to members. | ||||
|         self.assertIsNone(self._calculate_room_name(events, fallback_to_members=False)) | ||||
| 
 | ||||
|         # Check if the event content has garbage. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}), | ||||
|             ((EventTypes.Member, self.OTHER_USER_ID), {"foo": 1}), | ||||
|         ] | ||||
|         self.assertEqual("Invite from @user:test", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # No member event for sender. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}), | ||||
|         ] | ||||
|         self.assertEqual("Room Invite", self._calculate_room_name(events)) | ||||
| 
 | ||||
|     def test_no_members(self): | ||||
|         """Behaviour of an empty room.""" | ||||
|         events = [] | ||||
|         self.assertEqual("Empty Room", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # Note that events with invalid (or missing) membership are ignored. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.OTHER_USER_ID), {"foo": 1}), | ||||
|             ((EventTypes.Member, "@foo:test"), {"membership": "foo"}), | ||||
|         ] | ||||
|         self.assertEqual("Empty Room", self._calculate_room_name(events)) | ||||
| 
 | ||||
|     def test_no_other_members(self): | ||||
|         """Behaviour of a room with no other members in it.""" | ||||
|         events = [ | ||||
|             ( | ||||
|                 (EventTypes.Member, self.USER_ID), | ||||
|                 {"membership": Membership.JOIN, "displayname": "Me"}, | ||||
|             ), | ||||
|         ] | ||||
|         self.assertEqual("Me", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # Check if the event content has no displayname. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}), | ||||
|         ] | ||||
|         self.assertEqual("@test:test", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # 3pid invite, use the other user (who is set as the sender). | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.OTHER_USER_ID), {"membership": Membership.JOIN}), | ||||
|         ] | ||||
|         self.assertEqual( | ||||
|             "nobody", self._calculate_room_name(events, user_id=self.OTHER_USER_ID) | ||||
|         ) | ||||
| 
 | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.OTHER_USER_ID), {"membership": Membership.JOIN}), | ||||
|             ((EventTypes.ThirdPartyInvite, self.OTHER_USER_ID), {}), | ||||
|         ] | ||||
|         self.assertEqual( | ||||
|             "Inviting email address", | ||||
|             self._calculate_room_name(events, user_id=self.OTHER_USER_ID), | ||||
|         ) | ||||
| 
 | ||||
|     def test_one_other_member(self): | ||||
|         """Behaviour of a room with a single other member.""" | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}), | ||||
|             ( | ||||
|                 (EventTypes.Member, self.OTHER_USER_ID), | ||||
|                 {"membership": Membership.JOIN, "displayname": "Other User"}, | ||||
|             ), | ||||
|         ] | ||||
|         self.assertEqual("Other User", self._calculate_room_name(events)) | ||||
|         self.assertIsNone( | ||||
|             self._calculate_room_name(events, fallback_to_single_member=False) | ||||
|         ) | ||||
| 
 | ||||
|         # Check if the event content has no displayname and is an invite. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}), | ||||
|             ( | ||||
|                 (EventTypes.Member, self.OTHER_USER_ID), | ||||
|                 {"membership": Membership.INVITE}, | ||||
|             ), | ||||
|         ] | ||||
|         self.assertEqual("@user:test", self._calculate_room_name(events)) | ||||
| 
 | ||||
|     def test_other_members(self): | ||||
|         """Behaviour of a room with multiple other members.""" | ||||
|         # Two other members. | ||||
|         events = [ | ||||
|             ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}), | ||||
|             ( | ||||
|                 (EventTypes.Member, self.OTHER_USER_ID), | ||||
|                 {"membership": Membership.JOIN, "displayname": "Other User"}, | ||||
|             ), | ||||
|             ((EventTypes.Member, "@foo:test"), {"membership": Membership.JOIN}), | ||||
|         ] | ||||
|         self.assertEqual("Other User and @foo:test", self._calculate_room_name(events)) | ||||
| 
 | ||||
|         # Three or more other members. | ||||
|         events.append( | ||||
|             ((EventTypes.Member, "@fourth:test"), {"membership": Membership.INVITE}) | ||||
|         ) | ||||
|         self.assertEqual("Other User and 2 others", self._calculate_room_name(events)) | ||||
|  | @ -29,7 +29,7 @@ class PushRuleEvaluatorTestCase(unittest.TestCase): | |||
|                 "type": "m.room.history_visibility", | ||||
|                 "sender": "@user:test", | ||||
|                 "state_key": "", | ||||
|                 "room_id": "@room:test", | ||||
|                 "room_id": "#room:test", | ||||
|                 "content": content, | ||||
|             }, | ||||
|             RoomVersions.V1, | ||||
|  |  | |||
|  | @ -92,3 +92,15 @@ class SortTopologically(TestCase): | |||
|         # Valid orderings are `[1, 3, 2, 4]` or `[1, 2, 3, 4]`, but we should | ||||
|         # always get the same one. | ||||
|         self.assertEqual(list(sorted_topologically([4, 3, 2, 1], graph)), [1, 2, 3, 4]) | ||||
| 
 | ||||
|     def test_duplicates(self): | ||||
|         "Test that a graph with duplicate edges work" | ||||
|         graph = {1: [], 2: [1, 1], 3: [2, 2], 4: [3]}  # type: Dict[int, List[int]] | ||||
| 
 | ||||
|         self.assertEqual(list(sorted_topologically([4, 3, 2, 1], graph)), [1, 2, 3, 4]) | ||||
| 
 | ||||
|     def test_multiple_paths(self): | ||||
|         "Test that a graph with multiple paths between two nodes work" | ||||
|         graph = {1: [], 2: [1], 3: [2], 4: [3, 2, 1]}  # type: Dict[int, List[int]] | ||||
| 
 | ||||
|         self.assertEqual(list(sorted_topologically([4, 3, 2, 1], graph)), [1, 2, 3, 4]) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								tox.ini
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
										
									
									
									
								
							|  | @ -118,7 +118,7 @@ commands = | |||
|     # Make all greater-thans equals so we test the oldest version of our direct | ||||
|     # dependencies, but make the pyopenssl 17.0, which can work against an | ||||
|     # OpenSSL 1.1 compiled cryptography (as older ones don't compile on Travis). | ||||
|     /bin/sh -c 'python -m synapse.python_dependencies | sed -e "s/>=/==/g" -e "s/psycopg2==2.6//" -e "s/pyopenssl==16.0.0/pyopenssl==17.0.0/" | xargs -d"\n" pip install' | ||||
|     /bin/sh -c 'python -m synapse.python_dependencies | sed -e "s/>=/==/g" -e "/psycopg2/d" -e "s/pyopenssl==16.0.0/pyopenssl==17.0.0/" | xargs -d"\n" pip install' | ||||
| 
 | ||||
|     # Install Synapse itself. This won't update any libraries. | ||||
|     pip install -e ".[test]" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jason Robinson
						Jason Robinson