summary refs log tree commit diff
diff options
context:
space:
mode:
authorSean Quah <seanq@element.io>2021-11-12 19:57:42 +0000
committerSean Quah <seanq@element.io>2021-11-16 13:55:20 +0000
commitb43d085472390c77691fbbd86b3e66b4a1a0cb6d (patch)
tree8923cd324ddcfedfcbeae5dfb03f097157d8be1a
parentAdd newsfile (diff)
downloadsynapse-b43d085472390c77691fbbd86b3e66b4a1a0cb6d.tar.xz
Convert strings in `synapse.api.constants` to enums or `Final`
This change makes mypy type the constants as `Literal`s instead of
`str`s, allowing code of the following form to pass mypy:
```py
def do_something(
    membership: Literal[Membership.JOIN, Membership.LEAVE], ...
):
    ...

do_something(Membership.JOIN, ...)
```
-rw-r--r--changelog.d/11356.misc2
-rw-r--r--synapse/api/auth.py4
-rw-r--r--synapse/api/constants.py235
-rw-r--r--synapse/event_auth.py2
-rw-r--r--synapse/events/validator.py4
-rw-r--r--synapse/handlers/auth.py5
-rw-r--r--synapse/handlers/federation.py2
-rw-r--r--synapse/push/bulk_push_rule_evaluator.py5
-rw-r--r--synapse/rest/admin/users.py14
-rw-r--r--synapse/rest/client/register.py2
-rw-r--r--synapse/state/v1.py6
-rw-r--r--synapse/util/enum.py27
12 files changed, 173 insertions, 135 deletions
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__