mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2024-10-01 11:49:51 -04:00
Merge remote-tracking branch 'upstream/release-v1.60'
This commit is contained in:
commit
121950d34d
23
CHANGES.md
23
CHANGES.md
@ -1,4 +1,4 @@
|
|||||||
Synapse 1.60.0rc1 (2022-05-24)
|
Synapse 1.60.0rc2 (2022-05-27)
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
This release of Synapse adds a unique index to the `state_group_edges` table, in
|
This release of Synapse adds a unique index to the `state_group_edges` table, in
|
||||||
@ -17,6 +17,27 @@ for more details.
|
|||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
- Add an option allowing users to use their password to reauthenticate for privileged actions even though password login is disabled. ([\#12883](https://github.com/matrix-org/synapse/issues/12883))
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Explicitly close `ijson` coroutines once we are done with them, instead of leaving the garbage collector to close them. ([\#12875](https://github.com/matrix-org/synapse/issues/12875))
|
||||||
|
|
||||||
|
|
||||||
|
Internal Changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- Improve URL previews by not including the content of media tags in the generated description. ([\#12887](https://github.com/matrix-org/synapse/issues/12887))
|
||||||
|
|
||||||
|
|
||||||
|
Synapse 1.60.0rc1 (2022-05-24)
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
- Measure the time taken in spam-checking callbacks and expose those measurements as metrics. ([\#12513](https://github.com/matrix-org/synapse/issues/12513))
|
- Measure the time taken in spam-checking callbacks and expose those measurements as metrics. ([\#12513](https://github.com/matrix-org/synapse/issues/12513))
|
||||||
- Add a `default_power_level_content_override` config option to set default room power levels per room preset. ([\#12618](https://github.com/matrix-org/synapse/issues/12618))
|
- Add a `default_power_level_content_override` config option to set default room power levels per room preset. ([\#12618](https://github.com/matrix-org/synapse/issues/12618))
|
||||||
- Add support for [MSC3787: Allowing knocks to restricted rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/3787). ([\#12623](https://github.com/matrix-org/synapse/issues/12623))
|
- Add support for [MSC3787: Allowing knocks to restricted rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/3787). ([\#12623](https://github.com/matrix-org/synapse/issues/12623))
|
||||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
|||||||
|
matrix-synapse-py3 (1.60.0~rc2) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.60.0rc2.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Fri, 27 May 2022 11:04:55 +0100
|
||||||
|
|
||||||
matrix-synapse-py3 (1.60.0~rc1) stable; urgency=medium
|
matrix-synapse-py3 (1.60.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
* New Synapse release 1.60.0rc1.
|
* New Synapse release 1.60.0rc1.
|
||||||
|
@ -2216,7 +2216,9 @@ sso:
|
|||||||
|
|
||||||
|
|
||||||
password_config:
|
password_config:
|
||||||
# Uncomment to disable password login
|
# Uncomment to disable password login.
|
||||||
|
# Set to `only_for_reauth` to permit reauthentication for users that
|
||||||
|
# have passwords and are already logged in.
|
||||||
#
|
#
|
||||||
#enabled: false
|
#enabled: false
|
||||||
|
|
||||||
|
@ -2930,6 +2930,9 @@ Use this setting to enable password-based logins.
|
|||||||
|
|
||||||
This setting has the following sub-options:
|
This setting has the following sub-options:
|
||||||
* `enabled`: Defaults to true.
|
* `enabled`: Defaults to true.
|
||||||
|
Set to false to disable password authentication.
|
||||||
|
Set to `only_for_reauth` to allow users with existing passwords to use them
|
||||||
|
to log in and reauthenticate, whilst preventing new users from setting passwords.
|
||||||
* `localdb_enabled`: Set to false to disable authentication against the local password
|
* `localdb_enabled`: Set to false to disable authentication against the local password
|
||||||
database. This is ignored if `enabled` is false, and is only useful
|
database. This is ignored if `enabled` is false, and is only useful
|
||||||
if you have other `password_providers`. Defaults to true.
|
if you have other `password_providers`. Defaults to true.
|
||||||
|
@ -54,7 +54,7 @@ skip_gitignore = true
|
|||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "matrix-synapse"
|
name = "matrix-synapse"
|
||||||
version = "1.60.0rc1"
|
version = "1.60.0rc2"
|
||||||
description = "Homeserver for the Matrix decentralised comms protocol"
|
description = "Homeserver for the Matrix decentralised comms protocol"
|
||||||
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
@ -29,7 +29,18 @@ class AuthConfig(Config):
|
|||||||
if password_config is None:
|
if password_config is None:
|
||||||
password_config = {}
|
password_config = {}
|
||||||
|
|
||||||
self.password_enabled = password_config.get("enabled", True)
|
passwords_enabled = password_config.get("enabled", True)
|
||||||
|
# 'only_for_reauth' allows users who have previously set a password to use it,
|
||||||
|
# even though passwords would otherwise be disabled.
|
||||||
|
passwords_for_reauth_only = passwords_enabled == "only_for_reauth"
|
||||||
|
|
||||||
|
self.password_enabled_for_login = (
|
||||||
|
passwords_enabled and not passwords_for_reauth_only
|
||||||
|
)
|
||||||
|
self.password_enabled_for_reauth = (
|
||||||
|
passwords_for_reauth_only or passwords_enabled
|
||||||
|
)
|
||||||
|
|
||||||
self.password_localdb_enabled = password_config.get("localdb_enabled", True)
|
self.password_localdb_enabled = password_config.get("localdb_enabled", True)
|
||||||
self.password_pepper = password_config.get("pepper", "")
|
self.password_pepper = password_config.get("pepper", "")
|
||||||
|
|
||||||
@ -46,7 +57,9 @@ class AuthConfig(Config):
|
|||||||
def generate_config_section(self, **kwargs: Any) -> str:
|
def generate_config_section(self, **kwargs: Any) -> str:
|
||||||
return """\
|
return """\
|
||||||
password_config:
|
password_config:
|
||||||
# Uncomment to disable password login
|
# Uncomment to disable password login.
|
||||||
|
# Set to `only_for_reauth` to permit reauthentication for users that
|
||||||
|
# have passwords and are already logged in.
|
||||||
#
|
#
|
||||||
#enabled: false
|
#enabled: false
|
||||||
|
|
||||||
|
@ -1363,7 +1363,7 @@ class SendJoinParser(ByteParser[SendJoinResponse]):
|
|||||||
def __init__(self, room_version: RoomVersion, v1_api: bool):
|
def __init__(self, room_version: RoomVersion, v1_api: bool):
|
||||||
self._response = SendJoinResponse([], [], event_dict={})
|
self._response = SendJoinResponse([], [], event_dict={})
|
||||||
self._room_version = room_version
|
self._room_version = room_version
|
||||||
self._coros = []
|
self._coros: List[Generator[None, bytes, None]] = []
|
||||||
|
|
||||||
# The V1 API has the shape of `[200, {...}]`, which we handle by
|
# The V1 API has the shape of `[200, {...}]`, which we handle by
|
||||||
# prefixing with `item.*`.
|
# prefixing with `item.*`.
|
||||||
@ -1411,6 +1411,9 @@ class SendJoinParser(ByteParser[SendJoinResponse]):
|
|||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
def finish(self) -> SendJoinResponse:
|
def finish(self) -> SendJoinResponse:
|
||||||
|
for c in self._coros:
|
||||||
|
c.close()
|
||||||
|
|
||||||
if self._response.event_dict:
|
if self._response.event_dict:
|
||||||
self._response.event = make_event_from_dict(
|
self._response.event = make_event_from_dict(
|
||||||
self._response.event_dict, self._room_version
|
self._response.event_dict, self._room_version
|
||||||
@ -1430,7 +1433,7 @@ class _StateParser(ByteParser[StateRequestResponse]):
|
|||||||
def __init__(self, room_version: RoomVersion):
|
def __init__(self, room_version: RoomVersion):
|
||||||
self._response = StateRequestResponse([], [])
|
self._response = StateRequestResponse([], [])
|
||||||
self._room_version = room_version
|
self._room_version = room_version
|
||||||
self._coros = [
|
self._coros: List[Generator[None, bytes, None]] = [
|
||||||
ijson.items_coro(
|
ijson.items_coro(
|
||||||
_event_list_parser(room_version, self._response.state),
|
_event_list_parser(room_version, self._response.state),
|
||||||
"pdus.item",
|
"pdus.item",
|
||||||
@ -1449,4 +1452,6 @@ class _StateParser(ByteParser[StateRequestResponse]):
|
|||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
def finish(self) -> StateRequestResponse:
|
def finish(self) -> StateRequestResponse:
|
||||||
|
for c in self._coros:
|
||||||
|
c.close()
|
||||||
return self._response
|
return self._response
|
||||||
|
@ -210,7 +210,8 @@ class AuthHandler:
|
|||||||
|
|
||||||
self.hs = hs # FIXME better possibility to access registrationHandler later?
|
self.hs = hs # FIXME better possibility to access registrationHandler later?
|
||||||
self.macaroon_gen = hs.get_macaroon_generator()
|
self.macaroon_gen = hs.get_macaroon_generator()
|
||||||
self._password_enabled = hs.config.auth.password_enabled
|
self._password_enabled_for_login = hs.config.auth.password_enabled_for_login
|
||||||
|
self._password_enabled_for_reauth = hs.config.auth.password_enabled_for_reauth
|
||||||
self._password_localdb_enabled = hs.config.auth.password_localdb_enabled
|
self._password_localdb_enabled = hs.config.auth.password_localdb_enabled
|
||||||
self._third_party_rules = hs.get_third_party_event_rules()
|
self._third_party_rules = hs.get_third_party_event_rules()
|
||||||
|
|
||||||
@ -387,13 +388,13 @@ class AuthHandler:
|
|||||||
return params, session_id
|
return params, session_id
|
||||||
|
|
||||||
async def _get_available_ui_auth_types(self, user: UserID) -> Iterable[str]:
|
async def _get_available_ui_auth_types(self, user: UserID) -> Iterable[str]:
|
||||||
"""Get a list of the authentication types this user can use"""
|
"""Get a list of the user-interactive authentication types this user can use."""
|
||||||
|
|
||||||
ui_auth_types = set()
|
ui_auth_types = set()
|
||||||
|
|
||||||
# if the HS supports password auth, and the user has a non-null password, we
|
# if the HS supports password auth, and the user has a non-null password, we
|
||||||
# support password auth
|
# support password auth
|
||||||
if self._password_localdb_enabled and self._password_enabled:
|
if self._password_localdb_enabled and self._password_enabled_for_reauth:
|
||||||
lookupres = await self._find_user_id_and_pwd_hash(user.to_string())
|
lookupres = await self._find_user_id_and_pwd_hash(user.to_string())
|
||||||
if lookupres:
|
if lookupres:
|
||||||
_, password_hash = lookupres
|
_, password_hash = lookupres
|
||||||
@ -402,7 +403,7 @@ class AuthHandler:
|
|||||||
|
|
||||||
# also allow auth from password providers
|
# also allow auth from password providers
|
||||||
for t in self.password_auth_provider.get_supported_login_types().keys():
|
for t in self.password_auth_provider.get_supported_login_types().keys():
|
||||||
if t == LoginType.PASSWORD and not self._password_enabled:
|
if t == LoginType.PASSWORD and not self._password_enabled_for_reauth:
|
||||||
continue
|
continue
|
||||||
ui_auth_types.add(t)
|
ui_auth_types.add(t)
|
||||||
|
|
||||||
@ -710,7 +711,7 @@ class AuthHandler:
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
# fall back to the v1 login flow
|
# fall back to the v1 login flow
|
||||||
canonical_id, _ = await self.validate_login(authdict)
|
canonical_id, _ = await self.validate_login(authdict, is_reauth=True)
|
||||||
return canonical_id
|
return canonical_id
|
||||||
|
|
||||||
def _get_params_recaptcha(self) -> dict:
|
def _get_params_recaptcha(self) -> dict:
|
||||||
@ -1064,7 +1065,7 @@ class AuthHandler:
|
|||||||
Returns:
|
Returns:
|
||||||
Whether users on this server are allowed to change or set a password
|
Whether users on this server are allowed to change or set a password
|
||||||
"""
|
"""
|
||||||
return self._password_enabled and self._password_localdb_enabled
|
return self._password_enabled_for_login and self._password_localdb_enabled
|
||||||
|
|
||||||
def get_supported_login_types(self) -> Iterable[str]:
|
def get_supported_login_types(self) -> Iterable[str]:
|
||||||
"""Get a the login types supported for the /login API
|
"""Get a the login types supported for the /login API
|
||||||
@ -1089,9 +1090,9 @@ class AuthHandler:
|
|||||||
# that comes first, where it's present.
|
# that comes first, where it's present.
|
||||||
if LoginType.PASSWORD in types:
|
if LoginType.PASSWORD in types:
|
||||||
types.remove(LoginType.PASSWORD)
|
types.remove(LoginType.PASSWORD)
|
||||||
if self._password_enabled:
|
if self._password_enabled_for_login:
|
||||||
types.insert(0, LoginType.PASSWORD)
|
types.insert(0, LoginType.PASSWORD)
|
||||||
elif self._password_localdb_enabled and self._password_enabled:
|
elif self._password_localdb_enabled and self._password_enabled_for_login:
|
||||||
types.insert(0, LoginType.PASSWORD)
|
types.insert(0, LoginType.PASSWORD)
|
||||||
|
|
||||||
return types
|
return types
|
||||||
@ -1100,6 +1101,7 @@ class AuthHandler:
|
|||||||
self,
|
self,
|
||||||
login_submission: Dict[str, Any],
|
login_submission: Dict[str, Any],
|
||||||
ratelimit: bool = False,
|
ratelimit: bool = False,
|
||||||
|
is_reauth: bool = False,
|
||||||
) -> Tuple[str, Optional[Callable[["LoginResponse"], Awaitable[None]]]]:
|
) -> Tuple[str, Optional[Callable[["LoginResponse"], Awaitable[None]]]]:
|
||||||
"""Authenticates the user for the /login API
|
"""Authenticates the user for the /login API
|
||||||
|
|
||||||
@ -1110,6 +1112,9 @@ class AuthHandler:
|
|||||||
login_submission: the whole of the login submission
|
login_submission: the whole of the login submission
|
||||||
(including 'type' and other relevant fields)
|
(including 'type' and other relevant fields)
|
||||||
ratelimit: whether to apply the failed_login_attempt ratelimiter
|
ratelimit: whether to apply the failed_login_attempt ratelimiter
|
||||||
|
is_reauth: whether this is part of a User-Interactive Authorisation
|
||||||
|
flow to reauthenticate for a privileged action (rather than a
|
||||||
|
new login)
|
||||||
Returns:
|
Returns:
|
||||||
A tuple of the canonical user id, and optional callback
|
A tuple of the canonical user id, and optional callback
|
||||||
to be called once the access token and device id are issued
|
to be called once the access token and device id are issued
|
||||||
@ -1132,8 +1137,14 @@ class AuthHandler:
|
|||||||
# special case to check for "password" for the check_password interface
|
# special case to check for "password" for the check_password interface
|
||||||
# for the auth providers
|
# for the auth providers
|
||||||
password = login_submission.get("password")
|
password = login_submission.get("password")
|
||||||
|
|
||||||
if login_type == LoginType.PASSWORD:
|
if login_type == LoginType.PASSWORD:
|
||||||
if not self._password_enabled:
|
if is_reauth:
|
||||||
|
passwords_allowed_here = self._password_enabled_for_reauth
|
||||||
|
else:
|
||||||
|
passwords_allowed_here = self._password_enabled_for_login
|
||||||
|
|
||||||
|
if not passwords_allowed_here:
|
||||||
raise SynapseError(400, "Password login has been disabled.")
|
raise SynapseError(400, "Password login has been disabled.")
|
||||||
if not isinstance(password, str):
|
if not isinstance(password, str):
|
||||||
raise SynapseError(400, "Bad parameter: password", Codes.INVALID_PARAM)
|
raise SynapseError(400, "Bad parameter: password", Codes.INVALID_PARAM)
|
||||||
|
@ -225,6 +225,7 @@ async def _handle_response(
|
|||||||
if max_response_size is None:
|
if max_response_size is None:
|
||||||
max_response_size = MAX_RESPONSE_SIZE
|
max_response_size = MAX_RESPONSE_SIZE
|
||||||
|
|
||||||
|
finished = False
|
||||||
try:
|
try:
|
||||||
check_content_type_is(response.headers, parser.CONTENT_TYPE)
|
check_content_type_is(response.headers, parser.CONTENT_TYPE)
|
||||||
|
|
||||||
@ -233,6 +234,7 @@ async def _handle_response(
|
|||||||
|
|
||||||
length = await make_deferred_yieldable(d)
|
length = await make_deferred_yieldable(d)
|
||||||
|
|
||||||
|
finished = True
|
||||||
value = parser.finish()
|
value = parser.finish()
|
||||||
except BodyExceededMaxSize as e:
|
except BodyExceededMaxSize as e:
|
||||||
# The response was too big.
|
# The response was too big.
|
||||||
@ -283,6 +285,15 @@ async def _handle_response(
|
|||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
if not finished:
|
||||||
|
# There was an exception and we didn't `finish()` the parse.
|
||||||
|
# Let the parser know that it can free up any resources.
|
||||||
|
try:
|
||||||
|
parser.finish()
|
||||||
|
except Exception:
|
||||||
|
# Ignore any additional exceptions.
|
||||||
|
pass
|
||||||
|
|
||||||
time_taken_secs = reactor.seconds() - start_ms / 1000
|
time_taken_secs = reactor.seconds() - start_ms / 1000
|
||||||
|
|
||||||
|
@ -246,7 +246,9 @@ def parse_html_description(tree: "etree.Element") -> Optional[str]:
|
|||||||
|
|
||||||
Grabs any text nodes which are inside the <body/> tag, unless they are within
|
Grabs any text nodes which are inside the <body/> tag, unless they are within
|
||||||
an HTML5 semantic markup tag (<header/>, <nav/>, <aside/>, <footer/>), or
|
an HTML5 semantic markup tag (<header/>, <nav/>, <aside/>, <footer/>), or
|
||||||
if they are within a <script/> or <style/> tag.
|
if they are within a <script/>, <svg/> or <style/> tag, or if they are within
|
||||||
|
a tag whose content is usually only shown to old browsers
|
||||||
|
(<iframe/>, <video/>, <canvas/>, <picture/>).
|
||||||
|
|
||||||
This is a very very very coarse approximation to a plain text render of the page.
|
This is a very very very coarse approximation to a plain text render of the page.
|
||||||
|
|
||||||
@ -268,6 +270,12 @@ def parse_html_description(tree: "etree.Element") -> Optional[str]:
|
|||||||
"script",
|
"script",
|
||||||
"noscript",
|
"noscript",
|
||||||
"style",
|
"style",
|
||||||
|
"svg",
|
||||||
|
"iframe",
|
||||||
|
"video",
|
||||||
|
"canvas",
|
||||||
|
"img",
|
||||||
|
"picture",
|
||||||
etree.Comment,
|
etree.Comment,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -195,8 +195,17 @@ class UIAuthTests(unittest.HomeserverTestCase):
|
|||||||
self.user_pass = "pass"
|
self.user_pass = "pass"
|
||||||
self.user = self.register_user("test", self.user_pass)
|
self.user = self.register_user("test", self.user_pass)
|
||||||
self.device_id = "dev1"
|
self.device_id = "dev1"
|
||||||
|
|
||||||
|
# Force-enable password login for just long enough to log in.
|
||||||
|
auth_handler = self.hs.get_auth_handler()
|
||||||
|
allow_auth_for_login = auth_handler._password_enabled_for_login
|
||||||
|
auth_handler._password_enabled_for_login = True
|
||||||
|
|
||||||
self.user_tok = self.login("test", self.user_pass, self.device_id)
|
self.user_tok = self.login("test", self.user_pass, self.device_id)
|
||||||
|
|
||||||
|
# Restore password login to however it was.
|
||||||
|
auth_handler._password_enabled_for_login = allow_auth_for_login
|
||||||
|
|
||||||
def delete_device(
|
def delete_device(
|
||||||
self,
|
self,
|
||||||
access_token: str,
|
access_token: str,
|
||||||
@ -263,6 +272,38 @@ class UIAuthTests(unittest.HomeserverTestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_config({"password_config": {"enabled": "only_for_reauth"}})
|
||||||
|
def test_ui_auth_with_passwords_for_reauth_only(self) -> None:
|
||||||
|
"""
|
||||||
|
Test user interactive authentication outside of registration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Attempt to delete this device.
|
||||||
|
# Returns a 401 as per the spec
|
||||||
|
channel = self.delete_device(
|
||||||
|
self.user_tok, self.device_id, HTTPStatus.UNAUTHORIZED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grab the session
|
||||||
|
session = channel.json_body["session"]
|
||||||
|
# Ensure that flows are what is expected.
|
||||||
|
self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
|
||||||
|
|
||||||
|
# Make another request providing the UI auth flow.
|
||||||
|
self.delete_device(
|
||||||
|
self.user_tok,
|
||||||
|
self.device_id,
|
||||||
|
HTTPStatus.OK,
|
||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"type": "m.login.password",
|
||||||
|
"identifier": {"type": "m.id.user", "user": self.user},
|
||||||
|
"password": self.user_pass,
|
||||||
|
"session": session,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_grandfathered_identifier(self) -> None:
|
def test_grandfathered_identifier(self) -> None:
|
||||||
"""Check behaviour without "identifier" dict
|
"""Check behaviour without "identifier" dict
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user