diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index c1ade1333b..06ade25674 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -22,23 +22,23 @@ import pymacaroons
from netaddr import IPAddress
from twisted.internet import defer
+from twisted.web.server import Request
import synapse.logging.opentracing as opentracing
import synapse.types
from synapse import event_auth
-from synapse.api.constants import EventTypes, LimitBlockingTypes, Membership, UserTypes
+from synapse.api.auth_blocking import AuthBlocking
+from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import (
AuthError,
Codes,
InvalidClientTokenError,
MissingClientTokenError,
- ResourceLimitError,
)
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
-from synapse.config.server import is_threepid_reserved
from synapse.events import EventBase
from synapse.types import StateMap, UserID
-from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
+from synapse.util.caches import register_cache
from synapse.util.caches.lrucache import LruCache
from synapse.util.metrics import Measure
@@ -74,10 +74,14 @@ class Auth(object):
self.store = hs.get_datastore()
self.state = hs.get_state_handler()
- self.token_cache = LruCache(CACHE_SIZE_FACTOR * 10000)
+ self.token_cache = LruCache(10000)
register_cache("cache", "token_cache", self.token_cache)
+ self._auth_blocking = AuthBlocking(self.hs)
+
self._account_validity = hs.config.account_validity
+ self._track_appservice_user_ips = hs.config.track_appservice_user_ips
+ self._macaroon_secret_key = hs.config.macaroon_secret_key
@defer.inlineCallbacks
def check_from_context(self, room_version: str, event, context, do_sig_check=True):
@@ -159,19 +163,25 @@ class Auth(object):
@defer.inlineCallbacks
def get_user_by_req(
- self, request, allow_guest=False, rights="access", allow_expired=False
+ self,
+ request: Request,
+ allow_guest: bool = False,
+ rights: str = "access",
+ allow_expired: bool = False,
):
""" Get a registered user's ID.
Args:
- request - An HTTP request with an access_token query parameter.
- allow_expired - Whether to allow the request through even if the account is
- expired. If true, Synapse will still require an access token to be
- provided but won't check if the account it belongs to has expired. This
- works thanks to /login delivering access tokens regardless of accounts'
- expiration.
+ request: An HTTP request with an access_token query parameter.
+ allow_guest: If False, will raise an AuthError if the user making the
+ request is a guest.
+ rights: The operation being performed; the access token must allow this
+ allow_expired: If True, allow the request through even if the account
+ is expired, or session token lifetime has ended. Note that
+ /login will deliver access tokens regardless of expiration.
+
Returns:
- defer.Deferred: resolves to a ``synapse.types.Requester`` object
+ defer.Deferred: resolves to a `synapse.types.Requester` object
Raises:
InvalidClientCredentialsError if no user by that token exists or the token
is invalid.
@@ -191,7 +201,7 @@ class Auth(object):
opentracing.set_tag("authenticated_entity", user_id)
opentracing.set_tag("appservice_id", app_service.id)
- if ip_addr and self.hs.config.track_appservice_user_ips:
+ if ip_addr and self._track_appservice_user_ips:
yield self.store.insert_client_ip(
user_id=user_id,
access_token=access_token,
@@ -202,7 +212,9 @@ class Auth(object):
return synapse.types.create_requester(user_id, app_service=app_service)
- user_info = yield self.get_user_by_access_token(access_token, rights)
+ user_info = yield self.get_user_by_access_token(
+ access_token, rights, allow_expired=allow_expired
+ )
user = user_info["user"]
token_id = user_info["token_id"]
is_guest = user_info["is_guest"]
@@ -277,13 +289,17 @@ class Auth(object):
return user_id, app_service
@defer.inlineCallbacks
- def get_user_by_access_token(self, token, rights="access"):
+ def get_user_by_access_token(
+ self, token: str, rights: str = "access", allow_expired: bool = False,
+ ):
""" Validate access token and get user_id from it
Args:
- token (str): The access token to get the user by.
- rights (str): The operation being performed; the access token must
- allow this.
+ token: The access token to get the user by
+ rights: The operation being performed; the access token must
+ allow this
+ allow_expired: If False, raises an InvalidClientTokenError
+ if the token is expired
Returns:
Deferred[dict]: dict that includes:
`user` (UserID)
@@ -291,8 +307,10 @@ class Auth(object):
`token_id` (int|None): access token id. May be None if guest
`device_id` (str|None): device corresponding to access token
Raises:
+ InvalidClientTokenError if a user by that token exists, but the token is
+ expired
InvalidClientCredentialsError if no user by that token exists or the token
- is invalid.
+ is invalid
"""
if rights == "access":
@@ -301,7 +319,8 @@ class Auth(object):
if r:
valid_until_ms = r["valid_until_ms"]
if (
- valid_until_ms is not None
+ not allow_expired
+ and valid_until_ms is not None
and valid_until_ms < self.clock.time_msec()
):
# there was a valid access token, but it has expired.
@@ -454,7 +473,7 @@ class Auth(object):
# access_tokens include a nonce for uniqueness: any value is acceptable
v.satisfy_general(lambda c: c.startswith("nonce = "))
- v.verify(macaroon, self.hs.config.macaroon_secret_key)
+ v.verify(macaroon, self._macaroon_secret_key)
def _verify_expiry(self, caveat):
prefix = "time < "
@@ -491,16 +510,16 @@ class Auth(object):
request.authenticated_entity = service.sender
return defer.succeed(service)
- def is_server_admin(self, user):
+ async def is_server_admin(self, user: UserID) -> bool:
""" Check if the given user is a local server admin.
Args:
- user (UserID): user to check
+ user: user to check
Returns:
- bool: True if the user is an admin
+ True if the user is an admin
"""
- return self.store.is_server_admin(user)
+ return await self.store.is_server_admin(user)
def compute_auth_events(
self, event, current_state_ids: StateMap[str], for_verification: bool = False,
@@ -537,8 +556,7 @@ class Auth(object):
return defer.succeed(auth_ids)
- @defer.inlineCallbacks
- def check_can_change_room_list(self, room_id: str, user: UserID):
+ async def check_can_change_room_list(self, room_id: str, user: UserID):
"""Determine whether the user is allowed to edit the room's entry in the
published room list.
@@ -547,17 +565,17 @@ class Auth(object):
user
"""
- is_admin = yield self.is_server_admin(user)
+ is_admin = await self.is_server_admin(user)
if is_admin:
return True
user_id = user.to_string()
- yield self.check_user_in_room(room_id, user_id)
+ await self.check_user_in_room(room_id, user_id)
# We currently require the user is a "moderator" in the room. We do this
# by checking if they would (theoretically) be able to change the
# m.room.canonical_alias events
- power_level_event = yield self.state.get_current_state(
+ power_level_event = await self.state.get_current_state(
room_id, EventTypes.PowerLevels, ""
)
@@ -573,7 +591,7 @@ class Auth(object):
return user_level >= send_level
@staticmethod
- def has_access_token(request):
+ def has_access_token(request: Request):
"""Checks if the request has an access_token.
Returns:
@@ -584,7 +602,7 @@ class Auth(object):
return bool(query_params) or bool(auth_headers)
@staticmethod
- def get_access_token_from_request(request):
+ def get_access_token_from_request(request: Request):
"""Extracts the access_token from the request.
Args:
@@ -664,71 +682,5 @@ class Auth(object):
% (user_id, room_id),
)
- @defer.inlineCallbacks
- def check_auth_blocking(self, user_id=None, threepid=None, user_type=None):
- """Checks if the user should be rejected for some external reason,
- such as monthly active user limiting or global disable flag
-
- Args:
- user_id(str|None): If present, checks for presence against existing
- MAU cohort
-
- threepid(dict|None): If present, checks for presence against configured
- reserved threepid. Used in cases where the user is trying register
- with a MAU blocked server, normally they would be rejected but their
- threepid is on the reserved list. user_id and
- threepid should never be set at the same time.
-
- user_type(str|None): If present, is used to decide whether to check against
- certain blocking reasons like MAU.
- """
-
- # Never fail an auth check for the server notices users or support user
- # This can be a problem where event creation is prohibited due to blocking
- if user_id is not None:
- if user_id == self.hs.config.server_notices_mxid:
- return
- if (yield self.store.is_support_user(user_id)):
- return
-
- if self.hs.config.hs_disabled:
- raise ResourceLimitError(
- 403,
- self.hs.config.hs_disabled_message,
- errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
- admin_contact=self.hs.config.admin_contact,
- limit_type=LimitBlockingTypes.HS_DISABLED,
- )
- if self.hs.config.limit_usage_by_mau is True:
- assert not (user_id and threepid)
-
- # If the user is already part of the MAU cohort or a trial user
- if user_id:
- timestamp = yield self.store.user_last_seen_monthly_active(user_id)
- if timestamp:
- return
-
- is_trial = yield self.store.is_trial_user(user_id)
- if is_trial:
- return
- elif threepid:
- # If the user does not exist yet, but is signing up with a
- # reserved threepid then pass auth check
- if is_threepid_reserved(
- self.hs.config.mau_limits_reserved_threepids, threepid
- ):
- return
- elif user_type == UserTypes.SUPPORT:
- # If the user does not exist yet and is of type "support",
- # allow registration. Support users are excluded from MAU checks.
- return
- # Else if there is no room in the MAU bucket, bail
- current_mau = yield self.store.get_monthly_active_count()
- if current_mau >= self.hs.config.max_mau_value:
- raise ResourceLimitError(
- 403,
- "Monthly Active User Limit Exceeded",
- admin_contact=self.hs.config.admin_contact,
- errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
- limit_type=LimitBlockingTypes.MONTHLY_ACTIVE_USER,
- )
+ def check_auth_blocking(self, *args, **kwargs):
+ return self._auth_blocking.check_auth_blocking(*args, **kwargs)
diff --git a/synapse/api/auth_blocking.py b/synapse/api/auth_blocking.py
new file mode 100644
index 0000000000..5c499b6b4e
--- /dev/null
+++ b/synapse/api/auth_blocking.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+import logging
+
+from twisted.internet import defer
+
+from synapse.api.constants import LimitBlockingTypes, UserTypes
+from synapse.api.errors import Codes, ResourceLimitError
+from synapse.config.server import is_threepid_reserved
+
+logger = logging.getLogger(__name__)
+
+
+class AuthBlocking(object):
+ def __init__(self, hs):
+ self.store = hs.get_datastore()
+
+ self._server_notices_mxid = hs.config.server_notices_mxid
+ self._hs_disabled = hs.config.hs_disabled
+ self._hs_disabled_message = hs.config.hs_disabled_message
+ self._admin_contact = hs.config.admin_contact
+ self._max_mau_value = hs.config.max_mau_value
+ self._limit_usage_by_mau = hs.config.limit_usage_by_mau
+ self._mau_limits_reserved_threepids = hs.config.mau_limits_reserved_threepids
+
+ @defer.inlineCallbacks
+ def check_auth_blocking(self, user_id=None, threepid=None, user_type=None):
+ """Checks if the user should be rejected for some external reason,
+ such as monthly active user limiting or global disable flag
+
+ Args:
+ user_id(str|None): If present, checks for presence against existing
+ MAU cohort
+
+ threepid(dict|None): If present, checks for presence against configured
+ reserved threepid. Used in cases where the user is trying register
+ with a MAU blocked server, normally they would be rejected but their
+ threepid is on the reserved list. user_id and
+ threepid should never be set at the same time.
+
+ user_type(str|None): If present, is used to decide whether to check against
+ certain blocking reasons like MAU.
+ """
+
+ # Never fail an auth check for the server notices users or support user
+ # This can be a problem where event creation is prohibited due to blocking
+ if user_id is not None:
+ if user_id == self._server_notices_mxid:
+ return
+ if (yield self.store.is_support_user(user_id)):
+ return
+
+ if self._hs_disabled:
+ raise ResourceLimitError(
+ 403,
+ self._hs_disabled_message,
+ errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
+ admin_contact=self._admin_contact,
+ limit_type=LimitBlockingTypes.HS_DISABLED,
+ )
+ if self._limit_usage_by_mau is True:
+ assert not (user_id and threepid)
+
+ # If the user is already part of the MAU cohort or a trial user
+ if user_id:
+ timestamp = yield self.store.user_last_seen_monthly_active(user_id)
+ if timestamp:
+ return
+
+ is_trial = yield self.store.is_trial_user(user_id)
+ if is_trial:
+ return
+ elif threepid:
+ # If the user does not exist yet, but is signing up with a
+ # reserved threepid then pass auth check
+ if is_threepid_reserved(self._mau_limits_reserved_threepids, threepid):
+ return
+ elif user_type == UserTypes.SUPPORT:
+ # If the user does not exist yet and is of type "support",
+ # allow registration. Support users are excluded from MAU checks.
+ return
+ # Else if there is no room in the MAU bucket, bail
+ current_mau = yield self.store.get_monthly_active_count()
+ if current_mau >= self._max_mau_value:
+ raise ResourceLimitError(
+ 403,
+ "Monthly Active User Limit Exceeded",
+ admin_contact=self._admin_contact,
+ errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
+ limit_type=LimitBlockingTypes.MONTHLY_ACTIVE_USER,
+ )
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index cc8577552b..5ec4a77ccd 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -61,12 +61,9 @@ class LoginType(object):
MSISDN = "m.login.msisdn"
RECAPTCHA = "m.login.recaptcha"
TERMS = "m.login.terms"
+ SSO = "m.login.sso"
DUMMY = "m.login.dummy"
- # Only for C/S API v1
- APPLICATION_SERVICE = "m.login.application_service"
- SHARED_SECRET = "org.matrix.login.shared_secret"
-
class EventTypes(object):
Member = "m.room.member"
@@ -96,6 +93,8 @@ class EventTypes(object):
Retention = "m.room.retention"
+ Presence = "m.presence"
+
class RejectedReason(object):
AUTH_ERROR = "auth_error"
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 616942b057..d54dfb385d 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -64,6 +64,13 @@ class Codes(object):
INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
EXPIRED_ACCOUNT = "ORG_MATRIX_EXPIRED_ACCOUNT"
+ PASSWORD_TOO_SHORT = "M_PASSWORD_TOO_SHORT"
+ PASSWORD_NO_DIGIT = "M_PASSWORD_NO_DIGIT"
+ PASSWORD_NO_UPPERCASE = "M_PASSWORD_NO_UPPERCASE"
+ PASSWORD_NO_LOWERCASE = "M_PASSWORD_NO_LOWERCASE"
+ PASSWORD_NO_SYMBOL = "M_PASSWORD_NO_SYMBOL"
+ PASSWORD_IN_DICTIONARY = "M_PASSWORD_IN_DICTIONARY"
+ WEAK_PASSWORD = "M_WEAK_PASSWORD"
INVALID_SIGNATURE = "M_INVALID_SIGNATURE"
USER_DEACTIVATED = "M_USER_DEACTIVATED"
BAD_ALIAS = "M_BAD_ALIAS"
@@ -79,7 +86,14 @@ class CodeMessageException(RuntimeError):
def __init__(self, code, msg):
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
- self.code = code
+
+ # Some calls to this method pass instances of http.HTTPStatus for `code`.
+ # While HTTPStatus is a subclass of int, it has magic __str__ methods
+ # which emit `HTTPStatus.FORBIDDEN` when converted to a str, instead of `403`.
+ # This causes inconsistency in our log lines.
+ #
+ # To eliminate this behaviour, we convert them to their integer equivalents here.
+ self.code = int(code)
self.msg = msg
@@ -439,6 +453,20 @@ class IncompatibleRoomVersionError(SynapseError):
return cs_error(self.msg, self.errcode, room_version=self._room_version)
+class PasswordRefusedError(SynapseError):
+ """A password has been refused, either during password reset/change or registration.
+ """
+
+ def __init__(
+ self,
+ msg="This password doesn't comply with the server's policy",
+ errcode=Codes.WEAK_PASSWORD,
+ ):
+ super(PasswordRefusedError, self).__init__(
+ code=400, msg=msg, errcode=errcode,
+ )
+
+
class RequestSendFailed(RuntimeError):
"""Sending a HTTP request over federation failed due to not being able to
talk to the remote server for some reason.
diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py
index 7a049b3af7..ec6b3a69a2 100644
--- a/synapse/api/ratelimiting.py
+++ b/synapse/api/ratelimiting.py
@@ -1,4 +1,5 @@
# Copyright 2014-2016 OpenMarket Ltd
+# 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.
@@ -16,75 +17,157 @@ from collections import OrderedDict
from typing import Any, Optional, Tuple
from synapse.api.errors import LimitExceededError
+from synapse.util import Clock
class Ratelimiter(object):
"""
- Ratelimit message sending by user.
+ Ratelimit actions marked by arbitrary keys.
+
+ Args:
+ clock: A homeserver clock, for retrieving the current time
+ rate_hz: The long term number of actions that can be performed in a second.
+ burst_count: How many actions that can be performed before being limited.
"""
- def __init__(self):
- self.message_counts = (
- OrderedDict()
- ) # type: OrderedDict[Any, Tuple[float, int, Optional[float]]]
+ def __init__(self, clock: Clock, rate_hz: float, burst_count: int):
+ self.clock = clock
+ self.rate_hz = rate_hz
+ self.burst_count = burst_count
+
+ # A ordered dictionary keeping track of actions, when they were last
+ # performed and how often. Each entry is a mapping from a key of arbitrary type
+ # to a tuple representing:
+ # * How many times an action has occurred since a point in time
+ # * The point in time
+ # * The rate_hz of this particular entry. This can vary per request
+ self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
- def can_do_action(self, key, time_now_s, rate_hz, burst_count, update=True):
+ def can_do_action(
+ self,
+ key: Any,
+ rate_hz: Optional[float] = None,
+ burst_count: Optional[int] = None,
+ update: bool = True,
+ _time_now_s: Optional[int] = None,
+ ) -> Tuple[bool, float]:
"""Can the entity (e.g. user or IP address) perform the action?
+
Args:
key: The key we should use when rate limiting. Can be a user ID
(when sending events), an IP address, etc.
- time_now_s: The time now.
- rate_hz: The long term number of messages a user can send in a
- second.
- burst_count: How many messages the user can send before being
- limited.
- update (bool): Whether to update the message rates or not. This is
- useful to check if a message would be allowed to be sent before
- its ready to be actually sent.
+ rate_hz: The long term number of actions that can be performed in a second.
+ Overrides the value set during instantiation if set.
+ burst_count: How many actions that can be performed before being limited.
+ Overrides the value set during instantiation if set.
+ update: Whether to count this check as performing the action
+ _time_now_s: The current time. Optional, defaults to the current time according
+ to self.clock. Only used by tests.
+
Returns:
- A pair of a bool indicating if they can send a message now and a
- time in seconds of when they can next send a message.
+ A tuple containing:
+ * A bool indicating if they can perform the action now
+ * The reactor timestamp for when the action can be performed next.
+ -1 if rate_hz is less than or equal to zero
"""
- self.prune_message_counts(time_now_s)
- message_count, time_start, _ignored = self.message_counts.get(
- key, (0.0, time_now_s, None)
- )
+ # Override default values if set
+ time_now_s = _time_now_s if _time_now_s is not None else self.clock.time()
+ rate_hz = rate_hz if rate_hz is not None else self.rate_hz
+ burst_count = burst_count if burst_count is not None else self.burst_count
+
+ # Remove any expired entries
+ self._prune_message_counts(time_now_s)
+
+ # Check if there is an existing count entry for this key
+ action_count, time_start, _ = self.actions.get(key, (0.0, time_now_s, 0.0))
+
+ # Check whether performing another action is allowed
time_delta = time_now_s - time_start
- sent_count = message_count - time_delta * rate_hz
- if sent_count < 0:
+ performed_count = action_count - time_delta * rate_hz
+ if performed_count < 0:
+ # Allow, reset back to count 1
allowed = True
time_start = time_now_s
- message_count = 1.0
- elif sent_count > burst_count - 1.0:
+ action_count = 1.0
+ elif performed_count > burst_count - 1.0:
+ # Deny, we have exceeded our burst count
allowed = False
else:
+ # We haven't reached our limit yet
allowed = True
- message_count += 1
+ action_count += 1.0
if update:
- self.message_counts[key] = (message_count, time_start, rate_hz)
+ self.actions[key] = (action_count, time_start, rate_hz)
if rate_hz > 0:
- time_allowed = time_start + (message_count - burst_count + 1) / rate_hz
+ # Find out when the count of existing actions expires
+ time_allowed = time_start + (action_count - burst_count + 1) / rate_hz
+
+ # Don't give back a time in the past
if time_allowed < time_now_s:
time_allowed = time_now_s
+
else:
+ # XXX: Why is this -1? This seems to only be used in
+ # self.ratelimit. I guess so that clients get a time in the past and don't
+ # feel afraid to try again immediately
time_allowed = -1
return allowed, time_allowed
- def prune_message_counts(self, time_now_s):
- for key in list(self.message_counts.keys()):
- message_count, time_start, rate_hz = self.message_counts[key]
+ def _prune_message_counts(self, time_now_s: int):
+ """Remove message count entries that have not exceeded their defined
+ rate_hz limit
+
+ Args:
+ time_now_s: The current time
+ """
+ # We create a copy of the key list here as the dictionary is modified during
+ # the loop
+ for key in list(self.actions.keys()):
+ action_count, time_start, rate_hz = self.actions[key]
+
+ # Rate limit = "seconds since we started limiting this action" * rate_hz
+ # If this limit has not been exceeded, wipe our record of this action
time_delta = time_now_s - time_start
- if message_count - time_delta * rate_hz > 0:
- break
+ if action_count - time_delta * rate_hz > 0:
+ continue
else:
- del self.message_counts[key]
+ del self.actions[key]
+
+ def ratelimit(
+ self,
+ key: Any,
+ rate_hz: Optional[float] = None,
+ burst_count: Optional[int] = None,
+ update: bool = True,
+ _time_now_s: Optional[int] = None,
+ ):
+ """Checks if an action can be performed. If not, raises a LimitExceededError
+
+ Args:
+ key: An arbitrary key used to classify an action
+ rate_hz: The long term number of actions that can be performed in a second.
+ Overrides the value set during instantiation if set.
+ burst_count: How many actions that can be performed before being limited.
+ Overrides the value set during instantiation if set.
+ update: Whether to count this check as performing the action
+ _time_now_s: The current time. Optional, defaults to the current time according
+ to self.clock. Only used by tests.
+
+ Raises:
+ LimitExceededError: If an action could not be performed, along with the time in
+ milliseconds until the action can be performed again
+ """
+ time_now_s = _time_now_s if _time_now_s is not None else self.clock.time()
- def ratelimit(self, key, time_now_s, rate_hz, burst_count, update=True):
allowed, time_allowed = self.can_do_action(
- key, time_now_s, rate_hz, burst_count, update
+ key,
+ rate_hz=rate_hz,
+ burst_count=burst_count,
+ update=update,
+ _time_now_s=time_now_s,
)
if not allowed:
diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py
index 871179749a..d7baf2bc39 100644
--- a/synapse/api/room_versions.py
+++ b/synapse/api/room_versions.py
@@ -58,7 +58,15 @@ class RoomVersion(object):
enforce_key_validity = attr.ib() # bool
# bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
- special_case_aliases_auth = attr.ib(type=bool, default=False)
+ special_case_aliases_auth = attr.ib(type=bool)
+ # Strictly enforce canonicaljson, do not allow:
+ # * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
+ # * Floats
+ # * NaN, Infinity, -Infinity
+ strict_canonicaljson = attr.ib(type=bool)
+ # bool: MSC2209: Check 'notifications' key while verifying
+ # m.room.power_levels auth rules.
+ limit_notifications_power_levels = attr.ib(type=bool)
class RoomVersions(object):
@@ -69,6 +77,8 @@ class RoomVersions(object):
StateResolutionVersions.V1,
enforce_key_validity=False,
special_case_aliases_auth=True,
+ strict_canonicaljson=False,
+ limit_notifications_power_levels=False,
)
V2 = RoomVersion(
"2",
@@ -77,6 +87,8 @@ class RoomVersions(object):
StateResolutionVersions.V2,
enforce_key_validity=False,
special_case_aliases_auth=True,
+ strict_canonicaljson=False,
+ limit_notifications_power_levels=False,
)
V3 = RoomVersion(
"3",
@@ -85,6 +97,8 @@ class RoomVersions(object):
StateResolutionVersions.V2,
enforce_key_validity=False,
special_case_aliases_auth=True,
+ strict_canonicaljson=False,
+ limit_notifications_power_levels=False,
)
V4 = RoomVersion(
"4",
@@ -93,6 +107,8 @@ class RoomVersions(object):
StateResolutionVersions.V2,
enforce_key_validity=False,
special_case_aliases_auth=True,
+ strict_canonicaljson=False,
+ limit_notifications_power_levels=False,
)
V5 = RoomVersion(
"5",
@@ -101,14 +117,18 @@ class RoomVersions(object):
StateResolutionVersions.V2,
enforce_key_validity=True,
special_case_aliases_auth=True,
+ strict_canonicaljson=False,
+ limit_notifications_power_levels=False,
)
- MSC2432_DEV = RoomVersion(
- "org.matrix.msc2432",
- RoomDisposition.UNSTABLE,
+ V6 = RoomVersion(
+ "6",
+ RoomDisposition.STABLE,
EventFormatVersions.V3,
StateResolutionVersions.V2,
enforce_key_validity=True,
special_case_aliases_auth=False,
+ strict_canonicaljson=True,
+ limit_notifications_power_levels=True,
)
@@ -120,6 +140,6 @@ KNOWN_ROOM_VERSIONS = {
RoomVersions.V3,
RoomVersions.V4,
RoomVersions.V5,
- RoomVersions.MSC2432_DEV,
+ RoomVersions.V6,
)
} # type: Dict[str, RoomVersion]
|