diff --git a/changelog.d/11356.misc b/changelog.d/11356.misc
index 01ce6a306c..e93b3ddd38 100644
--- a/changelog.d/11356.misc
+++ b/changelog.d/11356.misc
@@ -1 +1 @@
-Add `Final` annotation to string constants in `synapse.api.constants` so that they get typed as `Literal`s.
+Convert most string constants in `synapse.api.constants` into enums, so that they are usable as `Literal`s, and make the rest `Final`.
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 44883c6663..3004f06fdd 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -34,7 +34,7 @@ from synapse.http import get_request_user_agent
from synapse.http.site import SynapseRequest
from synapse.logging import opentracing as opentracing
from synapse.storage.databases.main.registration import TokenLookupResult
-from synapse.types import Requester, StateMap, UserID, create_requester
+from synapse.types import MutableStateMap, Requester, StateMap, UserID, create_requester
from synapse.util.caches.lrucache import LruCache
from synapse.util.macaroons import get_value_from_macaroon, satisfy_expiry
@@ -511,7 +511,7 @@ class Auth:
room_id, EventTypes.PowerLevels, ""
)
- auth_events = {}
+ auth_events: MutableStateMap[EventBase] = {}
if power_level_event:
auth_events[(EventTypes.PowerLevels, "")] = power_level_event
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index f7d29b4319..e3ed86f57b 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -19,6 +19,8 @@
from typing_extensions import Final
+from synapse.util.enum import StrEnum
+
# the max size of a (canonical-json-encoded) event
MAX_PDU_SIZE = 65536
@@ -37,51 +39,49 @@ MAX_GROUP_CATEGORYID_LENGTH = 255
MAX_GROUP_ROLEID_LENGTH = 255
-class Membership:
-
+class Membership(StrEnum):
"""Represents the membership states of a user in a room."""
- INVITE: Final = "invite"
- JOIN: Final = "join"
- KNOCK: Final = "knock"
- LEAVE: Final = "leave"
- BAN: Final = "ban"
- LIST: Final = (INVITE, JOIN, KNOCK, LEAVE, BAN)
+ INVITE = "invite"
+ JOIN = "join"
+ KNOCK = "knock"
+ LEAVE = "leave"
+ BAN = "ban"
-class PresenceState:
+class PresenceState(StrEnum):
"""Represents the presence state of a user."""
- OFFLINE: Final = "offline"
- UNAVAILABLE: Final = "unavailable"
- ONLINE: Final = "online"
- BUSY: Final = "org.matrix.msc3026.busy"
+ OFFLINE = "offline"
+ UNAVAILABLE = "unavailable"
+ ONLINE = "online"
+ BUSY = "org.matrix.msc3026.busy"
-class JoinRules:
- PUBLIC: Final = "public"
- KNOCK: Final = "knock"
- INVITE: Final = "invite"
- PRIVATE: Final = "private"
+class JoinRules(StrEnum):
+ PUBLIC = "public"
+ KNOCK = "knock"
+ INVITE = "invite"
+ PRIVATE = "private"
# As defined for MSC3083.
- RESTRICTED: Final = "restricted"
+ RESTRICTED = "restricted"
-class RestrictedJoinRuleTypes:
+class RestrictedJoinRuleTypes(StrEnum):
"""Understood types for the allow rules in restricted join rules."""
- ROOM_MEMBERSHIP: Final = "m.room_membership"
+ ROOM_MEMBERSHIP = "m.room_membership"
-class LoginType:
- PASSWORD: Final = "m.login.password"
- EMAIL_IDENTITY: Final = "m.login.email.identity"
- MSISDN: Final = "m.login.msisdn"
- RECAPTCHA: Final = "m.login.recaptcha"
- TERMS: Final = "m.login.terms"
- SSO: Final = "m.login.sso"
- DUMMY: Final = "m.login.dummy"
- REGISTRATION_TOKEN: Final = "org.matrix.msc3231.login.registration_token"
+class LoginType(StrEnum):
+ PASSWORD = "m.login.password"
+ EMAIL_IDENTITY = "m.login.email.identity"
+ MSISDN = "m.login.msisdn"
+ RECAPTCHA = "m.login.recaptcha"
+ TERMS = "m.login.terms"
+ SSO = "m.login.sso"
+ DUMMY = "m.login.dummy"
+ REGISTRATION_TOKEN = "org.matrix.msc3231.login.registration_token"
# This is used in the `type` parameter for /register when called by
@@ -89,169 +89,168 @@ class LoginType:
APP_SERVICE_REGISTRATION_TYPE: Final = "m.login.application_service"
-class EventTypes:
- Member: Final = "m.room.member"
- Create: Final = "m.room.create"
- Tombstone: Final = "m.room.tombstone"
- JoinRules: Final = "m.room.join_rules"
- PowerLevels: Final = "m.room.power_levels"
- Aliases: Final = "m.room.aliases"
- Redaction: Final = "m.room.redaction"
- ThirdPartyInvite: Final = "m.room.third_party_invite"
- RelatedGroups: Final = "m.room.related_groups"
-
- RoomHistoryVisibility: Final = "m.room.history_visibility"
- CanonicalAlias: Final = "m.room.canonical_alias"
- Encrypted: Final = "m.room.encrypted"
- RoomAvatar: Final = "m.room.avatar"
- RoomEncryption: Final = "m.room.encryption"
- GuestAccess: Final = "m.room.guest_access"
+class EventTypes(StrEnum):
+ Member = "m.room.member"
+ Create = "m.room.create"
+ Tombstone = "m.room.tombstone"
+ JoinRules = "m.room.join_rules"
+ PowerLevels = "m.room.power_levels"
+ Aliases = "m.room.aliases"
+ Redaction = "m.room.redaction"
+ ThirdPartyInvite = "m.room.third_party_invite"
+ RelatedGroups = "m.room.related_groups"
+
+ RoomHistoryVisibility = "m.room.history_visibility"
+ CanonicalAlias = "m.room.canonical_alias"
+ Encrypted = "m.room.encrypted"
+ RoomAvatar = "m.room.avatar"
+ RoomEncryption = "m.room.encryption"
+ GuestAccess = "m.room.guest_access"
# These are used for validation
- Message: Final = "m.room.message"
- Topic: Final = "m.room.topic"
- Name: Final = "m.room.name"
+ Message = "m.room.message"
+ Topic = "m.room.topic"
+ Name = "m.room.name"
- ServerACL: Final = "m.room.server_acl"
- Pinned: Final = "m.room.pinned_events"
+ ServerACL = "m.room.server_acl"
+ Pinned = "m.room.pinned_events"
- Retention: Final = "m.room.retention"
+ Retention = "m.room.retention"
- Dummy: Final = "org.matrix.dummy_event"
+ Dummy = "org.matrix.dummy_event"
- SpaceChild: Final = "m.space.child"
- SpaceParent: Final = "m.space.parent"
+ SpaceChild = "m.space.child"
+ SpaceParent = "m.space.parent"
- MSC2716_INSERTION: Final = "org.matrix.msc2716.insertion"
- MSC2716_BATCH: Final = "org.matrix.msc2716.batch"
- MSC2716_MARKER: Final = "org.matrix.msc2716.marker"
+ MSC2716_INSERTION = "org.matrix.msc2716.insertion"
+ MSC2716_BATCH = "org.matrix.msc2716.batch"
+ MSC2716_MARKER = "org.matrix.msc2716.marker"
-class ToDeviceEventTypes:
- RoomKeyRequest: Final = "m.room_key_request"
+class ToDeviceEventTypes(StrEnum):
+ RoomKeyRequest = "m.room_key_request"
-class DeviceKeyAlgorithms:
+class DeviceKeyAlgorithms(StrEnum):
"""Spec'd algorithms for the generation of per-device keys"""
- ED25519: Final = "ed25519"
- CURVE25519: Final = "curve25519"
- SIGNED_CURVE25519: Final = "signed_curve25519"
+ ED25519 = "ed25519"
+ CURVE25519 = "curve25519"
+ SIGNED_CURVE25519 = "signed_curve25519"
-class EduTypes:
- Presence: Final = "m.presence"
+class EduTypes(StrEnum):
+ Presence = "m.presence"
-class RejectedReason:
- AUTH_ERROR: Final = "auth_error"
+class RejectedReason(StrEnum):
+ AUTH_ERROR = "auth_error"
-class RoomCreationPreset:
- PRIVATE_CHAT: Final = "private_chat"
- PUBLIC_CHAT: Final = "public_chat"
- TRUSTED_PRIVATE_CHAT: Final = "trusted_private_chat"
+class RoomCreationPreset(StrEnum):
+ PRIVATE_CHAT = "private_chat"
+ PUBLIC_CHAT = "public_chat"
+ TRUSTED_PRIVATE_CHAT = "trusted_private_chat"
-class ThirdPartyEntityKind:
- USER: Final = "user"
- LOCATION: Final = "location"
+class ThirdPartyEntityKind(StrEnum):
+ USER = "user"
+ LOCATION = "location"
ServerNoticeMsgType: Final = "m.server_notice"
ServerNoticeLimitReached: Final = "m.server_notice.usage_limit_reached"
-class UserTypes:
+class UserTypes(StrEnum):
"""Allows for user type specific behaviour. With the benefit of hindsight
'admin' and 'guest' users should also be UserTypes. Normal users are type None
"""
- SUPPORT: Final = "support"
- BOT: Final = "bot"
- ALL_USER_TYPES: Final = (SUPPORT, BOT)
+ SUPPORT = "support"
+ BOT = "bot"
-class RelationTypes:
+class RelationTypes(StrEnum):
"""The types of relations known to this server."""
- ANNOTATION: Final = "m.annotation"
- REPLACE: Final = "m.replace"
- REFERENCE: Final = "m.reference"
- THREAD: Final = "io.element.thread"
+ ANNOTATION = "m.annotation"
+ REPLACE = "m.replace"
+ REFERENCE = "m.reference"
+ THREAD = "io.element.thread"
-class LimitBlockingTypes:
+class LimitBlockingTypes(StrEnum):
"""Reasons that a server may be blocked"""
- MONTHLY_ACTIVE_USER: Final = "monthly_active_user"
- HS_DISABLED: Final = "hs_disabled"
+ MONTHLY_ACTIVE_USER = "monthly_active_user"
+ HS_DISABLED = "hs_disabled"
-class EventContentFields:
+class EventContentFields(StrEnum):
"""Fields found in events' content, regardless of type."""
# Labels for the event, cf https://github.com/matrix-org/matrix-doc/pull/2326
- LABELS: Final = "org.matrix.labels"
+ LABELS = "org.matrix.labels"
# Timestamp to delete the event after
# cf https://github.com/matrix-org/matrix-doc/pull/2228
- SELF_DESTRUCT_AFTER: Final = "org.matrix.self_destruct_after"
+ SELF_DESTRUCT_AFTER = "org.matrix.self_destruct_after"
# cf https://github.com/matrix-org/matrix-doc/pull/1772
- ROOM_TYPE: Final = "type"
+ ROOM_TYPE = "type"
# Whether a room can federate.
- FEDERATE: Final = "m.federate"
+ FEDERATE = "m.federate"
# The creator of the room, as used in `m.room.create` events.
- ROOM_CREATOR: Final = "creator"
+ ROOM_CREATOR = "creator"
# Used in m.room.guest_access events.
- GUEST_ACCESS: Final = "guest_access"
+ GUEST_ACCESS = "guest_access"
# Used on normal messages to indicate they were historically imported after the fact
- MSC2716_HISTORICAL: Final = "org.matrix.msc2716.historical"
+ MSC2716_HISTORICAL = "org.matrix.msc2716.historical"
# For "insertion" events to indicate what the next batch ID should be in
# order to connect to it
- MSC2716_NEXT_BATCH_ID: Final = "org.matrix.msc2716.next_batch_id"
+ MSC2716_NEXT_BATCH_ID = "org.matrix.msc2716.next_batch_id"
# Used on "batch" events to indicate which insertion event it connects to
- MSC2716_BATCH_ID: Final = "org.matrix.msc2716.batch_id"
+ MSC2716_BATCH_ID = "org.matrix.msc2716.batch_id"
# For "marker" events
- MSC2716_MARKER_INSERTION: Final = "org.matrix.msc2716.marker.insertion"
+ MSC2716_MARKER_INSERTION = "org.matrix.msc2716.marker.insertion"
# The authorising user for joining a restricted room.
- AUTHORISING_USER: Final = "join_authorised_via_users_server"
+ AUTHORISING_USER = "join_authorised_via_users_server"
-class RoomTypes:
+class RoomTypes(StrEnum):
"""Understood values of the room_type field of m.room.create events."""
- SPACE: Final = "m.space"
+ SPACE = "m.space"
-class RoomEncryptionAlgorithms:
- MEGOLM_V1_AES_SHA2: Final = "m.megolm.v1.aes-sha2"
- DEFAULT: Final = MEGOLM_V1_AES_SHA2
+class RoomEncryptionAlgorithms(StrEnum):
+ MEGOLM_V1_AES_SHA2 = "m.megolm.v1.aes-sha2"
+ DEFAULT = MEGOLM_V1_AES_SHA2
-class AccountDataTypes:
- DIRECT: Final = "m.direct"
- IGNORED_USER_LIST: Final = "m.ignored_user_list"
+class AccountDataTypes(StrEnum):
+ DIRECT = "m.direct"
+ IGNORED_USER_LIST = "m.ignored_user_list"
-class HistoryVisibility:
- INVITED: Final = "invited"
- JOINED: Final = "joined"
- SHARED: Final = "shared"
- WORLD_READABLE: Final = "world_readable"
+class HistoryVisibility(StrEnum):
+ INVITED = "invited"
+ JOINED = "joined"
+ SHARED = "shared"
+ WORLD_READABLE = "world_readable"
-class GuestAccess:
- CAN_JOIN: Final = "can_join"
+class GuestAccess(StrEnum):
+ CAN_JOIN = "can_join"
# anything that is not "can_join" is considered "forbidden", but for completeness:
- FORBIDDEN: Final = "forbidden"
+ FORBIDDEN = "forbidden"
-class ReadReceiptEventFields:
- MSC2285_HIDDEN: Final = "org.matrix.msc2285.hidden"
+class ReadReceiptEventFields(StrEnum):
+ MSC2285_HIDDEN = "org.matrix.msc2285.hidden"
diff --git a/synapse/event_auth.py b/synapse/event_auth.py
index e885961698..39fb4a3641 100644
--- a/synapse/event_auth.py
+++ b/synapse/event_auth.py
@@ -840,7 +840,7 @@ def get_public_keys(invite_event: EventBase) -> List[Dict[str, Any]]:
def auth_types_for_event(
room_version: RoomVersion, event: Union[EventBase, EventBuilder]
-) -> Set[Tuple[str, str]]:
+) -> Set[Tuple[EventTypes, str]]:
"""Given an event, return a list of (EventType, StateKey) that may be
needed to auth the event. The returned list may be a superset of what
would actually be required depending on the full state of the room.
diff --git a/synapse/events/validator.py b/synapse/events/validator.py
index cf86934968..f6fa96ba3c 100644
--- a/synapse/events/validator.py
+++ b/synapse/events/validator.py
@@ -189,7 +189,9 @@ class EventValidator:
if "membership" not in event.content:
raise SynapseError(400, "Content has not membership key")
- if event.content["membership"] not in Membership.LIST:
+ try:
+ Membership(event.content["membership"])
+ except ValueError:
raise SynapseError(400, "Invalid membership key")
self._ensure_state_event(event)
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
index b62e13b725..ab734134ee 100644
--- a/synapse/handlers/auth.py
+++ b/synapse/handlers/auth.py
@@ -28,6 +28,7 @@ from typing import (
List,
Mapping,
Optional,
+ Set,
Tuple,
Type,
Union,
@@ -382,7 +383,7 @@ class AuthHandler:
async def _get_available_ui_auth_types(self, user: UserID) -> Iterable[str]:
"""Get a list of the authentication types this user can use"""
- ui_auth_types = set()
+ ui_auth_types: Set[str] = set()
# if the HS supports password auth, and the user has a non-null password, we
# support password auth
@@ -735,7 +736,7 @@ class AuthHandler:
for f in flows:
public_flows.append(f)
- get_params = {
+ get_params: Dict[str, Any] = {
LoginType.RECAPTCHA: self._get_params_recaptcha,
LoginType.TERMS: self._get_params_terms,
}
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 3112cc88b1..4f543973f2 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -686,7 +686,7 @@ class FederationHandler:
)
raise NotFoundError("Not an active room on this server")
- event_content = {"membership": Membership.JOIN}
+ event_content: JsonDict = {"membership": Membership.JOIN}
# If the current room is using restricted join rules, additional information
# may need to be included in the event content in order to efficiently
diff --git a/synapse/push/bulk_push_rule_evaluator.py b/synapse/push/bulk_push_rule_evaluator.py
index 009d8e77b0..86377fbb1d 100644
--- a/synapse/push/bulk_push_rule_evaluator.py
+++ b/synapse/push/bulk_push_rule_evaluator.py
@@ -24,6 +24,7 @@ from synapse.event_auth import get_user_power_level
from synapse.events import EventBase
from synapse.events.snapshot import EventContext
from synapse.state import POWER_KEY
+from synapse.types import StateMap
from synapse.util.async_helpers import Linearizer
from synapse.util.caches import CacheMetric, register_cache
from synapse.util.caches.descriptors import lru_cache
@@ -170,7 +171,9 @@ class BulkPushRuleEvaluator:
if pl_event_id:
# fastpath: if there's a power level event, that's all we need, and
# not having a power level event is an extreme edge case
- auth_events = {POWER_KEY: await self.store.get_event(pl_event_id)}
+ auth_events: StateMap[EventBase] = {
+ POWER_KEY: await self.store.get_event(pl_event_id)
+ }
else:
auth_events_ids = self._event_auth_handler.compute_auth_events(
event, prev_state_ids, for_verification=False
diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py
index 23a8bf1fdb..3f78225882 100644
--- a/synapse/rest/admin/users.py
+++ b/synapse/rest/admin/users.py
@@ -209,8 +209,11 @@ class UserRestServletV2(RestServlet):
assert_params_in_dict(external_id, ["auth_provider", "external_id"])
user_type = body.get("user_type", None)
- if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
- raise SynapseError(400, "Invalid user type")
+ if user_type is not None:
+ try:
+ UserTypes(user_type)
+ except ValueError:
+ raise SynapseError(400, "Invalid user type")
set_admin_to = body.get("admin", False)
if not isinstance(set_admin_to, bool):
@@ -481,8 +484,11 @@ class UserRegisterServlet(RestServlet):
user_type = body.get("user_type", None)
displayname = body.get("displayname", None)
- if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
- raise SynapseError(400, "Invalid user type")
+ if user_type is not None:
+ try:
+ UserTypes(user_type)
+ except ValueError:
+ raise SynapseError(400, "Invalid user type")
if "mac" not in body:
raise SynapseError(400, "mac must be specified", errcode=Codes.BAD_JSON)
diff --git a/synapse/rest/client/register.py b/synapse/rest/client/register.py
index bf3cb34146..9293f4f3d5 100644
--- a/synapse/rest/client/register.py
+++ b/synapse/rest/client/register.py
@@ -876,7 +876,7 @@ def _calculate_registration_flows(
"validation is not configured"
)
- flows = []
+ flows: List[List[str]] = []
# only support 3PIDless registration if no 3PIDs are required
if not require_email and not require_msisdn:
diff --git a/synapse/state/v1.py b/synapse/state/v1.py
index 6edadea550..bfa676d1ac 100644
--- a/synapse/state/v1.py
+++ b/synapse/state/v1.py
@@ -204,7 +204,7 @@ def _create_auth_events_from_maps(
Returns:
A map from state key to event id.
"""
- auth_events = {}
+ auth_events: MutableStateMap[str] = {}
for event_ids in conflicted_state.values():
for event_id in event_ids:
if event_id in state_map:
@@ -269,7 +269,7 @@ def _resolve_state_events(
3. memberships
4. other events.
"""
- resolved_state = {}
+ resolved_state: MutableStateMap[EventBase] = {}
if POWER_KEY in conflicted_state:
events = conflicted_state[POWER_KEY]
logger.debug("Resolving conflicted power levels %r", events)
@@ -316,7 +316,7 @@ def _resolve_auth_events(
for key in event_auth.auth_types_for_event(room_version, event)
}
- new_auth_events = {}
+ new_auth_events: MutableStateMap[EventBase] = {}
for key in auth_keys:
auth_event = auth_events.get(key, None)
if auth_event:
diff --git a/synapse/util/enum.py b/synapse/util/enum.py
new file mode 100644
index 0000000000..c7a7d18437
--- /dev/null
+++ b/synapse/util/enum.py
@@ -0,0 +1,27 @@
+# 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 enum import Enum
+
+
+class StrEnum(str, Enum):
+ """Enum where members are also (and must be) strings
+
+ Similar to `IntEnum` except for strings.
+ Comparison and JSON serialization work as expected.
+ Interchangeable with regular strings when used as dictionary keys.
+ """
+
+ __str__ = str.__str__
+ __format__ = str.__format__
|