create support user (#4141)
Allow for the creation of a support user. A support user can access the server, join rooms, interact with other users, but does not appear in the user directory nor does it contribute to monthly active user limits.pull/4305/head
parent
e93a0ebf50
commit
d2f7c4e6b1
|
@ -0,0 +1 @@
|
|||
Special-case a support user for use in verifying behaviour of a given server. The support user does not appear in user directory or monthly active user counts.
|
|
@ -39,13 +39,13 @@ As an example::
|
|||
}
|
||||
|
||||
The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the key being
|
||||
the shared secret and the content being the nonce, user, password, and either
|
||||
the string "admin" or "notadmin", each separated by NULs. For an example of
|
||||
generation in Python::
|
||||
the shared secret and the content being the nonce, user, password, either the
|
||||
string "admin" or "notadmin", and optionally the user_type
|
||||
each separated by NULs. For an example of generation in Python::
|
||||
|
||||
import hmac, hashlib
|
||||
|
||||
def generate_mac(nonce, user, password, admin=False):
|
||||
def generate_mac(nonce, user, password, admin=False, user_type=None):
|
||||
|
||||
mac = hmac.new(
|
||||
key=shared_secret,
|
||||
|
@ -59,5 +59,8 @@ generation in Python::
|
|||
mac.update(password.encode('utf8'))
|
||||
mac.update(b"\x00")
|
||||
mac.update(b"admin" if admin else b"notadmin")
|
||||
if user_type:
|
||||
mac.update(b"\x00")
|
||||
mac.update(user_type.encode('utf8'))
|
||||
|
||||
return mac.hexdigest()
|
||||
|
|
|
@ -35,6 +35,7 @@ def request_registration(
|
|||
server_location,
|
||||
shared_secret,
|
||||
admin=False,
|
||||
user_type=None,
|
||||
requests=_requests,
|
||||
_print=print,
|
||||
exit=sys.exit,
|
||||
|
@ -65,6 +66,9 @@ def request_registration(
|
|||
mac.update(password.encode('utf8'))
|
||||
mac.update(b"\x00")
|
||||
mac.update(b"admin" if admin else b"notadmin")
|
||||
if user_type:
|
||||
mac.update(b"\x00")
|
||||
mac.update(user_type.encode('utf8'))
|
||||
|
||||
mac = mac.hexdigest()
|
||||
|
||||
|
@ -74,6 +78,7 @@ def request_registration(
|
|||
"password": password,
|
||||
"mac": mac,
|
||||
"admin": admin,
|
||||
"user_type": user_type,
|
||||
}
|
||||
|
||||
_print("Sending registration request...")
|
||||
|
@ -91,7 +96,7 @@ def request_registration(
|
|||
_print("Success!")
|
||||
|
||||
|
||||
def register_new_user(user, password, server_location, shared_secret, admin):
|
||||
def register_new_user(user, password, server_location, shared_secret, admin, user_type):
|
||||
if not user:
|
||||
try:
|
||||
default_user = getpass.getuser()
|
||||
|
@ -129,7 +134,8 @@ def register_new_user(user, password, server_location, shared_secret, admin):
|
|||
else:
|
||||
admin = False
|
||||
|
||||
request_registration(user, password, server_location, shared_secret, bool(admin))
|
||||
request_registration(user, password, server_location, shared_secret,
|
||||
bool(admin), user_type)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -154,6 +160,12 @@ def main():
|
|||
default=None,
|
||||
help="New password for user. Will prompt if omitted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--user_type",
|
||||
default=None,
|
||||
help="User type as specified in synapse.api.constants.UserTypes",
|
||||
)
|
||||
admin_group = parser.add_mutually_exclusive_group()
|
||||
admin_group.add_argument(
|
||||
"-a",
|
||||
|
@ -208,7 +220,8 @@ def main():
|
|||
if args.admin or args.no_admin:
|
||||
admin = args.admin
|
||||
|
||||
register_new_user(args.user, args.password, args.server_url, secret, admin)
|
||||
register_new_user(args.user, args.password, args.server_url, secret,
|
||||
admin, args.user_type)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -802,9 +802,10 @@ class Auth(object):
|
|||
threepid should never be set at the same time.
|
||||
"""
|
||||
|
||||
# Never fail an auth check for the server notices users
|
||||
# Never fail an auth check for the server notices users or support user
|
||||
# This can be a problem where event creation is prohibited due to blocking
|
||||
if user_id == self.hs.config.server_notices_mxid:
|
||||
is_support = yield self.store.is_support_user(user_id)
|
||||
if user_id == self.hs.config.server_notices_mxid or is_support:
|
||||
return
|
||||
|
||||
if self.hs.config.hs_disabled:
|
||||
|
|
|
@ -119,3 +119,11 @@ KNOWN_ROOM_VERSIONS = {
|
|||
|
||||
ServerNoticeMsgType = "m.server_notice"
|
||||
ServerNoticeLimitReached = "m.server_notice.usage_limit_reached"
|
||||
|
||||
|
||||
class UserTypes(object):
|
||||
"""Allows for user type specific behaviour. With the benefit of hindsight
|
||||
'admin' and 'guest' users should also be UserTypes. Normal users are type None
|
||||
"""
|
||||
SUPPORT = "support"
|
||||
ALL_USER_TYPES = (SUPPORT)
|
||||
|
|
|
@ -126,6 +126,7 @@ class RegistrationHandler(BaseHandler):
|
|||
make_guest=False,
|
||||
admin=False,
|
||||
threepid=None,
|
||||
user_type=None,
|
||||
default_display_name=None,
|
||||
):
|
||||
"""Registers a new client on the server.
|
||||
|
@ -141,6 +142,8 @@ class RegistrationHandler(BaseHandler):
|
|||
since it offers no means of associating a device_id with the
|
||||
access_token. Instead you should call auth_handler.issue_access_token
|
||||
after registration.
|
||||
user_type (str|None): type of user. One of the values from
|
||||
api.constants.UserTypes, or None for a normal user.
|
||||
default_display_name (unicode|None): if set, the new user's displayname
|
||||
will be set to this. Defaults to 'localpart'.
|
||||
Returns:
|
||||
|
@ -190,6 +193,7 @@ class RegistrationHandler(BaseHandler):
|
|||
make_guest=make_guest,
|
||||
create_profile_with_displayname=default_display_name,
|
||||
admin=admin,
|
||||
user_type=user_type,
|
||||
)
|
||||
|
||||
if self.hs.config.user_directory_search_all_users:
|
||||
|
@ -242,9 +246,16 @@ class RegistrationHandler(BaseHandler):
|
|||
# auto-join the user to any rooms we're supposed to dump them into
|
||||
fake_requester = create_requester(user_id)
|
||||
|
||||
# try to create the room if we're the first user on the server
|
||||
# try to create the room if we're the first real user on the server. Note
|
||||
# that an auto-generated support user is not a real user and will never be
|
||||
# the user to create the room
|
||||
should_auto_create_rooms = False
|
||||
if self.hs.config.autocreate_auto_join_rooms:
|
||||
is_support = yield self.store.is_support_user(user_id)
|
||||
# There is an edge case where the first user is the support user, then
|
||||
# the room is never created, though this seems unlikely and
|
||||
# recoverable from given the support user being involved in the first
|
||||
# place.
|
||||
if self.hs.config.autocreate_auto_join_rooms and not is_support:
|
||||
count = yield self.store.count_all_users()
|
||||
should_auto_create_rooms = count == 1
|
||||
for r in self.hs.config.auto_join_rooms:
|
||||
|
|
|
@ -433,7 +433,7 @@ class RoomCreationHandler(BaseHandler):
|
|||
"""
|
||||
user_id = requester.user.to_string()
|
||||
|
||||
self.auth.check_auth_blocking(user_id)
|
||||
yield self.auth.check_auth_blocking(user_id)
|
||||
|
||||
if not self.spam_checker.user_may_create_room(user_id):
|
||||
raise SynapseError(403, "You are not permitted to create rooms")
|
||||
|
|
|
@ -125,9 +125,12 @@ class UserDirectoryHandler(object):
|
|||
"""
|
||||
# FIXME(#3714): We should probably do this in the same worker as all
|
||||
# the other changes.
|
||||
yield self.store.update_profile_in_user_dir(
|
||||
user_id, profile.display_name, profile.avatar_url, None,
|
||||
)
|
||||
is_support = yield self.store.is_support_user(user_id)
|
||||
# Support users are for diagnostics and should not appear in the user directory.
|
||||
if not is_support:
|
||||
yield self.store.update_profile_in_user_dir(
|
||||
user_id, profile.display_name, profile.avatar_url, None,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def handle_user_deactivated(self, user_id):
|
||||
|
@ -329,14 +332,7 @@ class UserDirectoryHandler(object):
|
|||
public_value=Membership.JOIN,
|
||||
)
|
||||
|
||||
if change is None:
|
||||
# Handle any profile changes
|
||||
yield self._handle_profile_change(
|
||||
state_key, room_id, prev_event_id, event_id,
|
||||
)
|
||||
continue
|
||||
|
||||
if not change:
|
||||
if change is False:
|
||||
# Need to check if the server left the room entirely, if so
|
||||
# we might need to remove all the users in that room
|
||||
is_in_room = yield self.store.is_host_joined(
|
||||
|
@ -354,16 +350,25 @@ class UserDirectoryHandler(object):
|
|||
else:
|
||||
logger.debug("Server is still in room: %r", room_id)
|
||||
|
||||
if change: # The user joined
|
||||
event = yield self.store.get_event(event_id, allow_none=True)
|
||||
profile = ProfileInfo(
|
||||
avatar_url=event.content.get("avatar_url"),
|
||||
display_name=event.content.get("displayname"),
|
||||
)
|
||||
is_support = yield self.store.is_support_user(state_key)
|
||||
if not is_support:
|
||||
if change is None:
|
||||
# Handle any profile changes
|
||||
yield self._handle_profile_change(
|
||||
state_key, room_id, prev_event_id, event_id,
|
||||
)
|
||||
continue
|
||||
|
||||
yield self._handle_new_user(room_id, state_key, profile)
|
||||
else: # The user left
|
||||
yield self._handle_remove_user(room_id, state_key)
|
||||
if change: # The user joined
|
||||
event = yield self.store.get_event(event_id, allow_none=True)
|
||||
profile = ProfileInfo(
|
||||
avatar_url=event.content.get("avatar_url"),
|
||||
display_name=event.content.get("displayname"),
|
||||
)
|
||||
|
||||
yield self._handle_new_user(room_id, state_key, profile)
|
||||
else: # The user left
|
||||
yield self._handle_remove_user(room_id, state_key)
|
||||
else:
|
||||
logger.debug("Ignoring irrelevant type: %r", typ)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ from six.moves import http_client
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import Membership
|
||||
from synapse.api.constants import Membership, UserTypes
|
||||
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
||||
from synapse.http.servlet import (
|
||||
assert_params_in_dict,
|
||||
|
@ -158,6 +158,11 @@ class UserRegisterServlet(ClientV1RestServlet):
|
|||
raise SynapseError(400, "Invalid password")
|
||||
|
||||
admin = body.get("admin", None)
|
||||
user_type = body.get("user_type", None)
|
||||
|
||||
if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
|
||||
raise SynapseError(400, "Invalid user type")
|
||||
|
||||
got_mac = body["mac"]
|
||||
|
||||
want_mac = hmac.new(
|
||||
|
@ -171,6 +176,9 @@ class UserRegisterServlet(ClientV1RestServlet):
|
|||
want_mac.update(password)
|
||||
want_mac.update(b"\x00")
|
||||
want_mac.update(b"admin" if admin else b"notadmin")
|
||||
if user_type:
|
||||
want_mac.update(b"\x00")
|
||||
want_mac.update(user_type.encode('utf8'))
|
||||
want_mac = want_mac.hexdigest()
|
||||
|
||||
if not hmac.compare_digest(
|
||||
|
@ -189,6 +197,7 @@ class UserRegisterServlet(ClientV1RestServlet):
|
|||
password=body["password"],
|
||||
admin=bool(admin),
|
||||
generate_token=False,
|
||||
user_type=user_type,
|
||||
)
|
||||
|
||||
result = yield register._create_registration_details(user_id, body)
|
||||
|
|
|
@ -55,9 +55,12 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
txn,
|
||||
tp["medium"], tp["address"]
|
||||
)
|
||||
|
||||
if user_id:
|
||||
self.upsert_monthly_active_user_txn(txn, user_id)
|
||||
reserved_user_list.append(user_id)
|
||||
is_support = self.is_support_user_txn(txn, user_id)
|
||||
if not is_support:
|
||||
self.upsert_monthly_active_user_txn(txn, user_id)
|
||||
reserved_user_list.append(user_id)
|
||||
else:
|
||||
logger.warning(
|
||||
"mau limit reserved threepid %s not found in db" % tp
|
||||
|
@ -182,6 +185,18 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
Args:
|
||||
user_id (str): user to add/update
|
||||
"""
|
||||
# Support user never to be included in MAU stats. Note I can't easily call this
|
||||
# from upsert_monthly_active_user_txn because then I need a _txn form of
|
||||
# is_support_user which is complicated because I want to cache the result.
|
||||
# Therefore I call it here and ignore the case where
|
||||
# upsert_monthly_active_user_txn is called directly from
|
||||
# _initialise_reserved_users reasoning that it would be very strange to
|
||||
# include a support user in this context.
|
||||
|
||||
is_support = yield self.is_support_user(user_id)
|
||||
if is_support:
|
||||
return
|
||||
|
||||
is_insert = yield self.runInteraction(
|
||||
"upsert_monthly_active_user", self.upsert_monthly_active_user_txn,
|
||||
user_id
|
||||
|
@ -200,6 +215,16 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
in a database thread rather than the main thread, and we can't call
|
||||
txn.call_after because txn may not be a LoggingTransaction.
|
||||
|
||||
We consciously do not call is_support_txn from this method because it
|
||||
is not possible to cache the response. is_support_txn will be false in
|
||||
almost all cases, so it seems reasonable to call it only for
|
||||
upsert_monthly_active_user and to call is_support_txn manually
|
||||
for cases where upsert_monthly_active_user_txn is called directly,
|
||||
like _initialise_reserved_users
|
||||
|
||||
In short, don't call this method with support users. (Support users
|
||||
should not appear in the MAU stats).
|
||||
|
||||
Args:
|
||||
txn (cursor):
|
||||
user_id (str): user to add/update
|
||||
|
@ -208,6 +233,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
bool: True if a new entry was created, False if an
|
||||
existing one was updated.
|
||||
"""
|
||||
|
||||
# Am consciously deciding to lock the table on the basis that is ought
|
||||
# never be a big table and alternative approaches (batching multiple
|
||||
# upserts into a single txn) introduced a lot of extra complexity.
|
||||
|
|
|
@ -19,6 +19,7 @@ from six.moves import range
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.api.errors import Codes, StoreError
|
||||
from synapse.storage import background_updates
|
||||
from synapse.storage._base import SQLBaseStore
|
||||
|
@ -168,7 +169,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
|
||||
def register(self, user_id, token=None, password_hash=None,
|
||||
was_guest=False, make_guest=False, appservice_id=None,
|
||||
create_profile_with_displayname=None, admin=False):
|
||||
create_profile_with_displayname=None, admin=False, user_type=None):
|
||||
"""Attempts to register an account.
|
||||
|
||||
Args:
|
||||
|
@ -184,6 +185,10 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
appservice_id (str): The ID of the appservice registering the user.
|
||||
create_profile_with_displayname (unicode): Optionally create a profile for
|
||||
the user, setting their displayname to the given value
|
||||
admin (boolean): is an admin user?
|
||||
user_type (str|None): type of user. One of the values from
|
||||
api.constants.UserTypes, or None for a normal user.
|
||||
|
||||
Raises:
|
||||
StoreError if the user_id could not be registered.
|
||||
"""
|
||||
|
@ -197,7 +202,8 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
make_guest,
|
||||
appservice_id,
|
||||
create_profile_with_displayname,
|
||||
admin
|
||||
admin,
|
||||
user_type
|
||||
)
|
||||
|
||||
def _register(
|
||||
|
@ -211,6 +217,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
appservice_id,
|
||||
create_profile_with_displayname,
|
||||
admin,
|
||||
user_type,
|
||||
):
|
||||
user_id_obj = UserID.from_string(user_id)
|
||||
|
||||
|
@ -247,6 +254,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
"is_guest": 1 if make_guest else 0,
|
||||
"appservice_id": appservice_id,
|
||||
"admin": 1 if admin else 0,
|
||||
"user_type": user_type,
|
||||
}
|
||||
)
|
||||
else:
|
||||
|
@ -260,6 +268,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
"is_guest": 1 if make_guest else 0,
|
||||
"appservice_id": appservice_id,
|
||||
"admin": 1 if admin else 0,
|
||||
"user_type": user_type,
|
||||
}
|
||||
)
|
||||
except self.database_engine.module.IntegrityError:
|
||||
|
@ -456,6 +465,31 @@ class RegistrationStore(RegistrationWorkerStore,
|
|||
|
||||
defer.returnValue(res if res else False)
|
||||
|
||||
@cachedInlineCallbacks()
|
||||
def is_support_user(self, user_id):
|
||||
"""Determines if the user is of type UserTypes.SUPPORT
|
||||
|
||||
Args:
|
||||
user_id (str): user id to test
|
||||
|
||||
Returns:
|
||||
Deferred[bool]: True if user is of type UserTypes.SUPPORT
|
||||
"""
|
||||
res = yield self.runInteraction(
|
||||
"is_support_user", self.is_support_user_txn, user_id
|
||||
)
|
||||
defer.returnValue(res)
|
||||
|
||||
def is_support_user_txn(self, txn, user_id):
|
||||
res = self._simple_select_one_onecol_txn(
|
||||
txn=txn,
|
||||
table="users",
|
||||
keyvalues={"name": user_id},
|
||||
retcol="user_type",
|
||||
allow_none=True,
|
||||
)
|
||||
return True if res == UserTypes.SUPPORT else False
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def user_add_threepid(self, user_id, medium, address, validated_at, added_at):
|
||||
yield self._simple_upsert("user_threepids", {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* The type of the user: NULL for a regular user, or one of the constants in
|
||||
* synapse.api.constants.UserTypes
|
||||
*/
|
||||
ALTER TABLE users ADD COLUMN user_type TEXT DEFAULT NULL;
|
|
@ -50,6 +50,8 @@ class AuthTestCase(unittest.TestCase):
|
|||
# this is overridden for the appservice tests
|
||||
self.store.get_app_service_by_token = Mock(return_value=None)
|
||||
|
||||
self.store.is_support_user = Mock(return_value=defer.succeed(False))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_get_user_by_req_user_valid_token(self):
|
||||
user_info = {"name": self.test_user, "token_id": "ditto", "device_id": "device"}
|
||||
|
|
|
@ -17,7 +17,8 @@ from mock import Mock
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.errors import ResourceLimitError
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.api.errors import ResourceLimitError, SynapseError
|
||||
from synapse.handlers.register import RegistrationHandler
|
||||
from synapse.types import RoomAlias, UserID, create_requester
|
||||
|
||||
|
@ -64,6 +65,7 @@ class RegistrationTestCase(unittest.TestCase):
|
|||
requester, frank.localpart, "Frankie"
|
||||
)
|
||||
self.assertEquals(result_user_id, user_id)
|
||||
self.assertTrue(result_token is not None)
|
||||
self.assertEquals(result_token, 'secret')
|
||||
|
||||
@defer.inlineCallbacks
|
||||
|
@ -82,7 +84,7 @@ class RegistrationTestCase(unittest.TestCase):
|
|||
requester, local_part, None
|
||||
)
|
||||
self.assertEquals(result_user_id, user_id)
|
||||
self.assertEquals(result_token, 'secret')
|
||||
self.assertTrue(result_token is not None)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_mau_limits_when_disabled(self):
|
||||
|
@ -169,6 +171,20 @@ class RegistrationTestCase(unittest.TestCase):
|
|||
rooms = yield self.store.get_rooms_for_user(res[0])
|
||||
self.assertEqual(len(rooms), 0)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_auto_create_auto_join_rooms_when_support_user_exists(self):
|
||||
room_alias_str = "#room:test"
|
||||
self.hs.config.auto_join_rooms = [room_alias_str]
|
||||
|
||||
self.store.is_support_user = Mock(return_value=True)
|
||||
res = yield self.handler.register(localpart='support')
|
||||
rooms = yield self.store.get_rooms_for_user(res[0])
|
||||
self.assertEqual(len(rooms), 0)
|
||||
directory_handler = self.hs.get_handlers().directory_handler
|
||||
room_alias = RoomAlias.from_string(room_alias_str)
|
||||
with self.assertRaises(SynapseError):
|
||||
yield directory_handler.get_association(room_alias)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_auto_create_auto_join_where_no_consent(self):
|
||||
self.hs.config.user_consent_at_registration = True
|
||||
|
@ -179,3 +195,13 @@ class RegistrationTestCase(unittest.TestCase):
|
|||
yield self.handler.post_consent_actions(res[0])
|
||||
rooms = yield self.store.get_rooms_for_user(res[0])
|
||||
self.assertEqual(len(rooms), 0)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_register_support_user(self):
|
||||
res = yield self.handler.register(localpart='user', user_type=UserTypes.SUPPORT)
|
||||
self.assertTrue(self.store.is_support_user(res[0]))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_register_not_support_user(self):
|
||||
res = yield self.handler.register(localpart='user')
|
||||
self.assertFalse(self.store.is_support_user(res[0]))
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 New Vector
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from mock import Mock
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.handlers.user_directory import UserDirectoryHandler
|
||||
from synapse.storage.roommember import ProfileInfo
|
||||
|
||||
from tests import unittest
|
||||
from tests.utils import setup_test_homeserver
|
||||
|
||||
|
||||
class UserDirectoryHandlers(object):
|
||||
def __init__(self, hs):
|
||||
self.user_directory_handler = UserDirectoryHandler(hs)
|
||||
|
||||
|
||||
class UserDirectoryTestCase(unittest.TestCase):
|
||||
""" Tests the UserDirectoryHandler. """
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def setUp(self):
|
||||
hs = yield setup_test_homeserver(self.addCleanup)
|
||||
self.store = hs.get_datastore()
|
||||
hs.handlers = UserDirectoryHandlers(hs)
|
||||
|
||||
self.handler = hs.get_handlers().user_directory_handler
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_handle_local_profile_change_with_support_user(self):
|
||||
support_user_id = "@support:test"
|
||||
yield self.store.register(
|
||||
user_id=support_user_id,
|
||||
token="123",
|
||||
password_hash=None,
|
||||
user_type=UserTypes.SUPPORT
|
||||
)
|
||||
|
||||
yield self.handler.handle_local_profile_change(support_user_id, None)
|
||||
profile = yield self.store.get_user_in_directory(support_user_id)
|
||||
self.assertTrue(profile is None)
|
||||
display_name = 'display_name'
|
||||
|
||||
profile_info = ProfileInfo(
|
||||
avatar_url='avatar_url',
|
||||
display_name=display_name,
|
||||
)
|
||||
regular_user_id = '@regular:test'
|
||||
yield self.handler.handle_local_profile_change(regular_user_id, profile_info)
|
||||
profile = yield self.store.get_user_in_directory(regular_user_id)
|
||||
self.assertTrue(profile['display_name'] == display_name)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_handle_user_deactivated_support_user(self):
|
||||
s_user_id = "@support:test"
|
||||
self.store.register(
|
||||
user_id=s_user_id,
|
||||
token="123",
|
||||
password_hash=None,
|
||||
user_type=UserTypes.SUPPORT
|
||||
)
|
||||
|
||||
self.store.remove_from_user_dir = Mock()
|
||||
self.store.remove_from_user_in_public_room = Mock()
|
||||
yield self.handler.handle_user_deactivated(s_user_id)
|
||||
self.store.remove_from_user_dir.not_called()
|
||||
self.store.remove_from_user_in_public_room.not_called()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_handle_user_deactivated_regular_user(self):
|
||||
r_user_id = "@regular:test"
|
||||
self.store.register(user_id=r_user_id, token="123", password_hash=None)
|
||||
self.store.remove_from_user_dir = Mock()
|
||||
self.store.remove_from_user_in_public_room = Mock()
|
||||
yield self.handler.handle_user_deactivated(r_user_id)
|
||||
self.store.remove_from_user_dir.called_once_with(r_user_id)
|
||||
self.store.remove_from_user_in_public_room.assert_called_once_with(r_user_id)
|
|
@ -19,6 +19,7 @@ import json
|
|||
|
||||
from mock import Mock
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.rest.client.v1.admin import register_servlets
|
||||
|
||||
from tests import unittest
|
||||
|
@ -147,7 +148,9 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
nonce = channel.json_body["nonce"]
|
||||
|
||||
want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
|
||||
want_mac.update(nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin")
|
||||
want_mac.update(
|
||||
nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin\x00support"
|
||||
)
|
||||
want_mac = want_mac.hexdigest()
|
||||
|
||||
body = json.dumps(
|
||||
|
@ -156,6 +159,7 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
"username": "bob",
|
||||
"password": "abc123",
|
||||
"admin": True,
|
||||
"user_type": UserTypes.SUPPORT,
|
||||
"mac": want_mac,
|
||||
}
|
||||
)
|
||||
|
@ -174,7 +178,9 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
nonce = channel.json_body["nonce"]
|
||||
|
||||
want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
|
||||
want_mac.update(nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin")
|
||||
want_mac.update(
|
||||
nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin"
|
||||
)
|
||||
want_mac = want_mac.hexdigest()
|
||||
|
||||
body = json.dumps(
|
||||
|
@ -202,8 +208,8 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
def test_missing_parts(self):
|
||||
"""
|
||||
Synapse will complain if you don't give nonce, username, password, and
|
||||
mac. Admin is optional. Additional checks are done for length and
|
||||
type.
|
||||
mac. Admin and user_types are optional. Additional checks are done for length
|
||||
and type.
|
||||
"""
|
||||
|
||||
def nonce():
|
||||
|
@ -260,7 +266,7 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
self.assertEqual('Invalid username', channel.json_body["error"])
|
||||
|
||||
#
|
||||
# Username checks
|
||||
# Password checks
|
||||
#
|
||||
|
||||
# Must be present
|
||||
|
@ -296,3 +302,20 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual('Invalid password', channel.json_body["error"])
|
||||
|
||||
#
|
||||
# user_type check
|
||||
#
|
||||
|
||||
# Invalid user_type
|
||||
body = json.dumps({
|
||||
"nonce": nonce(),
|
||||
"username": "a",
|
||||
"password": "1234",
|
||||
"user_type": "invalid"}
|
||||
)
|
||||
request, channel = self.make_request("POST", self.url, body.encode('utf8'))
|
||||
self.render(request)
|
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual('Invalid user type', channel.json_body["error"])
|
||||
|
|
|
@ -16,6 +16,8 @@ from mock import Mock
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
|
||||
from tests.unittest import HomeserverTestCase
|
||||
|
||||
FORTY_DAYS = 40 * 24 * 60 * 60
|
||||
|
@ -28,6 +30,7 @@ class MonthlyActiveUsersTestCase(HomeserverTestCase):
|
|||
self.store = hs.get_datastore()
|
||||
hs.config.limit_usage_by_mau = True
|
||||
hs.config.max_mau_value = 50
|
||||
|
||||
# Advance the clock a bit
|
||||
reactor.advance(FORTY_DAYS)
|
||||
|
||||
|
@ -39,14 +42,23 @@ class MonthlyActiveUsersTestCase(HomeserverTestCase):
|
|||
user1_email = "user1@matrix.org"
|
||||
user2 = "@user2:server"
|
||||
user2_email = "user2@matrix.org"
|
||||
user3 = "@user3:server"
|
||||
user3_email = "user3@matrix.org"
|
||||
|
||||
threepids = [
|
||||
{'medium': 'email', 'address': user1_email},
|
||||
{'medium': 'email', 'address': user2_email},
|
||||
{'medium': 'email', 'address': user3_email},
|
||||
]
|
||||
user_num = len(threepids)
|
||||
# -1 because user3 is a support user and does not count
|
||||
user_num = len(threepids) - 1
|
||||
|
||||
self.store.register(user_id=user1, token="123", password_hash=None)
|
||||
self.store.register(user_id=user2, token="456", password_hash=None)
|
||||
self.store.register(
|
||||
user_id=user3, token="789",
|
||||
password_hash=None, user_type=UserTypes.SUPPORT
|
||||
)
|
||||
self.pump()
|
||||
|
||||
now = int(self.hs.get_clock().time_msec())
|
||||
|
@ -60,7 +72,7 @@ class MonthlyActiveUsersTestCase(HomeserverTestCase):
|
|||
|
||||
active_count = self.store.get_monthly_active_count()
|
||||
|
||||
# Test total counts
|
||||
# Test total counts, ensure user3 (support user) is not counted
|
||||
self.assertEquals(self.get_success(active_count), user_num)
|
||||
|
||||
# Test user is marked as active
|
||||
|
@ -221,6 +233,24 @@ class MonthlyActiveUsersTestCase(HomeserverTestCase):
|
|||
count = self.store.get_registered_reserved_users_count()
|
||||
self.assertEquals(self.get_success(count), len(threepids))
|
||||
|
||||
def test_support_user_not_add_to_mau_limits(self):
|
||||
support_user_id = "@support:test"
|
||||
count = self.store.get_monthly_active_count()
|
||||
self.pump()
|
||||
self.assertEqual(self.get_success(count), 0)
|
||||
|
||||
self.store.register(
|
||||
user_id=support_user_id,
|
||||
token="123",
|
||||
password_hash=None,
|
||||
user_type=UserTypes.SUPPORT
|
||||
)
|
||||
|
||||
self.store.upsert_monthly_active_user(support_user_id)
|
||||
count = self.store.get_monthly_active_count()
|
||||
self.pump()
|
||||
self.assertEqual(self.get_success(count), 0)
|
||||
|
||||
def test_track_monthly_users_without_cap(self):
|
||||
self.hs.config.limit_usage_by_mau = False
|
||||
self.hs.config.mau_stats_only = True
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
|
||||
from tests import unittest
|
||||
from tests.utils import setup_test_homeserver
|
||||
|
||||
|
@ -99,6 +101,26 @@ class RegistrationStoreTestCase(unittest.TestCase):
|
|||
user = yield self.store.get_user_by_access_token(self.tokens[0])
|
||||
self.assertIsNone(user, "access token was not deleted without device_id")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_is_support_user(self):
|
||||
TEST_USER = "@test:test"
|
||||
SUPPORT_USER = "@support:test"
|
||||
|
||||
res = yield self.store.is_support_user(None)
|
||||
self.assertFalse(res)
|
||||
yield self.store.register(user_id=TEST_USER, token="123", password_hash=None)
|
||||
res = yield self.store.is_support_user(TEST_USER)
|
||||
self.assertFalse(res)
|
||||
|
||||
yield self.store.register(
|
||||
user_id=SUPPORT_USER,
|
||||
token="456",
|
||||
password_hash=None,
|
||||
user_type=UserTypes.SUPPORT
|
||||
)
|
||||
res = yield self.store.is_support_user(SUPPORT_USER)
|
||||
self.assertTrue(res)
|
||||
|
||||
|
||||
class TokenGenerator:
|
||||
def __init__(self):
|
||||
|
|
|
@ -373,6 +373,7 @@ class HomeserverTestCase(TestCase):
|
|||
nonce_str += b"\x00admin"
|
||||
else:
|
||||
nonce_str += b"\x00notadmin"
|
||||
|
||||
want_mac.update(nonce.encode('ascii') + b"\x00" + nonce_str)
|
||||
want_mac = want_mac.hexdigest()
|
||||
|
||||
|
|
|
@ -140,7 +140,6 @@ def default_config(name):
|
|||
config.rc_messages_per_second = 10000
|
||||
config.rc_message_burst_count = 10000
|
||||
config.saml2_enabled = False
|
||||
|
||||
config.use_frozen_dicts = False
|
||||
|
||||
# we need a sane default_room_version, otherwise attempts to create rooms will
|
||||
|
|
Loading…
Reference in New Issue