Compare commits

...

18 Commits

Author SHA1 Message Date
Erik Johnston
23bc5ee14e Bump version 2019-09-13 14:17:05 +01:00
Erik Johnston
76b25075b0 Merge pull request #6034 from matrix-org/matthew/no-push-for-groups
don't push by default for group chats.
2019-09-13 14:16:39 +01:00
Erik Johnston
f69f4e7a97 Black 2019-09-13 14:14:51 +01:00
Matthew Hodgson
8a2e8eaaec default off groupchat push correctly 2019-09-13 12:14:29 +01:00
Matthew Hodgson
b18f54c845 don't push by default for group chats.
see https://github.com/vector-im/riot-web/issues/3268

This only works for new servers (e.g. mozilla-test.modular.im)
2019-09-13 12:05:55 +01:00
Erik Johnston
cbb926f237 Add +modular to version string 2019-09-12 13:39:27 +01:00
Erik Johnston
f7cb88c3bd Bump to alpha version 2019-09-12 13:30:22 +01:00
Erik Johnston
a6fb79cf36 Update sample config 2019-09-12 13:27:52 +01:00
Erik Johnston
6d2557f1e8 Fix comments
Co-Authored-By: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2019-09-12 13:27:52 +01:00
Erik Johnston
cec502a595 Add test for admin redaction ratelimiting. 2019-09-12 13:27:52 +01:00
Erik Johnston
fe8636b79e Fix how we check for self redaction 2019-09-12 13:27:52 +01:00
Erik Johnston
059369dd04 Update sample config 2019-09-12 13:27:52 +01:00
Erik Johnston
de7ba972be Newsfile 2019-09-12 13:27:52 +01:00
Erik Johnston
35e6e69ea9 Allow use of different ratelimits for admin redactions.
This is useful to allow room admins to quickly deal with a large number
of abusive messages.
2019-09-12 13:27:52 +01:00
David Baker
34b826bef8 Fix SSO fallback login
Well, it worked, but forgot to remove the thing saying login was
unavailable.
2019-09-12 13:25:28 +01:00
Olivier Wilkinson (reivilibre)
f014ea9b5c Document GET method for retrieving admin bit of user in admin API
Signed-off-by: Olivier Wilkinson (reivilibre) <olivier@librepush.net>
2019-09-12 13:25:28 +01:00
Olivier Wilkinson (reivilibre)
b0cf4228d2 Add GET method to admin API /users/@user:dom/admin
Signed-off-by: Olivier Wilkinson (reivilibre) <olivier@librepush.net>
2019-09-12 13:25:28 +01:00
reivilibre
21c037ac46 Add Admin API capability to set adminship of a user (#5878)
Admin API: Set adminship of a user
2019-09-12 13:25:28 +01:00
17 changed files with 295 additions and 14 deletions

1
changelog.d/5878.feature Normal file
View File

@@ -0,0 +1 @@
Add admin API endpoint for setting whether or not a user is a server administrator.

1
changelog.d/5914.feature Normal file
View File

@@ -0,0 +1 @@
Add admin API endpoint for getting whether or not a user is a server administrator.

1
changelog.d/6015.feature Normal file
View File

@@ -0,0 +1 @@
Add config option to increase ratelimits for room admins redacting messages.

View File

@@ -84,3 +84,42 @@ with a body of:
} }
including an ``access_token`` of a server admin. including an ``access_token`` of a server admin.
Get whether a user is a server administrator or not
===================================================
The api is::
GET /_synapse/admin/v1/users/<user_id>/admin
including an ``access_token`` of a server admin.
A response body like the following is returned:
.. code:: json
{
"admin": true
}
Change whether a user is a server administrator or not
======================================================
Note that you cannot demote yourself.
The api is::
PUT /_synapse/admin/v1/users/<user_id>/admin
with a body of:
.. code:: json
{
"admin": true
}
including an ``access_token`` of a server admin.

View File

@@ -510,6 +510,9 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# - one for login that ratelimits login requests based on the account the # - one for login that ratelimits login requests based on the account the
# client is attempting to log into, based on the amount of failed login # client is attempting to log into, based on the amount of failed login
# attempts for this account. # attempts for this account.
# - one for ratelimiting redactions by room admins. If this is not explicitly
# set then it uses the same ratelimiting as per rc_message. This is useful
# to allow room admins to deal with abuse quickly.
# #
# The defaults are as shown below. # The defaults are as shown below.
# #
@@ -531,6 +534,10 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# failed_attempts: # failed_attempts:
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # burst_count: 3
#
#rc_admin_redaction:
# per_second: 1
# burst_count: 50
# Ratelimiting settings for incoming federation # Ratelimiting settings for incoming federation

View File

@@ -35,4 +35,4 @@ try:
except ImportError: except ImportError:
pass pass
__version__ = "1.3.1" __version__ = "1.3.2-alpha.2+modular"

