Combine SpamCheckerApi with the more generic ModuleApi. (#8464)

Lots of different module apis is not easy to maintain.

Rather than adding yet another ModuleApi(hs, hs.get_auth_handler()) incantation, first add an hs.get_module_api() method and use it where possible.
This commit is contained in:
Richard van der Hoff 2020-10-07 12:03:26 +01:00 committed by GitHub
parent 01f82bfe32
commit 4f0637346a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 58 deletions

1
changelog.d/8464.misc Normal file
View file

@ -0,0 +1 @@
Combine `SpamCheckerApi` with the more generic `ModuleApi`.

View file

@ -11,7 +11,7 @@ able to be imported by the running Synapse.
The Python class is instantiated with two objects: The Python class is instantiated with two objects:
* Any configuration (see below). * Any configuration (see below).
* An instance of `synapse.spam_checker_api.SpamCheckerApi`. * An instance of `synapse.module_api.ModuleApi`.
It then implements methods which return a boolean to alter behavior in Synapse. It then implements methods which return a boolean to alter behavior in Synapse.
@ -26,11 +26,8 @@ well as some specific methods:
The details of the each of these methods (as well as their inputs and outputs) The details of the each of these methods (as well as their inputs and outputs)
are documented in the `synapse.events.spamcheck.SpamChecker` class. are documented in the `synapse.events.spamcheck.SpamChecker` class.
The `SpamCheckerApi` class provides a way for the custom spam checker class to The `ModuleApi` class provides a way for the custom spam checker class to
call back into the homeserver internals. It currently implements the following call back into the homeserver internals.
methods:
* `get_state_events_in_room`
### Example ### Example

View file

@ -56,7 +56,6 @@ from synapse.http.server import (
from synapse.http.site import SynapseSite from synapse.http.site import SynapseSite
from synapse.logging.context import LoggingContext from synapse.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
from synapse.module_api import ModuleApi
from synapse.python_dependencies import check_requirements from synapse.python_dependencies import check_requirements
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory
@ -106,7 +105,7 @@ class SynapseHomeServer(HomeServer):
additional_resources = listener_config.http_options.additional_resources additional_resources = listener_config.http_options.additional_resources
logger.debug("Configuring additional resources: %r", additional_resources) logger.debug("Configuring additional resources: %r", additional_resources)
module_api = ModuleApi(self, self.get_auth_handler()) module_api = self.get_module_api()
for path, resmodule in additional_resources.items(): for path, resmodule in additional_resources.items():
handler_cls, config = load_module(resmodule) handler_cls, config = load_module(resmodule)
handler = handler_cls(config, module_api) handler = handler_cls(config, module_api)

View file

@ -17,24 +17,25 @@
import inspect import inspect
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from synapse.spam_checker_api import RegistrationBehaviour, SpamCheckerApi from synapse.spam_checker_api import RegistrationBehaviour
from synapse.types import Collection from synapse.types import Collection
MYPY = False MYPY = False
if MYPY: if MYPY:
import synapse.events
import synapse.server import synapse.server
class SpamChecker: class SpamChecker:
def __init__(self, hs: "synapse.server.HomeServer"): def __init__(self, hs: "synapse.server.HomeServer"):
self.spam_checkers = [] # type: List[Any] self.spam_checkers = [] # type: List[Any]
api = hs.get_module_api()
for module, config in hs.config.spam_checkers: for module, config in hs.config.spam_checkers:
# Older spam checkers don't accept the `api` argument, so we # Older spam checkers don't accept the `api` argument, so we
# try and detect support. # try and detect support.
spam_args = inspect.getfullargspec(module) spam_args = inspect.getfullargspec(module)
if "api" in spam_args.args: if "api" in spam_args.args:
api = SpamCheckerApi(hs)
self.spam_checkers.append(module(config=config, api=api)) self.spam_checkers.append(module(config=config, api=api))
else: else:
self.spam_checkers.append(module(config=config)) self.spam_checkers.append(module(config=config))

View file

@ -16,7 +16,6 @@ from typing import Callable
from synapse.events import EventBase from synapse.events import EventBase
from synapse.events.snapshot import EventContext from synapse.events.snapshot import EventContext
from synapse.module_api import ModuleApi
from synapse.types import Requester, StateMap from synapse.types import Requester, StateMap
@ -40,7 +39,7 @@ class ThirdPartyEventRules:
if module is not None: if module is not None:
self.third_party_rules = module( self.third_party_rules = module(
config=config, module_api=ModuleApi(hs, hs.get_auth_handler()), config=config, module_api=hs.get_module_api(),
) )
async def check_event_allowed( async def check_event_allowed(

View file

@ -164,7 +164,14 @@ class AuthHandler(BaseHandler):
self.bcrypt_rounds = hs.config.bcrypt_rounds self.bcrypt_rounds = hs.config.bcrypt_rounds
# we can't use hs.get_module_api() here, because to do so will create an
# import loop.
#
# TODO: refactor this class to separate the lower-level stuff that
# ModuleApi can use from the higher-level stuff that uses ModuleApi, as
# better way to break the loop
account_handler = ModuleApi(hs, self) account_handler = ModuleApi(hs, self)
self.password_providers = [ self.password_providers = [
module(config=config, account_handler=account_handler) module(config=config, account_handler=account_handler)
for module, config in hs.config.password_providers for module, config in hs.config.password_providers

View file

@ -14,13 +14,14 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Iterable, Optional, Tuple
from twisted.internet import defer from twisted.internet import defer
from synapse.http.client import SimpleHttpClient from synapse.http.client import SimpleHttpClient
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable, run_in_background from synapse.logging.context import make_deferred_yieldable, run_in_background
from synapse.storage.state import StateFilter
from synapse.types import UserID from synapse.types import UserID
if TYPE_CHECKING: if TYPE_CHECKING:
@ -293,6 +294,32 @@ class ModuleApi:
registered_user_id, request, client_redirect_url, registered_user_id, request, client_redirect_url,
) )
@defer.inlineCallbacks
def get_state_events_in_room(
self, room_id: str, types: Iterable[Tuple[str, Optional[str]]]
) -> defer.Deferred:
"""Gets current state events for the given room.
(This is exposed for compatibility with the old SpamCheckerApi. We should
probably deprecate it and replace it with an async method in a subclass.)
Args:
room_id: The room ID to get state events in.
types: The event type and state key (using None
to represent 'any') of the room state to acquire.
Returns:
twisted.internet.defer.Deferred[list(synapse.events.FrozenEvent)]:
The filtered state events in the room.
"""
state_ids = yield defer.ensureDeferred(
self._store.get_filtered_current_state_ids(
room_id=room_id, state_filter=StateFilter.from_types(types)
)
)
state = yield defer.ensureDeferred(self._store.get_events(state_ids.values()))
return state.values()
class PublicRoomListManager: class PublicRoomListManager:
"""Contains methods for adding to, removing from and querying whether a room """Contains methods for adding to, removing from and querying whether a room

View file

@ -91,6 +91,7 @@ from synapse.handlers.typing import FollowerTypingHandler, TypingWriterHandler
from synapse.handlers.user_directory import UserDirectoryHandler from synapse.handlers.user_directory import UserDirectoryHandler
from synapse.http.client import InsecureInterceptableContextFactory, SimpleHttpClient from synapse.http.client import InsecureInterceptableContextFactory, SimpleHttpClient
from synapse.http.matrixfederationclient import MatrixFederationHttpClient from synapse.http.matrixfederationclient import MatrixFederationHttpClient
from synapse.module_api import ModuleApi
from synapse.notifier import Notifier from synapse.notifier import Notifier
from synapse.push.action_generator import ActionGenerator from synapse.push.action_generator import ActionGenerator
from synapse.push.pusherpool import PusherPool from synapse.push.pusherpool import PusherPool
@ -656,6 +657,10 @@ class HomeServer(metaclass=abc.ABCMeta):
def get_federation_ratelimiter(self) -> FederationRateLimiter: def get_federation_ratelimiter(self) -> FederationRateLimiter:
return FederationRateLimiter(self.clock, config=self.config.rc_federation) return FederationRateLimiter(self.clock, config=self.config.rc_federation)
@cache_in_self
def get_module_api(self) -> ModuleApi:
return ModuleApi(self, self.get_auth_handler())
async def remove_pusher(self, app_id: str, push_key: str, user_id: str): async def remove_pusher(self, app_id: str, push_key: str, user_id: str):
return await self.get_pusherpool().remove_pusher(app_id, push_key, user_id) return await self.get_pusherpool().remove_pusher(app_id, push_key, user_id)

View file

@ -12,19 +12,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
from enum import Enum from enum import Enum
from twisted.internet import defer
from synapse.storage.state import StateFilter
MYPY = False
if MYPY:
import synapse.server
logger = logging.getLogger(__name__)
class RegistrationBehaviour(Enum): class RegistrationBehaviour(Enum):
""" """
@ -34,35 +23,3 @@ class RegistrationBehaviour(Enum):
ALLOW = "allow" ALLOW = "allow"
SHADOW_BAN = "shadow_ban" SHADOW_BAN = "shadow_ban"
DENY = "deny" DENY = "deny"
class SpamCheckerApi:
"""A proxy object that gets passed to spam checkers so they can get
access to rooms and other relevant information.
"""
def __init__(self, hs: "synapse.server.HomeServer"):
self.hs = hs
self._store = hs.get_datastore()
@defer.inlineCallbacks
def get_state_events_in_room(self, room_id: str, types: tuple) -> defer.Deferred:
"""Gets state events for the given room.
Args:
room_id: The room ID to get state events in.
types: The event type and state key (using None
to represent 'any') of the room state to acquire.
Returns:
twisted.internet.defer.Deferred[list(synapse.events.FrozenEvent)]:
The filtered state events in the room.
"""
state_ids = yield defer.ensureDeferred(
self._store.get_filtered_current_state_ids(
room_id=room_id, state_filter=StateFilter.from_types(types)
)
)
state = yield defer.ensureDeferred(self._store.get_events(state_ids.values()))
return state.values()

View file

@ -12,7 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from synapse.module_api import ModuleApi
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client.v1 import login, room from synapse.rest.client.v1 import login, room
@ -28,7 +28,7 @@ class ModuleApiTestCase(HomeserverTestCase):
def prepare(self, reactor, clock, homeserver): def prepare(self, reactor, clock, homeserver):
self.store = homeserver.get_datastore() self.store = homeserver.get_datastore()
self.module_api = ModuleApi(homeserver, homeserver.get_auth_handler()) self.module_api = homeserver.get_module_api()
def test_can_register_user(self): def test_can_register_user(self):
"""Tests that an external module can register a user""" """Tests that an external module can register a user"""