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

Commit

Permalink
Add support to the spam checker for blocking registrations.
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep committed Aug 14, 2020
1 parent ac77cdb commit 7a88534
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.d/8034.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for shadow-banning users (ignoring any message send requests).
35 changes: 33 additions & 2 deletions synapse/events/spamcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
# limitations under the License.

import inspect
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional, Tuple

from synapse.spam_checker_api import SpamCheckerApi
from synapse.spam_checker_api import RegistrationBehaviour, SpamCheckerApi
from synapse.types import Collection

MYPY = False
if MYPY:
Expand Down Expand Up @@ -160,3 +161,33 @@ def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
return True

return False

def check_registration_for_spam(
self,
email_threepid: Optional[dict],
username: Optional[str],
request_info: Collection[Tuple[str, str]],
) -> RegistrationBehaviour:
"""Checks if we should allow the given registration request.
Args:
email_threepid: The email threepid used for registering, if any
username: The request user name, if any
request_info: List of tuples of user agent and IP that
were used during the registration process.
Returns:
Enum for how the request should be handled
"""

for spam_checker in self.spam_checkers:
# For backwards compatibility, only run if the method exists on the
# spam checker
checker = getattr(spam_checker, "check_registration_for_spam", None)
if checker:
behaviour = checker(email_threepid, username, request_info)
assert isinstance(behaviour, RegistrationBehaviour)
if behaviour != RegistrationBehaviour.ALLOW:
return behaviour

return RegistrationBehaviour.ALLOW
8 changes: 8 additions & 0 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ async def check_ui_auth(
# authentication flow.
await self.store.set_ui_auth_clientdict(sid, clientdict)

user_agent = request.requestHeaders.getRawHeaders(b"User-Agent", default=[b""])[
0
].decode("ascii", "surrogateescape")

await self.store.add_user_agent_ip_to_ui_auth_session(
session.session_id, user_agent, clientip
)

if not authdict:
raise InteractiveAuthIncompleteError(
session.session_id, self._auth_dict_for_flows(flows, session.session_id)
Expand Down
26 changes: 26 additions & 0 deletions synapse/rest/client/v2_alpha/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
parse_string,
)
from synapse.push.mailer import load_jinja2_templates
from synapse.spam_checker_api import RegistrationBehaviour
from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.ratelimitutils import FederationRateLimiter
from synapse.util.stringutils import assert_valid_client_secret, random_string
Expand Down Expand Up @@ -390,6 +391,8 @@ def __init__(self, hs):
self.clock = hs.get_clock()
self._registration_enabled = self.hs.config.enable_registration

self.spam_checker = hs.get_spam_checker()

self._registration_flows = _calculate_registration_flows(
hs.config, self.auth_handler
)
Expand Down Expand Up @@ -610,12 +613,35 @@ async def on_POST(self, request):
Codes.THREEPID_IN_USE,
)

entries = await self.store.get_user_agents_ips_to_ui_auth_session(
session_id
)

result = self.spam_checker.check_registration_for_spam(
threepid, desired_username, entries,
)

if result == RegistrationBehaviour.DENY:
logger.info(
"Blocked registration of %r", desired_username,
)
# We return a 429 to make it not obvious that they've been
# denied.
raise SynapseError(429, "Rate limited")

shadow_banned = result == RegistrationBehaviour.SHADOW_BAN
if shadow_banned:
logger.info(
"Shadow banning registration of %r", desired_username,
)

registered_user_id = await self.registration_handler.register_user(
localpart=desired_username,
password_hash=password_hash,
guest_access_token=guest_access_token,
threepid=threepid,
address=client_addr,
shadow_banned=shadow_banned,
)
# Necessary due to auth checks prior to the threepid being
# written to the db
Expand Down
11 changes: 11 additions & 0 deletions synapse/spam_checker_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from enum import Enum

from twisted.internet import defer

Expand All @@ -25,6 +26,16 @@
logger = logging.getLogger(__name__)


class RegistrationBehaviour(Enum):
"""
Enum to define whether a registration request should allowed, denied, or shadow-banned.
"""

ALLOW = "allow"
SHADOW_BAN = "shadow_ban"
DENY = "deny"


class SpamCheckerApi(object):
"""A proxy object that gets passed to spam checkers so they can get
access to rooms and other relevant information.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Copyright 2020 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.
*/

CREATE TABLE IF NOT EXISTS ui_auth_sessions_ips(
session_id TEXT NOT NULL,
ip TEXT NOT NULL,
user_agent TEXT NOT NULL,
UNIQUE (session_id, ip, user_agent),
FOREIGN KEY (session_id)
REFERENCES ui_auth_sessions (session_id)
);
39 changes: 38 additions & 1 deletion synapse/storage/databases/main/ui_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# 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 Any, Dict, Optional, Union
from typing import Any, Dict, List, Optional, Tuple, Union

import attr
from canonicaljson import json
Expand Down Expand Up @@ -258,6 +258,34 @@ async def get_ui_auth_session_data(

return serverdict.get(key, default)

async def add_user_agent_ip_to_ui_auth_session(
self, session_id: str, user_agent: str, ip: str,
):
"""Add the given user agent / IP to the tracking table
"""
await self.db_pool.simple_upsert(
table="ui_auth_sessions_ips",
keyvalues={"session_id": session_id, "user_agent": user_agent, "ip": ip},
values={},
desc="add_user_agent_ip_to_ui_auth_session",
)

async def get_user_agents_ips_to_ui_auth_session(
self, session_id: str,
) -> List[Tuple[str, str]]:
"""Get the given user agents / IPs used during the ui auth process
Returns:
List of user_agent/ip pairs
"""
rows = await self.db_pool.simple_select_list(
table="ui_auth_sessions_ips",
keyvalues={"session_id": session_id},
retcols=("user_agent", "ip"),
desc="get_user_agents_ips_to_ui_auth_session",
)
return [(row["user_agent"], row["ip"]) for row in rows]


class UIAuthStore(UIAuthWorkerStore):
def delete_old_ui_auth_sessions(self, expiration_time: int):
Expand All @@ -281,6 +309,15 @@ def _delete_old_ui_auth_sessions_txn(self, txn, expiration_time: int):
txn.execute(sql, [expiration_time])
session_ids = [r[0] for r in txn.fetchall()]

# Delete the corresponding IP/user agents.
self.db_pool.simple_delete_many_txn(
txn,
table="ui_auth_sessions_ips",
column="session_id",
iterable=session_ids,
keyvalues={},
)

# Delete the corresponding completed credentials.
self.db_pool.simple_delete_many_txn(
txn,
Expand Down

0 comments on commit 7a88534

Please sign in to comment.