View File

@@ -80,6 +80,12 @@ class RatelimitConfig(Config):
"federation_rr_transactions_per_room_per_second", 50 "federation_rr_transactions_per_room_per_second", 50
) )
rc_admin_redaction = config.get("rc_admin_redaction")
if rc_admin_redaction:
self.rc_admin_redaction = RateLimitConfig(rc_admin_redaction)
else:
self.rc_admin_redaction = None
def generate_config_section(self, **kwargs): def generate_config_section(self, **kwargs):
return """\ return """\
## Ratelimiting ## ## Ratelimiting ##
@@ -102,6 +108,9 @@ class RatelimitConfig(Config):
# - one for login that ratelimits login requests based on the account the # - one for login that ratelimits login requests based on the account the
# client is attempting to log into, based on the amount of failed login # client is attempting to log into, based on the amount of failed login
# attempts for this account. # attempts for this account.
# - one for ratelimiting redactions by room admins. If this is not explicitly
# set then it uses the same ratelimiting as per rc_message. This is useful
# to allow room admins to deal with abuse quickly.
# #
# The defaults are as shown below. # The defaults are as shown below.
# #
@@ -123,6 +132,10 @@ class RatelimitConfig(Config):
# failed_attempts: # failed_attempts:
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # burst_count: 3
#
#rc_admin_redaction:
# per_second: 1
# burst_count: 50
# Ratelimiting settings for incoming federation # Ratelimiting settings for incoming federation

View File

@@ -45,6 +45,7 @@ class BaseHandler(object):
self.state_handler = hs.get_state_handler() self.state_handler = hs.get_state_handler()
self.distributor = hs.get_distributor() self.distributor = hs.get_distributor()
self.ratelimiter = hs.get_ratelimiter() self.ratelimiter = hs.get_ratelimiter()
self.admin_redaction_ratelimiter = hs.get_admin_redaction_ratelimiter()
self.clock = hs.get_clock() self.clock = hs.get_clock()
self.hs = hs self.hs = hs
@@ -53,7 +54,7 @@ class BaseHandler(object):
self.event_builder_factory = hs.get_event_builder_factory() self.event_builder_factory = hs.get_event_builder_factory()
@defer.inlineCallbacks @defer.inlineCallbacks
def ratelimit(self, requester, update=True): def ratelimit(self, requester, update=True, is_admin_redaction=False):
"""Ratelimits requests. """Ratelimits requests.
Args: Args:
@@ -62,6 +63,9 @@ class BaseHandler(object):
Set to False when doing multiple checks for one request (e.g. Set to False when doing multiple checks for one request (e.g.
to check up front if we would reject the request), and set to to check up front if we would reject the request), and set to
True for the last call for a given request. True for the last call for a given request.
is_admin_redaction (bool): Whether this is a room admin/moderator
redacting an event. If so then we may apply different
ratelimits depending on config.
Raises: Raises:
LimitExceededError if the request should be ratelimited LimitExceededError if the request should be ratelimited
@@ -90,16 +94,33 @@ class BaseHandler(object):
messages_per_second = override.messages_per_second messages_per_second = override.messages_per_second
burst_count = override.burst_count burst_count = override.burst_count
else: else:
messages_per_second = self.hs.config.rc_message.per_second # We default to different values if this is an admin redaction and
burst_count = self.hs.config.rc_message.burst_count # the config is set
if is_admin_redaction and self.hs.config.rc_admin_redaction:
messages_per_second = self.hs.config.rc_admin_redaction.per_second
burst_count = self.hs.config.rc_admin_redaction.burst_count
else:
messages_per_second = self.hs.config.rc_message.per_second
burst_count = self.hs.config.rc_message.burst_count
allowed, time_allowed = self.ratelimiter.can_do_action( if is_admin_redaction and self.hs.config.rc_admin_redaction:
user_id, # If we have separate config for admin redactions we use a separate
time_now, # ratelimiter
rate_hz=messages_per_second, allowed, time_allowed = self.admin_redaction_ratelimiter.can_do_action(
burst_count=burst_count, user_id,
update=update, time_now,
) rate_hz=messages_per_second,
burst_count=burst_count,
update=update,
)
else:
allowed, time_allowed = self.ratelimiter.can_do_action(
user_id,
time_now,
rate_hz=messages_per_second,
burst_count=burst_count,
update=update,
)
if not allowed: if not allowed:
raise LimitExceededError( raise LimitExceededError(
retry_after_ms=int(1000 * (time_allowed - time_now)) retry_after_ms=int(1000 * (time_allowed - time_now))

View File

@@ -94,6 +94,25 @@ class AdminHandler(BaseHandler):
return ret return ret
def get_user_server_admin(self, user):
"""
Get the admin bit on a user.
Args:
user_id (UserID): the (necessarily local) user to manipulate
"""
return self.store.is_server_admin(user)
def set_user_server_admin(self, user, admin):
"""
Set the admin bit on a user.
Args:
user_id (UserID): the (necessarily local) user to manipulate
admin (bool): whether or not the user should be an admin of this server
"""
return self.store.set_server_admin(user, admin)
@defer.inlineCallbacks @defer.inlineCallbacks
def export_user_data(self, user_id, writer): def export_user_data(self, user_id, writer):
"""Write all data we have on the user to the given writer. """Write all data we have on the user to the given writer.

View File

@@ -726,7 +726,27 @@ class EventCreationHandler(object):
assert not self.config.worker_app assert not self.config.worker_app
if ratelimit: if ratelimit:
yield self.base_handler.ratelimit(requester) # We check if this is a room admin redacting an event so that we
# can apply different ratelimiting. We do this by simply checking
# it's not a self-redaction (to avoid having to look up whether the
# user is actually admin or not).
is_admin_redaction = False
if event.type == EventTypes.Redaction:
original_event = yield self.store.get_event(
event.redacts,
check_redacted=False,
get_prev_content=False,
allow_rejected=False,
allow_none=True,
)
is_admin_redaction = (
original_event and event.sender != original_event.sender
)
yield self.base_handler.ratelimit(
requester, is_admin_redaction=is_admin_redaction
)
yield self.base_handler.maybe_kick_guest_users(event, context) yield self.base_handler.maybe_kick_guest_users(event, context)

View File

@@ -335,6 +335,11 @@ BASE_APPEND_UNDERRIDE_RULES = [
"_id": "_message", "_id": "_message",
} }
], ],
# default to not notifying for group chats
# see https://github.com/vector-im/riot-web/issues/3268
# we can't do this on existing servers because we need to
# add per-user overrides to preserve their existing behaviour
"enabled": False,
"actions": ["notify", {"set_tweak": "highlight", "value": False}], "actions": ["notify", {"set_tweak": "highlight", "value": False}],
}, },
# XXX: this is going to fire for events which aren't m.room.messages # XXX: this is going to fire for events which aren't m.room.messages

