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

Make AccessRules use the public rooms directory instead of checking a room's join rules on rule change #63

Merged
merged 17 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions UPGRADE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,70 @@ for example:
wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb

Upgrading to v1.21.0
====================

Forwarding ``/_synapse/client`` through your reverse proxy
----------------------------------------------------------

The `reverse proxy documentation
<https://github.com/matrix-org/synapse/blob/develop/docs/reverse_proxy.md>`_ has been updated
to include reverse proxy directives for ``/_synapse/client/*`` endpoints. As the user password
reset flow now uses endpoints under this prefix, **you must update your reverse proxy
configurations for user password reset to work**.

Additionally, note that the `Synapse worker documentation
<https://github.com/matrix-org/synapse/blob/develop/docs/workers.md>`_ has been updated to
state that the ``/_synapse/client/password_reset/email/submit_token`` endpoint can be handled
by all workers. If you make use of Synapse's worker feature, please update your reverse proxy
configuration to reflect this change.

New HTML templates
------------------

A new HTML template,
`password_reset_confirmation.html <https://github.com/matrix-org/synapse/blob/develop/synapse/res/templates/password_reset_confirmation.html>`_,
has been added to the ``synapse/res/templates`` directory. If you are using a
custom template directory, you may want to copy the template over and modify it.

Note that as of v1.20.0, templates do not need to be included in custom template
directories for Synapse to start. The default templates will be used if a custom
template cannot be found.

This page will appear to the user after clicking a password reset link that has
been emailed to them.

To complete password reset, the page must include a way to make a `POST`
request to
``/_synapse/client/password_reset/{medium}/submit_token``
with the query parameters from the original link, presented as a URL-encoded form. See the file
itself for more details.

Updated Single Sign-on HTML Templates
-------------------------------------

The ``saml_error.html`` template was removed from Synapse and replaced with the
``sso_error.html`` template. If your Synapse is configured to use SAML and a
custom ``sso_redirect_confirm_template_dir`` configuration then any customisations
of the ``saml_error.html`` template will need to be merged into the ``sso_error.html``
template. These templates are similar, but the parameters are slightly different:

* The ``msg`` parameter should be renamed to ``error_description``.
* There is no longer a ``code`` parameter for the response code.
* A string ``error`` parameter is available that includes a short hint of why a
user is seeing the error page.

ThirdPartyEventRules breaking changes
-------------------------------------

This release introduces a backwards-incompatible change to modules making use of
`ThirdPartyEventRules` in Synapse.

The `http_client` argument is no longer passed to modules as they are initialised. Instead,
modules are expected to make use of the `http_client` property on the `ModuleApi` class.
Modules are now passed a `module_api` argument during initialisation, which is an instance of
`ModuleApi`.

Upgrading to v1.18.0
====================

Expand Down
1 change: 1 addition & 0 deletions changelog.d/63.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make AccessRules use the public rooms directory instead of checking a room's join rules on rule change.
73 changes: 55 additions & 18 deletions synapse/events/third_party_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
# 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 Callable

from twisted.internet import defer
from synapse.events import EventBase
from synapse.module_api import ModuleApi
from synapse.types import StateMap


class ThirdPartyEventRules(object):
Expand All @@ -36,11 +39,10 @@ def __init__(self, hs):

if module is not None:
self.third_party_rules = module(
config=config, http_client=hs.get_simple_http_client()
config=config, module_api=ModuleApi(hs, hs.get_auth_handler()),
)

@defer.inlineCallbacks
def check_event_allowed(self, event, context):
async def check_event_allowed(self, event, context):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We needed to async this method to eventually call the check on whether a room is public. Luckily this method is already awaited on everywhere!

"""Check if a provided event should be allowed in the given context.

Args:
Expand All @@ -53,18 +55,17 @@ def check_event_allowed(self, event, context):
if self.third_party_rules is None:
return True

prev_state_ids = yield context.get_prev_state_ids()
prev_state_ids = await context.get_prev_state_ids()

# Retrieve the state events from the database.
state_events = {}
for key, event_id in prev_state_ids.items():
state_events[key] = yield self.store.get_event(event_id, allow_none=True)
state_events[key] = await self.store.get_event(event_id, allow_none=True)

ret = yield self.third_party_rules.check_event_allowed(event, state_events)
ret = await self.third_party_rules.check_event_allowed(event, state_events)
return ret

@defer.inlineCallbacks
def on_create_room(self, requester, config, is_requester_admin):
async def on_create_room(self, requester, config, is_requester_admin):
"""Intercept requests to create room to allow, deny or update the
request config.

Expand All @@ -80,13 +81,12 @@ def on_create_room(self, requester, config, is_requester_admin):
if self.third_party_rules is None:
return True

ret = yield self.third_party_rules.on_create_room(
ret = await self.third_party_rules.on_create_room(
requester, config, is_requester_admin
)
return ret

@defer.inlineCallbacks
def check_threepid_can_be_invited(self, medium, address, room_id):
async def check_threepid_can_be_invited(self, medium, address, room_id):
"""Check if a provided 3PID can be invited in the given room.

Args:
Expand All @@ -101,14 +101,51 @@ def check_threepid_can_be_invited(self, medium, address, room_id):
if self.third_party_rules is None:
return True

state_ids = yield self.store.get_filtered_current_state_ids(room_id)
room_state_events = yield self.store.get_events(state_ids.values())
state_events = await self._get_state_map_for_room(room_id)

ret = await self.third_party_rules.check_threepid_can_be_invited(
medium, address, state_events
)
return ret

async def check_visibility_can_be_modified(
self, room_id: str, new_visibility: str
) -> bool:
"""Check if a room is allowed to be published to, or removed from, the public room
list.

Args:
room_id: The ID of the room.
new_visibility: The new visibility state. Either "public" or "private".

Returns:
True if the room's visibility can be modified, False if not.
"""
if self.third_party_rules is None:
return True

check_func = getattr(self.third_party_rules, "check_visibility_can_be_modified")
if not check_func or not isinstance(check_func, Callable):
babolivier marked this conversation as resolved.
Show resolved Hide resolved
return True

state_events = await self._get_state_map_for_room(room_id)

return await check_func(room_id, state_events, new_visibility)

async def _get_state_map_for_room(self, room_id: str) -> StateMap[EventBase]:
"""Given a room ID, return the state events of that room.

Args:
room_id: The ID of the room.

Returns:
A dict mapping (event type, state key) to state event.
"""
state_ids = await self.store.get_filtered_current_state_ids(room_id)
room_state_events = await self.store.get_events(state_ids.values())

state_events = {}
for key, event_id in state_ids.items():
state_events[key] = room_state_events[event_id]

ret = yield self.third_party_rules.check_threepid_can_be_invited(
medium, address, state_events
)
return ret
return state_events
10 changes: 10 additions & 0 deletions synapse/handlers/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, hs):
self.config = hs.config
self.enable_room_list_search = hs.config.enable_room_list_search
self.require_membership = hs.config.require_membership_for_aliases
self.third_party_event_rules = hs.get_third_party_event_rules()

self.federation = hs.get_federation_client()
hs.get_federation_registry().register_query_handler(
Expand Down Expand Up @@ -448,6 +449,15 @@ async def edit_published_room_list(
# per alias creation rule?
raise SynapseError(403, "Not allowed to publish room")

# Check if publishing is blocked by a third party module
allowed_by_third_party_rules = await (
self.third_party_event_rules.check_visibility_can_be_modified(
room_id, visibility
)
)
if not allowed_by_third_party_rules:
raise SynapseError(403, "Not allowed to publish room")

await self.store.set_room_is_public(room_id, making_public)

async def edit_published_appservice_room_list(
Expand Down
9 changes: 9 additions & 0 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,15 @@ async def create_room(
creator_id=user_id, is_public=is_public, room_version=room_version,
)

# Check whether this visibility value is blocked by a third party module
allowed_by_third_party_rules = await (
self.third_party_event_rules.check_visibility_can_be_modified(
room_id, visibility
)
)
if not allowed_by_third_party_rules:
raise SynapseError(403, "Room visibility value not allowed.")

directory_handler = self.hs.get_handlers().directory_handler
if room_alias:
await directory_handler.create_association(
Expand Down
52 changes: 52 additions & 0 deletions synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from typing import TYPE_CHECKING

from twisted.internet import defer

from synapse.http.client import SimpleHttpClient
from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable, run_in_background
from synapse.types import UserID

if TYPE_CHECKING:
from synapse.server import HomeServer

"""
This package defines the 'stable' API which can be used by extension modules which
are loaded into Synapse.
Expand All @@ -31,6 +36,50 @@
logger = logging.getLogger(__name__)


class PublicRoomListManager:
"""Contains methods for adding to, removing from and querying whether a room
is in the public room list.

Args:
hs: The Homeserver object
"""

def __init__(self, hs: "HomeServer"):
self._store = hs.get_datastore()

async def room_is_in_public_room_list(self, room_id: str) -> bool:
"""Checks whether a room is in the public room list.

Args:
room_id: The ID of the room.

Returns:
Whether the room is in the public room list. Returns False if the room does
not exist.
"""
room = await self._store.get_room(room_id)
if not room:
return False

return room.get("is_public", False)

async def add_room_to_public_room_list(self, room_id: str) -> None:
"""Publishes a room to the public room list.

Args:
room_id: The ID of the room.
"""
await self._store.set_room_is_public(room_id, True)

async def remove_room_from_public_room_list(self, room_id: str) -> None:
"""Removes a room from the public room list.

Args:
room_id: The ID of the room.
"""
await self._store.set_room_is_public(room_id, False)


class ModuleApi(object):
"""A proxy object that gets passed to various plugin modules so they
can register new users etc if necessary.
Expand All @@ -43,6 +92,9 @@ def __init__(self, hs, auth_handler):
self._auth = hs.get_auth()
self._auth_handler = auth_handler

self.http_client = hs.get_simple_http_client() # type: SimpleHttpClient
self.public_room_list_manager = PublicRoomListManager(hs)

def get_user_by_req(self, req, allow_guest=False):
"""Check the access_token provided for a request

Expand Down
Loading