forked from MirrorHub/synapse
Merge branch 'develop' into babolivier/mark_unread
This commit is contained in:
commit
c2b4621630
12
CHANGES.md
12
CHANGES.md
|
@ -1,3 +1,9 @@
|
|||
Synapse 1.15.0 (2020-06-11)
|
||||
===========================
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
Synapse 1.15.0rc1 (2020-06-09)
|
||||
==============================
|
||||
|
||||
|
@ -8,14 +14,14 @@ Features
|
|||
- Add an option to disable autojoining rooms for guest accounts. ([\#6637](https://github.com/matrix-org/synapse/issues/6637))
|
||||
- For SAML authentication, add the ability to pass email addresses to be added to new users' accounts via SAML attributes. Contributed by Christopher Cooper. ([\#7385](https://github.com/matrix-org/synapse/issues/7385))
|
||||
- Add admin APIs to allow server admins to manage users' devices. Contributed by @dklimpel. ([\#7481](https://github.com/matrix-org/synapse/issues/7481))
|
||||
- Add support for generating thumbnails for WebP images. Previously, users would see an empty box instead of preview image. ([\#7586](https://github.com/matrix-org/synapse/issues/7586))
|
||||
- Add support for generating thumbnails for WebP images. Previously, users would see an empty box instead of preview image. Contributed by @WGH-. ([\#7586](https://github.com/matrix-org/synapse/issues/7586))
|
||||
- Support the standardized `m.login.sso` user-interactive authentication flow. ([\#7630](https://github.com/matrix-org/synapse/issues/7630))
|
||||
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Allow new users to be registered via the admin API even if the monthly active user limit has been reached. Contributed by @dkimpel. ([\#7263](https://github.com/matrix-org/synapse/issues/7263))
|
||||
- Allow new users to be registered via the admin API even if the monthly active user limit has been reached. Contributed by @dklimpel. ([\#7263](https://github.com/matrix-org/synapse/issues/7263))
|
||||
- Fix email notifications not being enabled for new users when created via the Admin API. ([\#7267](https://github.com/matrix-org/synapse/issues/7267))
|
||||
- Fix str placeholders in an instance of `PrepareDatabaseException`. Introduced in Synapse v1.8.0. ([\#7575](https://github.com/matrix-org/synapse/issues/7575))
|
||||
- Fix a bug in automatic user creation during first time login with `m.login.jwt`. Regression in v1.6.0. Contributed by @olof. ([\#7585](https://github.com/matrix-org/synapse/issues/7585))
|
||||
|
@ -44,7 +50,7 @@ Internal Changes
|
|||
----------------
|
||||
|
||||
- Convert the identity handler to async/await. ([\#7561](https://github.com/matrix-org/synapse/issues/7561))
|
||||
- Improve query performance for fetching state from a PostgreSQL database. ([\#7567](https://github.com/matrix-org/synapse/issues/7567))
|
||||
- Improve query performance for fetching state from a PostgreSQL database. Contributed by @ilmari. ([\#7567](https://github.com/matrix-org/synapse/issues/7567))
|
||||
- Speed up processing of federation stream RDATA rows. ([\#7584](https://github.com/matrix-org/synapse/issues/7584))
|
||||
- Add comment to systemd example to show postgresql dependency. ([\#7591](https://github.com/matrix-org/synapse/issues/7591))
|
||||
- Refactor `Ratelimiter` to limit the amount of expensive config value accesses. ([\#7595](https://github.com/matrix-org/synapse/issues/7595))
|
||||
|
|
1
changelog.d/7606.bugfix
Normal file
1
changelog.d/7606.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Remove `user_id` from the response to `GET /_matrix/client/r0/presence/{userId}/status` to match the specification.
|
1
changelog.d/7639.feature
Normal file
1
changelog.d/7639.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add an option to enable encryption by default for new rooms.
|
1
changelog.d/7652.doc
Normal file
1
changelog.d/7652.doc
Normal file
|
@ -0,0 +1 @@
|
|||
Spelling correction in sample_config.yaml.
|
1
changelog.d/7657.misc
Normal file
1
changelog.d/7657.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Clean-up the login fallback code.
|
1
changelog.d/7663.bugfix
Normal file
1
changelog.d/7663.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix intermittent exception during startup, introduced in Synapse 1.14.0.
|
1
changelog.d/7664.misc
Normal file
1
changelog.d/7664.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Increase the default SAML session expirary time to 15 minutes.
|
6
debian/changelog
vendored
6
debian/changelog
vendored
|
@ -1,3 +1,9 @@
|
|||
matrix-synapse-py3 (1.15.0) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.15.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 11 Jun 2020 13:27:06 +0100
|
||||
|
||||
matrix-synapse-py3 (1.14.0) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.14.0.
|
||||
|
|
|
@ -283,7 +283,7 @@ listeners:
|
|||
# number of monthly active users.
|
||||
#
|
||||
# 'limit_usage_by_mau' disables/enables monthly active user blocking. When
|
||||
# anabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||
# enabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||
# with error type Codes.RESOURCE_LIMIT_EXCEEDED
|
||||
#
|
||||
# 'max_mau_value' is the hard limit of monthly active users above which
|
||||
|
@ -1454,7 +1454,7 @@ saml2_config:
|
|||
|
||||
# The lifetime of a SAML session. This defines how long a user has to
|
||||
# complete the authentication process, if allow_unsolicited is unset.
|
||||
# The default is 5 minutes.
|
||||
# The default is 15 minutes.
|
||||
#
|
||||
#saml_session_lifetime: 5m
|
||||
|
||||
|
@ -1973,6 +1973,26 @@ spam_checker:
|
|||
# example_stop_events_from: ['@bad:example.com']
|
||||
|
||||
|
||||
## Rooms ##
|
||||
|
||||
# Controls whether locally-created rooms should be end-to-end encrypted by
|
||||
# default.
|
||||
#
|
||||
# Possible options are "all", "invite", and "off". They are defined as:
|
||||
#
|
||||
# * "all": any locally-created room
|
||||
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
|
||||
# room creation presets
|
||||
# * "off": this option will take no effect
|
||||
#
|
||||
# The default value is "off".
|
||||
#
|
||||
# Note that this option will only affect rooms created after it is set. It
|
||||
# will also not affect rooms created by other servers.
|
||||
#
|
||||
#encryption_enabled_by_default_for_room_type: invite
|
||||
|
||||
|
||||
# Uncomment to allow non-server-admin users to create groups on this server
|
||||
#
|
||||
#enable_group_creation: true
|
||||
|
|
|
@ -36,7 +36,7 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "1.15.0rc1"
|
||||
__version__ = "1.15.0"
|
||||
|
||||
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
||||
# We import here so that we don't have to install a bunch of deps when
|
||||
|
|
|
@ -150,3 +150,8 @@ class EventContentFields(object):
|
|||
# Timestamp to delete the event after
|
||||
# cf https://github.com/matrix-org/matrix-doc/pull/2228
|
||||
SELF_DESTRUCT_AFTER = "org.matrix.self_destruct_after"
|
||||
|
||||
|
||||
class RoomEncryptionAlgorithms(object):
|
||||
MEGOLM_V1_AES_SHA2 = "m.megolm.v1.aes-sha2"
|
||||
DEFAULT = MEGOLM_V1_AES_SHA2
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
from typing import Callable, Dict
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
|
@ -25,6 +26,9 @@ _CACHE_PREFIX = "SYNAPSE_CACHE_FACTOR"
|
|||
# Map from canonicalised cache name to cache.
|
||||
_CACHES = {}
|
||||
|
||||
# a lock on the contents of _CACHES
|
||||
_CACHES_LOCK = threading.Lock()
|
||||
|
||||
_DEFAULT_FACTOR_SIZE = 0.5
|
||||
_DEFAULT_EVENT_CACHE_SIZE = "10K"
|
||||
|
||||
|
@ -66,7 +70,10 @@ def add_resizable_cache(cache_name: str, cache_resize_callback: Callable):
|
|||
# Some caches have '*' in them which we strip out.
|
||||
cache_name = _canonicalise_cache_name(cache_name)
|
||||
|
||||
_CACHES[cache_name] = cache_resize_callback
|
||||
# sometimes caches are initialised from background threads, so we need to make
|
||||
# sure we don't conflict with another thread running a resize operation
|
||||
with _CACHES_LOCK:
|
||||
_CACHES[cache_name] = cache_resize_callback
|
||||
|
||||
# Ensure all loaded caches are sized appropriately
|
||||
#
|
||||
|
@ -87,7 +94,8 @@ class CacheConfig(Config):
|
|||
os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
|
||||
)
|
||||
properties.resize_all_caches_func = None
|
||||
_CACHES.clear()
|
||||
with _CACHES_LOCK:
|
||||
_CACHES.clear()
|
||||
|
||||
def generate_config_section(self, **kwargs):
|
||||
return """\
|
||||
|
@ -193,6 +201,8 @@ class CacheConfig(Config):
|
|||
For each cache, run the mapped callback function with either
|
||||
a specific cache factor or the default, global one.
|
||||
"""
|
||||
for cache_name, callback in _CACHES.items():
|
||||
new_factor = self.cache_factors.get(cache_name, self.global_factor)
|
||||
callback(new_factor)
|
||||
# block other threads from modifying _CACHES while we iterate it.
|
||||
with _CACHES_LOCK:
|
||||
for cache_name, callback in _CACHES.items():
|
||||
new_factor = self.cache_factors.get(cache_name, self.global_factor)
|
||||
callback(new_factor)
|
||||
|
|
|
@ -36,6 +36,7 @@ from .ratelimiting import RatelimitConfig
|
|||
from .redis import RedisConfig
|
||||
from .registration import RegistrationConfig
|
||||
from .repository import ContentRepositoryConfig
|
||||
from .room import RoomConfig
|
||||
from .room_directory import RoomDirectoryConfig
|
||||
from .saml2_config import SAML2Config
|
||||
from .server import ServerConfig
|
||||
|
@ -79,6 +80,7 @@ class HomeServerConfig(RootConfig):
|
|||
PasswordAuthProviderConfig,
|
||||
PushConfig,
|
||||
SpamCheckerConfig,
|
||||
RoomConfig,
|
||||
GroupsConfig,
|
||||
UserDirectoryConfig,
|
||||
ConsentConfig,
|
||||
|
|
80
synapse/config/room.py
Normal file
80
synapse/config/room.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
# -*- 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 synapse.api.constants import RoomCreationPreset
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
|
||||
logger = logging.Logger(__name__)
|
||||
|
||||
|
||||
class RoomDefaultEncryptionTypes(object):
|
||||
"""Possible values for the encryption_enabled_by_default_for_room_type config option"""
|
||||
|
||||
ALL = "all"
|
||||
INVITE = "invite"
|
||||
OFF = "off"
|
||||
|
||||
|
||||
class RoomConfig(Config):
|
||||
section = "room"
|
||||
|
||||
def read_config(self, config, **kwargs):
|
||||
# Whether new, locally-created rooms should have encryption enabled
|
||||
encryption_for_room_type = config.get(
|
||||
"encryption_enabled_by_default_for_room_type",
|
||||
RoomDefaultEncryptionTypes.OFF,
|
||||
)
|
||||
if encryption_for_room_type == RoomDefaultEncryptionTypes.ALL:
|
||||
self.encryption_enabled_by_default_for_room_presets = [
|
||||
RoomCreationPreset.PRIVATE_CHAT,
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
|
||||
RoomCreationPreset.PUBLIC_CHAT,
|
||||
]
|
||||
elif encryption_for_room_type == RoomDefaultEncryptionTypes.INVITE:
|
||||
self.encryption_enabled_by_default_for_room_presets = [
|
||||
RoomCreationPreset.PRIVATE_CHAT,
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
|
||||
]
|
||||
elif encryption_for_room_type == RoomDefaultEncryptionTypes.OFF:
|
||||
self.encryption_enabled_by_default_for_room_presets = []
|
||||
else:
|
||||
raise ConfigError(
|
||||
"Invalid value for encryption_enabled_by_default_for_room_type"
|
||||
)
|
||||
|
||||
def generate_config_section(self, **kwargs):
|
||||
return """\
|
||||
## Rooms ##
|
||||
|
||||
# Controls whether locally-created rooms should be end-to-end encrypted by
|
||||
# default.
|
||||
#
|
||||
# Possible options are "all", "invite", and "off". They are defined as:
|
||||
#
|
||||
# * "all": any locally-created room
|
||||
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
|
||||
# room creation presets
|
||||
# * "off": this option will take no effect
|
||||
#
|
||||
# The default value is "off".
|
||||
#
|
||||
# Note that this option will only affect rooms created after it is set. It
|
||||
# will also not affect rooms created by other servers.
|
||||
#
|
||||
#encryption_enabled_by_default_for_room_type: invite
|
||||
"""
|
|
@ -160,7 +160,7 @@ class SAML2Config(Config):
|
|||
|
||||
# session lifetime: in milliseconds
|
||||
self.saml2_session_lifetime = self.parse_duration(
|
||||
saml2_config.get("saml_session_lifetime", "5m")
|
||||
saml2_config.get("saml_session_lifetime", "15m")
|
||||
)
|
||||
|
||||
template_dir = saml2_config.get("template_dir")
|
||||
|
@ -286,7 +286,7 @@ class SAML2Config(Config):
|
|||
|
||||
# The lifetime of a SAML session. This defines how long a user has to
|
||||
# complete the authentication process, if allow_unsolicited is unset.
|
||||
# The default is 5 minutes.
|
||||
# The default is 15 minutes.
|
||||
#
|
||||
#saml_session_lifetime: 5m
|
||||
|
||||
|
|
|
@ -856,7 +856,7 @@ class ServerConfig(Config):
|
|||
# number of monthly active users.
|
||||
#
|
||||
# 'limit_usage_by_mau' disables/enables monthly active user blocking. When
|
||||
# anabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||
# enabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||
# with error type Codes.RESOURCE_LIMIT_EXCEEDED
|
||||
#
|
||||
# 'max_mau_value' is the hard limit of monthly active users above which
|
||||
|
|
|
@ -33,7 +33,12 @@ from unpaddedbase64 import decode_base64
|
|||
from twisted.internet import defer
|
||||
|
||||
from synapse import event_auth
|
||||
from synapse.api.constants import EventTypes, Membership, RejectedReason
|
||||
from synapse.api.constants import (
|
||||
EventTypes,
|
||||
Membership,
|
||||
RejectedReason,
|
||||
RoomEncryptionAlgorithms,
|
||||
)
|
||||
from synapse.api.errors import (
|
||||
AuthError,
|
||||
CodeMessageException,
|
||||
|
@ -742,7 +747,10 @@ class FederationHandler(BaseHandler):
|
|||
if device:
|
||||
keys = device.get("keys", {}).get("keys", {})
|
||||
|
||||
if event.content.get("algorithm") == "m.megolm.v1.aes-sha2":
|
||||
if (
|
||||
event.content.get("algorithm")
|
||||
== RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2
|
||||
):
|
||||
# For this algorithm we expect a curve25519 key.
|
||||
key_name = "curve25519:%s" % (device_id,)
|
||||
current_keys = [keys.get(key_name)]
|
||||
|
|
|
@ -26,7 +26,12 @@ from typing import Tuple
|
|||
|
||||
from six import iteritems, string_types
|
||||
|
||||
from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset
|
||||
from synapse.api.constants import (
|
||||
EventTypes,
|
||||
JoinRules,
|
||||
RoomCreationPreset,
|
||||
RoomEncryptionAlgorithms,
|
||||
)
|
||||
from synapse.api.errors import AuthError, Codes, NotFoundError, StoreError, SynapseError
|
||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
|
||||
from synapse.events.utils import copy_power_levels_contents
|
||||
|
@ -56,31 +61,6 @@ FIVE_MINUTES_IN_MS = 5 * 60 * 1000
|
|||
|
||||
|
||||
class RoomCreationHandler(BaseHandler):
|
||||
|
||||
PRESETS_DICT = {
|
||||
RoomCreationPreset.PRIVATE_CHAT: {
|
||||
"join_rules": JoinRules.INVITE,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": False,
|
||||
"guest_can_join": True,
|
||||
"power_level_content_override": {"invite": 0},
|
||||
},
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
|
||||
"join_rules": JoinRules.INVITE,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": True,
|
||||
"guest_can_join": True,
|
||||
"power_level_content_override": {"invite": 0},
|
||||
},
|
||||
RoomCreationPreset.PUBLIC_CHAT: {
|
||||
"join_rules": JoinRules.PUBLIC,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": False,
|
||||
"guest_can_join": False,
|
||||
"power_level_content_override": {},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, hs):
|
||||
super(RoomCreationHandler, self).__init__(hs)
|
||||
|
||||
|
@ -89,6 +69,39 @@ class RoomCreationHandler(BaseHandler):
|
|||
self.room_member_handler = hs.get_room_member_handler()
|
||||
self.config = hs.config
|
||||
|
||||
# Room state based off defined presets
|
||||
self._presets_dict = {
|
||||
RoomCreationPreset.PRIVATE_CHAT: {
|
||||
"join_rules": JoinRules.INVITE,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": False,
|
||||
"guest_can_join": True,
|
||||
"power_level_content_override": {"invite": 0},
|
||||
},
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
|
||||
"join_rules": JoinRules.INVITE,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": True,
|
||||
"guest_can_join": True,
|
||||
"power_level_content_override": {"invite": 0},
|
||||
},
|
||||
RoomCreationPreset.PUBLIC_CHAT: {
|
||||
"join_rules": JoinRules.PUBLIC,
|
||||
"history_visibility": "shared",
|
||||
"original_invitees_have_ops": False,
|
||||
"guest_can_join": False,
|
||||
"power_level_content_override": {},
|
||||
},
|
||||
}
|
||||
|
||||
# Modify presets to selectively enable encryption by default per homeserver config
|
||||
for preset_name, preset_config in self._presets_dict.items():
|
||||
encrypted = (
|
||||
preset_name
|
||||
in self.config.encryption_enabled_by_default_for_room_presets
|
||||
)
|
||||
preset_config["encrypted"] = encrypted
|
||||
|
||||
self._replication = hs.get_replication_data_handler()
|
||||
|
||||
# linearizer to stop two upgrades happening at once
|
||||
|
@ -798,7 +811,7 @@ class RoomCreationHandler(BaseHandler):
|
|||
)
|
||||
return last_stream_id
|
||||
|
||||
config = RoomCreationHandler.PRESETS_DICT[preset_config]
|
||||
config = self._presets_dict[preset_config]
|
||||
|
||||
creator_id = creator.user.to_string()
|
||||
|
||||
|
@ -888,6 +901,13 @@ class RoomCreationHandler(BaseHandler):
|
|||
etype=etype, state_key=state_key, content=content
|
||||
)
|
||||
|
||||
if config["encrypted"]:
|
||||
last_sent_stream_id = await send(
|
||||
etype=EventTypes.RoomEncryption,
|
||||
state_key="",
|
||||
content={"algorithm": RoomEncryptionAlgorithms.DEFAULT},
|
||||
)
|
||||
|
||||
return last_sent_stream_id
|
||||
|
||||
async def _generate_room_id(
|
||||
|
|
|
@ -51,7 +51,9 @@ class PresenceStatusRestServlet(RestServlet):
|
|||
raise AuthError(403, "You are not allowed to see their presence.")
|
||||
|
||||
state = await self.presence_handler.get_state(target_user=user)
|
||||
state = format_user_presence_state(state, self.clock.time_msec())
|
||||
state = format_user_presence_state(
|
||||
state, self.clock.time_msec(), include_user_id=False
|
||||
)
|
||||
|
||||
return 200, state
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title> Login </title>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="js/jquery-3.4.1.min.js"></script>
|
||||
<script src="js/login.js"></script>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title> Login </title>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="js/jquery-3.4.1.min.js"></script>
|
||||
<script src="js/login.js"></script>
|
||||
</head>
|
||||
<body onload="matrixLogin.onLoad()">
|
||||
<center>
|
||||
<br/>
|
||||
<div id="container">
|
||||
<h1 id="title"></h1>
|
||||
|
||||
<span id="feedback" style="color: #f00"></span>
|
||||
<span id="feedback"></span>
|
||||
|
||||
<div id="loading">
|
||||
<img src="spinner.gif" />
|
||||
</div>
|
||||
|
||||
<div id="sso_flow" class="login_flow" style="display:none">
|
||||
<div id="sso_flow" class="login_flow" style="display: none;">
|
||||
Single-sign on:
|
||||
<form id="sso_form" action="/_matrix/client/r0/login/sso/redirect" method="get">
|
||||
<input id="sso_redirect_url" type="hidden" name="redirectUrl" value=""/>
|
||||
|
@ -26,9 +26,9 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div id="password_flow" class="login_flow" style="display:none">
|
||||
<div id="password_flow" class="login_flow" style="display: none;">
|
||||
Password Authentication:
|
||||
<form onsubmit="matrixLogin.password_login(); return false;">
|
||||
<form onsubmit="matrixLogin.passwordLogin(); return false;">
|
||||
<input id="user_id" size="32" type="text" placeholder="Matrix ID (e.g. bob)" autocapitalize="off" autocorrect="off" />
|
||||
<br/>
|
||||
<input id="password" size="32" type="password" placeholder="Password"/>
|
||||
|
@ -38,9 +38,9 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div id="no_login_types" type="button" class="login_flow" style="display:none">
|
||||
<div id="no_login_types" type="button" class="login_flow" style="display: none;">
|
||||
Log in currently unavailable.
|
||||
</div>
|
||||
</center>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,11 +5,11 @@ window.matrixLogin = {
|
|||
};
|
||||
|
||||
// Titles get updated through the process to give users feedback.
|
||||
var TITLE_PRE_AUTH = "Log in with one of the following methods";
|
||||
var TITLE_POST_AUTH = "Logging in...";
|
||||
const TITLE_PRE_AUTH = "Log in with one of the following methods";
|
||||
const TITLE_POST_AUTH = "Logging in...";
|
||||
|
||||
// The cookie used to store the original query parameters when using SSO.
|
||||
var COOKIE_KEY = "synapse_login_fallback_qs";
|
||||
const COOKIE_KEY = "synapse_login_fallback_qs";
|
||||
|
||||
/*
|
||||
* Submit a login request.
|
||||
|
@ -20,9 +20,9 @@ var COOKIE_KEY = "synapse_login_fallback_qs";
|
|||
* login request, e.g. device_id.
|
||||
* callback: (Optional) Function to call on successful login.
|
||||
*/
|
||||
var submitLogin = function(type, data, extra, callback) {
|
||||
function submitLogin(type, data, extra, callback) {
|
||||
console.log("Logging in with " + type);
|
||||
set_title(TITLE_POST_AUTH);
|
||||
setTitle(TITLE_POST_AUTH);
|
||||
|
||||
// Add the login type.
|
||||
data.type = type;
|
||||
|
@ -41,12 +41,15 @@ var submitLogin = function(type, data, extra, callback) {
|
|||
}
|
||||
matrixLogin.onLogin(response);
|
||||
}).fail(errorFunc);
|
||||
};
|
||||
}
|
||||
|
||||
var errorFunc = function(err) {
|
||||
/*
|
||||
* Display an error to the user and show the login form again.
|
||||
*/
|
||||
function errorFunc(err) {
|
||||
// We want to show the error to the user rather than redirecting immediately to the
|
||||
// SSO portal (if SSO is the only login option), so we inhibit the redirect.
|
||||
show_login(true);
|
||||
showLogin(true);
|
||||
|
||||
if (err.responseJSON && err.responseJSON.error) {
|
||||
setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
|
||||
|
@ -54,27 +57,42 @@ var errorFunc = function(err) {
|
|||
else {
|
||||
setFeedbackString("Request failed: " + err.status);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var setFeedbackString = function(text) {
|
||||
/*
|
||||
* Display an error to the user.
|
||||
*/
|
||||
function setFeedbackString(text) {
|
||||
$("#feedback").text(text);
|
||||
};
|
||||
}
|
||||
|
||||
var show_login = function(inhibit_redirect) {
|
||||
// Set the redirect to come back to this page, a login token will get added
|
||||
// and handled after the redirect.
|
||||
var this_page = window.location.origin + window.location.pathname;
|
||||
$("#sso_redirect_url").val(this_page);
|
||||
/*
|
||||
* (Maybe) Show the login forms.
|
||||
*
|
||||
* This actually does a few unrelated functions:
|
||||
*
|
||||
* * Configures the SSO redirect URL to come back to this page.
|
||||
* * Configures and shows the SSO form, if the server supports SSO.
|
||||
* * Otherwise, shows the password form.
|
||||
*/
|
||||
function showLogin(inhibitRedirect) {
|
||||
setTitle(TITLE_PRE_AUTH);
|
||||
|
||||
// If inhibit_redirect is false, and SSO is the only supported login method,
|
||||
// If inhibitRedirect is false, and SSO is the only supported login method,
|
||||
// we can redirect straight to the SSO page.
|
||||
if (matrixLogin.serverAcceptsSso) {
|
||||
// Set the redirect to come back to this page, a login token will get
|
||||
// added as a query parameter and handled after the redirect.
|
||||
$("#sso_redirect_url").val(window.location.origin + window.location.pathname);
|
||||
|
||||
// Before submitting SSO, set the current query parameters into a cookie
|
||||
// for retrieval later.
|
||||
var qs = parseQsFromUrl();
|
||||
setCookie(COOKIE_KEY, JSON.stringify(qs));
|
||||
|
||||
if (!inhibit_redirect && !matrixLogin.serverAcceptsPassword) {
|
||||
// If password is not supported and redirects are allowed, then submit
|
||||
// the form (redirecting to the SSO provider).
|
||||
if (!inhibitRedirect && !matrixLogin.serverAcceptsPassword) {
|
||||
$("#sso_form").submit();
|
||||
return;
|
||||
}
|
||||
|
@ -87,30 +105,39 @@ var show_login = function(inhibit_redirect) {
|
|||
$("#password_flow").show();
|
||||
}
|
||||
|
||||
// If neither password or SSO are supported, show an error to the user.
|
||||
if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) {
|
||||
$("#no_login_types").show();
|
||||
}
|
||||
|
||||
set_title(TITLE_PRE_AUTH);
|
||||
|
||||
$("#loading").hide();
|
||||
};
|
||||
}
|
||||
|
||||
var show_spinner = function() {
|
||||
/*
|
||||
* Hides the forms and shows a loading throbber.
|
||||
*/
|
||||
function showSpinner() {
|
||||
$("#password_flow").hide();
|
||||
$("#sso_flow").hide();
|
||||
$("#no_login_types").hide();
|
||||
$("#loading").show();
|
||||
};
|
||||
}
|
||||
|
||||
var set_title = function(title) {
|
||||
/*
|
||||
* Helper to show the page's main title.
|
||||
*/
|
||||
function setTitle(title) {
|
||||
$("#title").text(title);
|
||||
};
|
||||
}
|
||||
|
||||
var fetch_info = function(cb) {
|
||||
/*
|
||||
* Query the login endpoint for the homeserver's supported flows.
|
||||
*
|
||||
* This populates matrixLogin.serverAccepts* variables.
|
||||
*/
|
||||
function fetchLoginFlows(cb) {
|
||||
$.get(matrixLogin.endpoint, function(response) {
|
||||
var serverAcceptsPassword = false;
|
||||
for (var i=0; i<response.flows.length; i++) {
|
||||
for (var i = 0; i < response.flows.length; i++) {
|
||||
var flow = response.flows[i];
|
||||
if ("m.login.sso" === flow.type) {
|
||||
matrixLogin.serverAcceptsSso = true;
|
||||
|
@ -126,27 +153,41 @@ var fetch_info = function(cb) {
|
|||
}).fail(errorFunc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called on load to fetch login flows and attempt SSO login (if a token is available).
|
||||
*/
|
||||
matrixLogin.onLoad = function() {
|
||||
fetch_info(function() {
|
||||
if (!try_token()) {
|
||||
show_login(false);
|
||||
fetchLoginFlows(function() {
|
||||
// (Maybe) attempt logging in via SSO if a token is available.
|
||||
if (!tryTokenLogin()) {
|
||||
showLogin(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
matrixLogin.password_login = function() {
|
||||
/*
|
||||
* Submit simple user & password login.
|
||||
*/
|
||||
matrixLogin.passwordLogin = function() {
|
||||
var user = $("#user_id").val();
|
||||
var pwd = $("#password").val();
|
||||
|
||||
setFeedbackString("");
|
||||
|
||||
show_spinner();
|
||||
showSpinner();
|
||||
submitLogin(
|
||||
"m.login.password",
|
||||
{user: user, password: pwd},
|
||||
parseQsFromUrl());
|
||||
};
|
||||
|
||||
/*
|
||||
* The onLogin function gets called after a succesful login.
|
||||
*
|
||||
* It is expected that implementations override this to be notified when the
|
||||
* login is complete. The response to the login call is provided as the single
|
||||
* parameter.
|
||||
*/
|
||||
matrixLogin.onLogin = function(response) {
|
||||
// clobber this function
|
||||
console.warn("onLogin - This function should be replaced to proceed.");
|
||||
|
@ -155,7 +196,7 @@ matrixLogin.onLogin = function(response) {
|
|||
/*
|
||||
* Process the query parameters from the current URL into an object.
|
||||
*/
|
||||
var parseQsFromUrl = function() {
|
||||
function parseQsFromUrl() {
|
||||
var pos = window.location.href.indexOf("?");
|
||||
if (pos == -1) {
|
||||
return {};
|
||||
|
@ -174,12 +215,12 @@ var parseQsFromUrl = function() {
|
|||
result[key] = val;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the cookies and return an object.
|
||||
*/
|
||||
var parseCookies = function() {
|
||||
function parseCookies() {
|
||||
var allCookies = document.cookie;
|
||||
var result = {};
|
||||
allCookies.split(";").forEach(function(part) {
|
||||
|
@ -196,32 +237,32 @@ var parseCookies = function() {
|
|||
result[key] = val;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Set a cookie that is valid for 1 hour.
|
||||
*/
|
||||
var setCookie = function(key, value) {
|
||||
function setCookie(key, value) {
|
||||
// The maximum age is set in seconds.
|
||||
var maxAge = 60 * 60;
|
||||
// Set the cookie, this defaults to the current domain and path.
|
||||
document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax";
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes a cookie by key.
|
||||
*/
|
||||
var deleteCookie = function(key) {
|
||||
function deleteCookie(key) {
|
||||
// Delete a cookie by setting the expiration to 0. (Note that the value
|
||||
// doesn't matter.)
|
||||
document.cookie = key + "=deleted;expires=0";
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Submits the login token if one is found in the query parameters. Returns a
|
||||
* boolean of whether the login token was found or not.
|
||||
*/
|
||||
var try_token = function() {
|
||||
function tryTokenLogin() {
|
||||
// Check if the login token is in the query parameters.
|
||||
var qs = parseQsFromUrl();
|
||||
|
||||
|
@ -233,18 +274,18 @@ var try_token = function() {
|
|||
// Retrieve the original query parameters (from before the SSO redirect).
|
||||
// They are stored as JSON in a cookie.
|
||||
var cookies = parseCookies();
|
||||
var original_query_params = JSON.parse(cookies[COOKIE_KEY] || "{}")
|
||||
var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}")
|
||||
|
||||
// If the login is successful, delete the cookie.
|
||||
var callback = function() {
|
||||
function callback() {
|
||||
deleteCookie(COOKIE_KEY);
|
||||
}
|
||||
|
||||
submitLogin(
|
||||
"m.login.token",
|
||||
{token: loginToken},
|
||||
original_query_params,
|
||||
originalQueryParams,
|
||||
callback);
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,20 +31,44 @@ form {
|
|||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add some padding to the viewport.
|
||||
*/
|
||||
#container {
|
||||
padding: 10px;
|
||||
}
|
||||
/*
|
||||
* Center all direct children of the main form.
|
||||
*/
|
||||
#container > * {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*
|
||||
* A wrapper around each login flow.
|
||||
*/
|
||||
.login_flow {
|
||||
width: 300px;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to show error content.
|
||||
*/
|
||||
#feedback {
|
||||
/* Red text. */
|
||||
color: #ff0000;
|
||||
/* A little space to not overlap the box-shadow. */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ from signedjson.types import BaseKey, SigningKey
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import RoomEncryptionAlgorithms
|
||||
from synapse.rest import admin
|
||||
from synapse.rest.client.v1 import login
|
||||
from synapse.types import JsonDict, ReadReceipt
|
||||
|
@ -536,7 +537,10 @@ def build_device_dict(user_id: str, device_id: str, sk: SigningKey):
|
|||
return {
|
||||
"user_id": user_id,
|
||||
"device_id": device_id,
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"curve25519:" + device_id: "curve25519+key",
|
||||
key_id(sk): encode_pubkey(sk),
|
||||
|
|
|
@ -25,6 +25,7 @@ from twisted.internet import defer
|
|||
import synapse.handlers.e2e_keys
|
||||
import synapse.storage
|
||||
from synapse.api import errors
|
||||
from synapse.api.constants import RoomEncryptionAlgorithms
|
||||
|
||||
from tests import unittest, utils
|
||||
|
||||
|
@ -222,7 +223,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key_1 = {
|
||||
"user_id": local_user,
|
||||
"device_id": "abc",
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:abc": "base64+ed25519+key",
|
||||
"curve25519:abc": "base64+curve25519+key",
|
||||
|
@ -232,7 +236,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key_2 = {
|
||||
"user_id": local_user,
|
||||
"device_id": "def",
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:def": "base64+ed25519+key",
|
||||
"curve25519:def": "base64+curve25519+key",
|
||||
|
@ -315,7 +322,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key = {
|
||||
"user_id": local_user,
|
||||
"device_id": device_id,
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
|
||||
"signatures": {local_user: {"ed25519:xyz": "something"}},
|
||||
}
|
||||
|
@ -392,7 +402,7 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
"device_id": device_id,
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"curve25519:xyz": "curve25519+key",
|
||||
|
|
|
@ -17,12 +17,13 @@ from mock import Mock
|
|||
from twisted.internet import defer
|
||||
|
||||
import synapse.rest.admin
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.api.constants import EventTypes, RoomEncryptionAlgorithms, UserTypes
|
||||
from synapse.rest.client.v1 import login, room
|
||||
from synapse.rest.client.v2_alpha import user_directory
|
||||
from synapse.storage.roommember import ProfileInfo
|
||||
|
||||
from tests import unittest
|
||||
from tests.unittest import override_config
|
||||
|
||||
|
||||
class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
|
@ -147,6 +148,94 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
|||
s = self.get_success(self.handler.search_users(u1, "user3", 10))
|
||||
self.assertEqual(len(s["results"]), 0)
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "all"})
|
||||
def test_encrypted_by_default_config_option_all(self):
|
||||
"""Tests that invite-only and non-invite-only rooms have encryption enabled by
|
||||
default when the config option encryption_enabled_by_default_for_room_type is "all".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "invite"})
|
||||
def test_encrypted_by_default_config_option_invite(self):
|
||||
"""Tests that only new, invite-only rooms have encryption enabled by default when
|
||||
the config option encryption_enabled_by_default_for_room_type is "invite".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "off"})
|
||||
def test_encrypted_by_default_config_option_off(self):
|
||||
"""Tests that neither new invite-only nor non-invite-only rooms have encryption
|
||||
enabled by default when the config option
|
||||
encryption_enabled_by_default_for_room_type is "off".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
def test_spam_checker(self):
|
||||
"""
|
||||
A user which fails to the spam checks will not appear in search results.
|
||||
|
|
|
@ -30,7 +30,7 @@ class MessageAcceptTests(unittest.HomeserverTestCase):
|
|||
room_creator = self.homeserver.get_room_creation_handler()
|
||||
room_deferred = ensureDeferred(
|
||||
room_creator.create_room(
|
||||
our_user, room_creator.PRESETS_DICT["public_chat"], ratelimit=False
|
||||
our_user, room_creator._presets_dict["public_chat"], ratelimit=False
|
||||
)
|
||||
)
|
||||
self.reactor.advance(0.1)
|
||||
|
|
Loading…
Reference in a new issue