View File

@@ -43,6 +43,7 @@ from synapse.rest.admin._base import (
) )
from synapse.rest.admin.media import register_servlets_for_media_repo from synapse.rest.admin.media import register_servlets_for_media_repo
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
from synapse.rest.admin.users import UserAdminServlet
from synapse.types import UserID, create_requester from synapse.types import UserID, create_requester
from synapse.util.versionstring import get_version_string from synapse.util.versionstring import get_version_string
@@ -50,7 +51,7 @@ logger = logging.getLogger(__name__)
class UsersRestServlet(RestServlet): class UsersRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)") PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$")
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@@ -740,6 +741,7 @@ def register_servlets(hs, http_server):
register_servlets_for_client_rest_resource(hs, http_server) register_servlets_for_client_rest_resource(hs, http_server)
SendServerNoticeServlet(hs).register(http_server) SendServerNoticeServlet(hs).register(http_server)
VersionServlet(hs).register(http_server) VersionServlet(hs).register(http_server)
UserAdminServlet(hs).register(http_server)
def register_servlets_for_client_rest_resource(hs, http_server): def register_servlets_for_client_rest_resource(hs, http_server):

100
synapse/rest/admin/users.py Normal file
View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright 2019 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 re
from twisted.internet import defer
from synapse.api.errors import SynapseError
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
parse_json_object_from_request,
)
from synapse.rest.admin import assert_requester_is_admin, assert_user_is_admin
from synapse.types import UserID
class UserAdminServlet(RestServlet):
"""
Get or set whether or not a user is a server administrator.
Note that only local users can be server administrators, and that an
administrator may not demote themselves.
Only server administrators can use this API.
Examples:
* Get
GET /_synapse/admin/v1/users/@nonadmin:example.com/admin
response on success:
{
"admin": false
}
* Set
PUT /_synapse/admin/v1/users/@reivilibre:librepush.net/admin
request body:
{
"admin": true
}
response on success:
{}
"""
PATTERNS = (re.compile("^/_synapse/admin/v1/users/(?P<user_id>@[^/]*)/admin$"),)
def __init__(self, hs):
self.hs = hs
self.auth = hs.get_auth()
self.handlers = hs.get_handlers()
@defer.inlineCallbacks
def on_GET(self, request, user_id):
yield assert_requester_is_admin(self.auth, request)
target_user = UserID.from_string(user_id)
if not self.hs.is_mine(target_user):
raise SynapseError(400, "Only local users can be admins of this homeserver")
is_admin = yield self.handlers.admin_handler.get_user_server_admin(target_user)
is_admin = bool(is_admin)
return (200, {"admin": is_admin})
@defer.inlineCallbacks
def on_PUT(self, request, user_id):
requester = yield self.auth.get_user_by_req(request)
yield assert_user_is_admin(self.auth, requester.user)
auth_user = requester.user
target_user = UserID.from_string(user_id)
body = parse_json_object_from_request(request)
assert_params_in_dict(body, ["admin"])
if not self.hs.is_mine(target_user):
raise SynapseError(400, "Only local users can be admins of this homeserver")
set_admin_to = bool(body["admin"])
if target_user == auth_user and not set_admin_to:
raise SynapseError(400, "You may not demote yourself.")
yield self.handlers.admin_handler.set_user_server_admin(
target_user, set_admin_to
)
return (200, {})

