Correct `check_username_for_spam` annotations and docs (#12246)
* Formally type the UserProfile in user searches * export UserProfile in synapse.module_api * Update docs Co-authored-by: Sean Quah <8349537+squahtx@users.noreply.github.com>pull/12250/head
parent
12d1f82db2
commit
872dbb0181
|
@ -0,0 +1 @@
|
||||||
|
Correct `check_username_for_spam` annotations and docs.
|
|
@ -172,7 +172,7 @@ any of the subsequent implementations of this callback.
|
||||||
_First introduced in Synapse v1.37.0_
|
_First introduced in Synapse v1.37.0_
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def check_username_for_spam(user_profile: Dict[str, str]) -> bool
|
async def check_username_for_spam(user_profile: synapse.module_api.UserProfile) -> bool
|
||||||
```
|
```
|
||||||
|
|
||||||
Called when computing search results in the user directory. The module must return a
|
Called when computing search results in the user directory. The module must return a
|
||||||
|
@ -182,9 +182,11 @@ search results; otherwise return `False`.
|
||||||
|
|
||||||
The profile is represented as a dictionary with the following keys:
|
The profile is represented as a dictionary with the following keys:
|
||||||
|
|
||||||
* `user_id`: The Matrix ID for this user.
|
* `user_id: str`. The Matrix ID for this user.
|
||||||
* `display_name`: The user's display name.
|
* `display_name: Optional[str]`. The user's display name, or `None` if this user
|
||||||
* `avatar_url`: The `mxc://` URL to the user's avatar.
|
has not set a display name.
|
||||||
|
* `avatar_url: Optional[str]`. The `mxc://` URL to the user's avatar, or `None`
|
||||||
|
if this user has not set an avatar.
|
||||||
|
|
||||||
The module is given a copy of the original dictionary, so modifying it from within the
|
The module is given a copy of the original dictionary, so modifying it from within the
|
||||||
module cannot modify a user's profile when included in user directory search results.
|
module cannot modify a user's profile when included in user directory search results.
|
||||||
|
|
|
@ -21,7 +21,6 @@ from typing import (
|
||||||
Awaitable,
|
Awaitable,
|
||||||
Callable,
|
Callable,
|
||||||
Collection,
|
Collection,
|
||||||
Dict,
|
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
@ -31,7 +30,7 @@ from typing import (
|
||||||
from synapse.rest.media.v1._base import FileInfo
|
from synapse.rest.media.v1._base import FileInfo
|
||||||
from synapse.rest.media.v1.media_storage import ReadableFileWrapper
|
from synapse.rest.media.v1.media_storage import ReadableFileWrapper
|
||||||
from synapse.spam_checker_api import RegistrationBehaviour
|
from synapse.spam_checker_api import RegistrationBehaviour
|
||||||
from synapse.types import RoomAlias
|
from synapse.types import RoomAlias, UserProfile
|
||||||
from synapse.util.async_helpers import maybe_awaitable
|
from synapse.util.async_helpers import maybe_awaitable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -50,7 +49,7 @@ USER_MAY_SEND_3PID_INVITE_CALLBACK = Callable[[str, str, str, str], Awaitable[bo
|
||||||
USER_MAY_CREATE_ROOM_CALLBACK = Callable[[str], Awaitable[bool]]
|
USER_MAY_CREATE_ROOM_CALLBACK = Callable[[str], Awaitable[bool]]
|
||||||
USER_MAY_CREATE_ROOM_ALIAS_CALLBACK = Callable[[str, RoomAlias], Awaitable[bool]]
|
USER_MAY_CREATE_ROOM_ALIAS_CALLBACK = Callable[[str, RoomAlias], Awaitable[bool]]
|
||||||
USER_MAY_PUBLISH_ROOM_CALLBACK = Callable[[str, str], Awaitable[bool]]
|
USER_MAY_PUBLISH_ROOM_CALLBACK = Callable[[str, str], Awaitable[bool]]
|
||||||
CHECK_USERNAME_FOR_SPAM_CALLBACK = Callable[[Dict[str, str]], Awaitable[bool]]
|
CHECK_USERNAME_FOR_SPAM_CALLBACK = Callable[[UserProfile], Awaitable[bool]]
|
||||||
LEGACY_CHECK_REGISTRATION_FOR_SPAM_CALLBACK = Callable[
|
LEGACY_CHECK_REGISTRATION_FOR_SPAM_CALLBACK = Callable[
|
||||||
[
|
[
|
||||||
Optional[dict],
|
Optional[dict],
|
||||||
|
@ -383,7 +382,7 @@ class SpamChecker:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
|
async def check_username_for_spam(self, user_profile: UserProfile) -> bool:
|
||||||
"""Checks if a user ID or display name are considered "spammy" by this server.
|
"""Checks if a user ID or display name are considered "spammy" by this server.
|
||||||
|
|
||||||
If the server considers a username spammy, then it will not be included in
|
If the server considers a username spammy, then it will not be included in
|
||||||
|
|
|
@ -19,8 +19,8 @@ import synapse.metrics
|
||||||
from synapse.api.constants import EventTypes, HistoryVisibility, JoinRules, Membership
|
from synapse.api.constants import EventTypes, HistoryVisibility, JoinRules, Membership
|
||||||
from synapse.handlers.state_deltas import MatchChange, StateDeltasHandler
|
from synapse.handlers.state_deltas import MatchChange, StateDeltasHandler
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
|
from synapse.storage.databases.main.user_directory import SearchResult
|
||||||
from synapse.storage.roommember import ProfileInfo
|
from synapse.storage.roommember import ProfileInfo
|
||||||
from synapse.types import JsonDict
|
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -78,7 +78,7 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||||
|
|
||||||
async def search_users(
|
async def search_users(
|
||||||
self, user_id: str, search_term: str, limit: int
|
self, user_id: str, search_term: str, limit: int
|
||||||
) -> JsonDict:
|
) -> SearchResult:
|
||||||
"""Searches for users in directory
|
"""Searches for users in directory
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
@ -111,6 +111,7 @@ from synapse.types import (
|
||||||
StateMap,
|
StateMap,
|
||||||
UserID,
|
UserID,
|
||||||
UserInfo,
|
UserInfo,
|
||||||
|
UserProfile,
|
||||||
create_requester,
|
create_requester,
|
||||||
)
|
)
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
|
@ -150,6 +151,7 @@ __all__ = [
|
||||||
"EventBase",
|
"EventBase",
|
||||||
"StateMap",
|
"StateMap",
|
||||||
"ProfileInfo",
|
"ProfileInfo",
|
||||||
|
"UserProfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -19,7 +19,7 @@ from synapse.api.errors import SynapseError
|
||||||
from synapse.http.server import HttpServer
|
from synapse.http.server import HttpServer
|
||||||
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
from synapse.types import JsonDict
|
from synapse.types import JsonMapping
|
||||||
|
|
||||||
from ._base import client_patterns
|
from ._base import client_patterns
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class UserDirectorySearchRestServlet(RestServlet):
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
self.user_directory_handler = hs.get_user_directory_handler()
|
self.user_directory_handler = hs.get_user_directory_handler()
|
||||||
|
|
||||||
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonMapping]:
|
||||||
"""Searches for users in directory
|
"""Searches for users in directory
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
@ -26,6 +26,8 @@ from typing import (
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from synapse.api.errors import StoreError
|
from synapse.api.errors import StoreError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -40,7 +42,12 @@ from synapse.storage.database import (
|
||||||
from synapse.storage.databases.main.state import StateFilter
|
from synapse.storage.databases.main.state import StateFilter
|
||||||
from synapse.storage.databases.main.state_deltas import StateDeltasStore
|
from synapse.storage.databases.main.state_deltas import StateDeltasStore
|
||||||
from synapse.storage.engines import PostgresEngine, Sqlite3Engine
|
from synapse.storage.engines import PostgresEngine, Sqlite3Engine
|
||||||
from synapse.types import JsonDict, get_domain_from_id, get_localpart_from_id
|
from synapse.types import (
|
||||||
|
JsonDict,
|
||||||
|
UserProfile,
|
||||||
|
get_domain_from_id,
|
||||||
|
get_localpart_from_id,
|
||||||
|
)
|
||||||
from synapse.util.caches.descriptors import cached
|
from synapse.util.caches.descriptors import cached
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -591,6 +598,11 @@ class UserDirectoryBackgroundUpdateStore(StateDeltasStore):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchResult(TypedDict):
|
||||||
|
limited: bool
|
||||||
|
results: List[UserProfile]
|
||||||
|
|
||||||
|
|
||||||
class UserDirectoryStore(UserDirectoryBackgroundUpdateStore):
|
class UserDirectoryStore(UserDirectoryBackgroundUpdateStore):
|
||||||
# How many records do we calculate before sending it to
|
# How many records do we calculate before sending it to
|
||||||
# add_users_who_share_private_rooms?
|
# add_users_who_share_private_rooms?
|
||||||
|
@ -777,7 +789,7 @@ class UserDirectoryStore(UserDirectoryBackgroundUpdateStore):
|
||||||
|
|
||||||
async def search_user_dir(
|
async def search_user_dir(
|
||||||
self, user_id: str, search_term: str, limit: int
|
self, user_id: str, search_term: str, limit: int
|
||||||
) -> JsonDict:
|
) -> SearchResult:
|
||||||
"""Searches for users in directory
|
"""Searches for users in directory
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -910,8 +922,11 @@ class UserDirectoryStore(UserDirectoryBackgroundUpdateStore):
|
||||||
# This should be unreachable.
|
# This should be unreachable.
|
||||||
raise Exception("Unrecognized database engine")
|
raise Exception("Unrecognized database engine")
|
||||||
|
|
||||||
results = await self.db_pool.execute(
|
results = cast(
|
||||||
"search_user_dir", self.db_pool.cursor_to_dict, sql, *args
|
List[UserProfile],
|
||||||
|
await self.db_pool.execute(
|
||||||
|
"search_user_dir", self.db_pool.cursor_to_dict, sql, *args
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
limited = len(results) > limit
|
limited = len(results) > limit
|
||||||
|
|
|
@ -34,6 +34,7 @@ from typing import (
|
||||||
import attr
|
import attr
|
||||||
from frozendict import frozendict
|
from frozendict import frozendict
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
|
from typing_extensions import TypedDict
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
|
|
||||||
|
@ -63,6 +64,10 @@ MutableStateMap = MutableMapping[StateKey, T]
|
||||||
# JSON types. These could be made stronger, but will do for now.
|
# JSON types. These could be made stronger, but will do for now.
|
||||||
# A JSON-serialisable dict.
|
# A JSON-serialisable dict.
|
||||||
JsonDict = Dict[str, Any]
|
JsonDict = Dict[str, Any]
|
||||||
|
# A JSON-serialisable mapping; roughly speaking an immutable JSONDict.
|
||||||
|
# Useful when you have a TypedDict which isn't going to be mutated and you don't want
|
||||||
|
# to cast to JsonDict everywhere.
|
||||||
|
JsonMapping = Mapping[str, Any]
|
||||||
# A JSON-serialisable object.
|
# A JSON-serialisable object.
|
||||||
JsonSerializable = object
|
JsonSerializable = object
|
||||||
|
|
||||||
|
@ -791,3 +796,9 @@ class UserInfo:
|
||||||
is_deactivated: bool
|
is_deactivated: bool
|
||||||
is_guest: bool
|
is_guest: bool
|
||||||
is_shadow_banned: bool
|
is_shadow_banned: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfile(TypedDict):
|
||||||
|
user_id: str
|
||||||
|
display_name: Optional[str]
|
||||||
|
avatar_url: Optional[str]
|
||||||
|
|
Loading…
Reference in New Issue