View File

@@ -221,6 +221,7 @@ class HomeServer(object):
self.clock = Clock(reactor) self.clock = Clock(reactor)
self.distributor = Distributor() self.distributor = Distributor()
self.ratelimiter = Ratelimiter() self.ratelimiter = Ratelimiter()
self.admin_redaction_ratelimiter = Ratelimiter()
self.registration_ratelimiter = Ratelimiter() self.registration_ratelimiter = Ratelimiter()
self.datastore = None self.datastore = None
@@ -279,6 +280,9 @@ class HomeServer(object):
def get_registration_ratelimiter(self): def get_registration_ratelimiter(self):
return self.registration_ratelimiter return self.registration_ratelimiter
def get_admin_redaction_ratelimiter(self):
return self.admin_redaction_ratelimiter
def build_federation_client(self): def build_federation_client(self):
return FederationClient(self) return FederationClient(self)

View File

@@ -62,7 +62,7 @@ var show_login = function() {
$("#sso_flow").show(); $("#sso_flow").show();
} }
if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsCas) { if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsCas && !matrixLogin.serverAcceptsSso) {
$("#no_login_types").show(); $("#no_login_types").show();
} }
}; };

View File

@@ -272,6 +272,14 @@ class RegistrationWorkerStore(SQLBaseStore):
@defer.inlineCallbacks @defer.inlineCallbacks
def is_server_admin(self, user): def is_server_admin(self, user):
"""Determines if a user is an admin of this homeserver.
Args:
user (UserID): user ID of the user to test
Returns (bool):
true iff the user is a server admin, false otherwise.
"""
res = yield self._simple_select_one_onecol( res = yield self._simple_select_one_onecol(
table="users", table="users",
keyvalues={"name": user.to_string()}, keyvalues={"name": user.to_string()},
@@ -282,6 +290,21 @@ class RegistrationWorkerStore(SQLBaseStore):
return res if res else False return res if res else False
def set_server_admin(self, user, admin):
"""Sets whether a user is an admin of this homeserver.
Args:
user (UserID): user ID of the user to test
admin (bool): true iff the user is to be a server admin,
false otherwise.
"""
return self._simple_update_one(
table="users",
keyvalues={"name": user.to_string()},
updatevalues={"admin": 1 if admin else 0},
desc="set_server_admin",
)
def _query_for_auth(self, txn, token): def _query_for_auth(self, txn, token):
sql = ( sql = (
"SELECT users.name, users.is_guest, access_tokens.id as token_id," "SELECT users.name, users.is_guest, access_tokens.id as token_id,"

View File

@@ -30,6 +30,14 @@ class RedactionsTestCase(HomeserverTestCase):
sync.register_servlets, sync.register_servlets,
] ]
def make_homeserver(self, reactor, clock):
config = self.default_config()
config["rc_message"] = {"per_second": 0.2, "burst_count": 10}
config["rc_admin_redaction"] = {"per_second": 1, "burst_count": 100}
return self.setup_test_homeserver(config=config)
def prepare(self, reactor, clock, hs): def prepare(self, reactor, clock, hs):
# register a couple of users # register a couple of users
self.mod_user_id = self.register_user("user1", "pass") self.mod_user_id = self.register_user("user1", "pass")
@@ -177,3 +185,20 @@ class RedactionsTestCase(HomeserverTestCase):
self._redact_event( self._redact_event(
self.other_access_token, self.room_id, create_event_id, expect_code=403 self.other_access_token, self.room_id, create_event_id, expect_code=403
) )
def test_redact_event_as_moderator_ratelimit(self):
"""Tests that the correct ratelimiting is applied to redactions
"""
message_ids = []
# as a regular user, send messages to redact
for _ in range(20):
b = self.helper.send(room_id=self.room_id, tok=self.other_access_token)
message_ids.append(b["event_id"])
self.reactor.advance(10) # To get around ratelimits
# as the moderator, send a bunch of redactions
for msg_id in message_ids:
# These should all succeed, even though this would be denied by
# the standard message ratelimiter
self._redact_event(self.mod_access_token, self.room_id, msg_id)