2019-05-17 20:37:31 +02:00
|
|
|
# Copyright 2014-2016 OpenMarket Ltd
|
|
|
|
# Copyright 2017-2018 New Vector Ltd
|
|
|
|
# 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.
|
2019-04-08 18:10:55 +02:00
|
|
|
import datetime
|
2018-07-09 08:09:20 +02:00
|
|
|
import json
|
2019-04-10 18:58:47 +02:00
|
|
|
import os
|
|
|
|
|
|
|
|
import pkg_resources
|
2018-07-09 08:09:20 +02:00
|
|
|
|
2019-05-01 16:32:38 +02:00
|
|
|
import synapse.rest.admin
|
2021-04-12 16:13:55 +02:00
|
|
|
from synapse.api.constants import APP_SERVICE_REGISTRATION_TYPE, LoginType
|
2019-04-08 18:10:55 +02:00
|
|
|
from synapse.api.errors import Codes
|
2019-02-18 17:49:38 +01:00
|
|
|
from synapse.appservice import ApplicationService
|
2020-05-14 17:32:49 +02:00
|
|
|
from synapse.rest.client.v1 import login, logout
|
2019-06-07 16:30:54 +02:00
|
|
|
from synapse.rest.client.v2_alpha import account, account_validity, register, sync
|
2018-07-09 08:09:20 +02:00
|
|
|
|
2015-07-28 18:34:12 +02:00
|
|
|
from tests import unittest
|
2020-06-05 11:47:20 +02:00
|
|
|
from tests.unittest import override_config
|
2015-07-28 18:34:12 +02:00
|
|
|
|
|
|
|
|
2018-11-06 17:00:00 +01:00
|
|
|
class RegisterRestServletTestCase(unittest.HomeserverTestCase):
|
|
|
|
|
2020-04-23 11:23:53 +02:00
|
|
|
servlets = [
|
|
|
|
login.register_servlets,
|
|
|
|
register.register_servlets,
|
|
|
|
synapse.rest.admin.register_servlets,
|
|
|
|
]
|
2019-09-25 12:32:05 +02:00
|
|
|
url = b"/_matrix/client/r0/register"
|
2018-11-06 17:00:00 +01:00
|
|
|
|
2020-03-24 19:33:49 +01:00
|
|
|
def default_config(self):
|
|
|
|
config = super().default_config()
|
2019-09-25 12:32:05 +02:00
|
|
|
config["allow_guest_access"] = True
|
|
|
|
return config
|
2015-07-28 18:34:12 +02:00
|
|
|
|
|
|
|
def test_POST_appservice_registration_valid(self):
|
2019-02-18 17:49:38 +01:00
|
|
|
user_id = "@as_user_kermit:test"
|
|
|
|
as_token = "i_am_an_app_service"
|
|
|
|
|
|
|
|
appservice = ApplicationService(
|
2019-05-10 07:12:11 +02:00
|
|
|
as_token,
|
|
|
|
self.hs.config.server_name,
|
2019-02-18 17:49:38 +01:00
|
|
|
id="1234",
|
2019-05-10 07:12:11 +02:00
|
|
|
namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
|
2020-10-29 16:58:44 +01:00
|
|
|
sender="@as:test",
|
2019-02-18 17:49:38 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
self.hs.get_datastore().services_cache.append(appservice)
|
2021-04-12 16:13:55 +02:00
|
|
|
request_data = json.dumps(
|
|
|
|
{"username": "as_user_kermit", "type": APP_SERVICE_REGISTRATION_TYPE}
|
|
|
|
)
|
2018-07-17 12:43:18 +02:00
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2018-07-17 12:43:18 +02:00
|
|
|
b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
|
2016-07-22 15:52:53 +02:00
|
|
|
)
|
2016-07-19 19:46:19 +02:00
|
|
|
|
2018-07-17 12:43:18 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
2019-05-10 07:12:11 +02:00
|
|
|
det_data = {"user_id": user_id, "home_server": self.hs.hostname}
|
2018-08-09 04:22:01 +02:00
|
|
|
self.assertDictContainsSubset(det_data, channel.json_body)
|
2015-07-28 18:34:12 +02:00
|
|
|
|
2021-04-12 16:13:55 +02:00
|
|
|
def test_POST_appservice_registration_no_type(self):
|
|
|
|
as_token = "i_am_an_app_service"
|
|
|
|
|
|
|
|
appservice = ApplicationService(
|
|
|
|
as_token,
|
|
|
|
self.hs.config.server_name,
|
|
|
|
id="1234",
|
|
|
|
namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
|
|
|
|
sender="@as:test",
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hs.get_datastore().services_cache.append(appservice)
|
|
|
|
request_data = json.dumps({"username": "as_user_kermit"})
|
|
|
|
|
|
|
|
channel = self.make_request(
|
|
|
|
b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"400", channel.result)
|
|
|
|
|
2015-07-28 18:34:12 +02:00
|
|
|
def test_POST_appservice_registration_invalid(self):
|
|
|
|
self.appservice = None # no application service exists
|
2021-04-12 16:13:55 +02:00
|
|
|
request_data = json.dumps(
|
|
|
|
{"username": "kermit", "type": APP_SERVICE_REGISTRATION_TYPE}
|
|
|
|
)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2018-07-17 12:43:18 +02:00
|
|
|
b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"401", channel.result)
|
2015-07-28 18:34:12 +02:00
|
|
|
|
|
|
|
def test_POST_bad_password(self):
|
2018-07-17 12:43:18 +02:00
|
|
|
request_data = json.dumps({"username": "kermit", "password": 666})
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, request_data)
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"400", channel.result)
|
2018-08-10 15:54:09 +02:00
|
|
|
self.assertEquals(channel.json_body["error"], "Invalid password")
|
2015-07-28 18:34:12 +02:00
|
|
|
|
|
|
|
def test_POST_bad_username(self):
|
2018-07-17 12:43:18 +02:00
|
|
|
request_data = json.dumps({"username": 777, "password": "monkey"})
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, request_data)
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"400", channel.result)
|
2018-08-10 15:54:09 +02:00
|
|
|
self.assertEquals(channel.json_body["error"], "Invalid username")
|
2018-07-17 12:43:18 +02:00
|
|
|
|
2015-07-28 18:34:12 +02:00
|
|
|
def test_POST_user_valid(self):
|
2019-02-18 17:49:38 +01:00
|
|
|
user_id = "@kermit:test"
|
2016-07-19 19:38:26 +02:00
|
|
|
device_id = "frogfone"
|
2019-02-18 17:49:38 +01:00
|
|
|
params = {
|
|
|
|
"username": "kermit",
|
|
|
|
"password": "monkey",
|
|
|
|
"device_id": device_id,
|
|
|
|
"auth": {"type": LoginType.DUMMY},
|
|
|
|
}
|
2019-02-18 14:43:16 +01:00
|
|
|
request_data = json.dumps(params)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, request_data)
|
2015-07-28 18:34:12 +02:00
|
|
|
|
2016-03-10 00:08:37 +01:00
|
|
|
det_data = {
|
2016-03-10 10:13:35 +01:00
|
|
|
"user_id": user_id,
|
2016-07-19 19:38:26 +02:00
|
|
|
"home_server": self.hs.hostname,
|
|
|
|
"device_id": device_id,
|
2016-03-10 00:08:37 +01:00
|
|
|
}
|
2018-07-17 12:43:18 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
2018-08-09 04:22:01 +02:00
|
|
|
self.assertDictContainsSubset(det_data, channel.json_body)
|
2015-07-28 18:34:12 +02:00
|
|
|
|
2020-08-06 14:09:55 +02:00
|
|
|
@override_config({"enable_registration": False})
|
2015-07-28 18:34:12 +02:00
|
|
|
def test_POST_disabled_registration(self):
|
2018-07-17 12:43:18 +02:00
|
|
|
request_data = json.dumps({"username": "kermit", "password": "monkey"})
|
|
|
|
self.auth_result = (None, {"username": "kermit", "password": "monkey"}, None)
|
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, request_data)
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"403", channel.result)
|
2018-08-10 15:54:09 +02:00
|
|
|
self.assertEquals(channel.json_body["error"], "Registration has been disabled")
|
2020-12-03 16:41:19 +01:00
|
|
|
self.assertEquals(channel.json_body["errcode"], "M_FORBIDDEN")
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
def test_POST_guest_registration(self):
|
|
|
|
self.hs.config.macaroon_secret_key = "test"
|
|
|
|
self.hs.config.allow_guest_access = True
|
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
|
2018-07-17 12:43:18 +02:00
|
|
|
|
2019-05-10 07:12:11 +02:00
|
|
|
det_data = {"home_server": self.hs.hostname, "device_id": "guest_device"}
|
2018-07-17 12:43:18 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
2018-08-09 04:22:01 +02:00
|
|
|
self.assertDictContainsSubset(det_data, channel.json_body)
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
def test_POST_disabled_guest_registration(self):
|
|
|
|
self.hs.config.allow_guest_access = False
|
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
|
2018-07-17 12:43:18 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"403", channel.result)
|
2018-08-10 15:54:09 +02:00
|
|
|
self.assertEquals(channel.json_body["error"], "Guest access is disabled")
|
2019-03-05 15:25:33 +01:00
|
|
|
|
2020-06-05 11:47:20 +02:00
|
|
|
@override_config({"rc_registration": {"per_second": 0.17, "burst_count": 5}})
|
2019-03-05 15:25:33 +01:00
|
|
|
def test_POST_ratelimiting_guest(self):
|
|
|
|
for i in range(0, 6):
|
|
|
|
url = self.url + b"?kind=guest"
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", url, b"{}")
|
2019-03-05 15:25:33 +01:00
|
|
|
|
|
|
|
if i == 5:
|
|
|
|
self.assertEquals(channel.result["code"], b"429", channel.result)
|
|
|
|
retry_after_ms = int(channel.json_body["retry_after_ms"])
|
|
|
|
else:
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
2020-08-19 14:07:57 +02:00
|
|
|
self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
|
2019-03-05 15:25:33 +01:00
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
|
2019-03-05 15:25:33 +01:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
2020-06-05 11:47:20 +02:00
|
|
|
@override_config({"rc_registration": {"per_second": 0.17, "burst_count": 5}})
|
2019-03-05 15:25:33 +01:00
|
|
|
def test_POST_ratelimiting(self):
|
|
|
|
for i in range(0, 6):
|
|
|
|
params = {
|
|
|
|
"username": "kermit" + str(i),
|
|
|
|
"password": "monkey",
|
|
|
|
"device_id": "frogfone",
|
|
|
|
"auth": {"type": LoginType.DUMMY},
|
|
|
|
}
|
|
|
|
request_data = json.dumps(params)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, request_data)
|
2019-03-05 15:25:33 +01:00
|
|
|
|
|
|
|
if i == 5:
|
|
|
|
self.assertEquals(channel.result["code"], b"429", channel.result)
|
|
|
|
retry_after_ms = int(channel.json_body["retry_after_ms"])
|
|
|
|
else:
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
2020-08-19 14:07:57 +02:00
|
|
|
self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
|
2019-03-05 15:25:33 +01:00
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
|
2019-03-05 15:25:33 +01:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
2019-04-08 18:10:55 +02:00
|
|
|
|
2019-09-25 12:32:05 +02:00
|
|
|
def test_advertised_flows(self):
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, b"{}")
|
2019-09-25 12:32:05 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"401", channel.result)
|
|
|
|
flows = channel.json_body["flows"]
|
|
|
|
|
2019-09-25 13:10:26 +02:00
|
|
|
# with the stock config, we only expect the dummy flow
|
|
|
|
self.assertCountEqual([["m.login.dummy"]], (f["stages"] for f in flows))
|
2019-09-25 12:32:05 +02:00
|
|
|
|
|
|
|
@unittest.override_config(
|
|
|
|
{
|
2019-11-26 19:42:27 +01:00
|
|
|
"public_baseurl": "https://test_server",
|
2019-09-25 12:32:05 +02:00
|
|
|
"enable_registration_captcha": True,
|
|
|
|
"user_consent": {
|
|
|
|
"version": "1",
|
|
|
|
"template_dir": "/",
|
|
|
|
"require_at_registration": True,
|
|
|
|
},
|
2019-09-25 13:10:26 +02:00
|
|
|
"account_threepid_delegates": {
|
|
|
|
"email": "https://id_server",
|
|
|
|
"msisdn": "https://id_server",
|
|
|
|
},
|
2019-09-25 12:32:05 +02:00
|
|
|
}
|
|
|
|
)
|
2019-09-25 13:10:26 +02:00
|
|
|
def test_advertised_flows_captcha_and_terms_and_3pids(self):
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, b"{}")
|
2019-09-25 12:32:05 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"401", channel.result)
|
|
|
|
flows = channel.json_body["flows"]
|
|
|
|
|
|
|
|
self.assertCountEqual(
|
|
|
|
[
|
|
|
|
["m.login.recaptcha", "m.login.terms", "m.login.dummy"],
|
|
|
|
["m.login.recaptcha", "m.login.terms", "m.login.email.identity"],
|
|
|
|
["m.login.recaptcha", "m.login.terms", "m.login.msisdn"],
|
|
|
|
[
|
|
|
|
"m.login.recaptcha",
|
|
|
|
"m.login.terms",
|
|
|
|
"m.login.msisdn",
|
|
|
|
"m.login.email.identity",
|
|
|
|
],
|
|
|
|
],
|
|
|
|
(f["stages"] for f in flows),
|
|
|
|
)
|
|
|
|
|
|
|
|
@unittest.override_config(
|
2019-09-25 13:10:26 +02:00
|
|
|
{
|
|
|
|
"public_baseurl": "https://test_server",
|
|
|
|
"registrations_require_3pid": ["email"],
|
|
|
|
"disable_msisdn_registration": True,
|
|
|
|
"email": {
|
|
|
|
"smtp_host": "mail_server",
|
|
|
|
"smtp_port": 2525,
|
|
|
|
"notif_from": "sender@host",
|
|
|
|
},
|
|
|
|
}
|
2019-09-25 12:32:05 +02:00
|
|
|
)
|
|
|
|
def test_advertised_flows_no_msisdn_email_required(self):
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", self.url, b"{}")
|
2019-09-25 12:32:05 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"401", channel.result)
|
|
|
|
flows = channel.json_body["flows"]
|
|
|
|
|
|
|
|
# with the stock config, we expect all four combinations of 3pid
|
|
|
|
self.assertCountEqual(
|
|
|
|
[["m.login.email.identity"]], (f["stages"] for f in flows)
|
|
|
|
)
|
|
|
|
|
2020-04-23 11:23:53 +02:00
|
|
|
@unittest.override_config(
|
|
|
|
{
|
|
|
|
"request_token_inhibit_3pid_errors": True,
|
|
|
|
"public_baseurl": "https://test_server",
|
|
|
|
"email": {
|
|
|
|
"smtp_host": "mail_server",
|
|
|
|
"smtp_port": 2525,
|
|
|
|
"notif_from": "sender@host",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
def test_request_token_existing_email_inhibit_error(self):
|
|
|
|
"""Test that requesting a token via this endpoint doesn't leak existing
|
|
|
|
associations if configured that way.
|
|
|
|
"""
|
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
email = "test@example.com"
|
|
|
|
|
|
|
|
# Add a threepid
|
|
|
|
self.get_success(
|
|
|
|
self.hs.get_datastore().user_add_threepid(
|
|
|
|
user_id=user_id,
|
|
|
|
medium="email",
|
|
|
|
address=email,
|
|
|
|
validated_at=0,
|
|
|
|
added_at=0,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2020-04-23 11:23:53 +02:00
|
|
|
"POST",
|
|
|
|
b"register/email/requestToken",
|
|
|
|
{"client_secret": "foobar", "email": email, "send_attempt": 1},
|
|
|
|
)
|
|
|
|
self.assertEquals(200, channel.code, channel.result)
|
|
|
|
|
|
|
|
self.assertIsNotNone(channel.json_body.get("sid"))
|
|
|
|
|
2019-04-08 18:10:55 +02:00
|
|
|
|
|
|
|
class AccountValidityTestCase(unittest.HomeserverTestCase):
|
|
|
|
|
|
|
|
servlets = [
|
|
|
|
register.register_servlets,
|
2019-05-02 12:59:16 +02:00
|
|
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
2019-04-08 18:10:55 +02:00
|
|
|
login.register_servlets,
|
|
|
|
sync.register_servlets,
|
2020-05-14 17:32:49 +02:00
|
|
|
logout.register_servlets,
|
2019-04-16 21:13:59 +02:00
|
|
|
account_validity.register_servlets,
|
2019-04-08 18:10:55 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
def make_homeserver(self, reactor, clock):
|
|
|
|
config = self.default_config()
|
2019-04-10 18:58:47 +02:00
|
|
|
# Test for account expiring after a week.
|
2019-05-13 22:01:14 +02:00
|
|
|
config["enable_registration"] = True
|
|
|
|
config["account_validity"] = {
|
|
|
|
"enabled": True,
|
|
|
|
"period": 604800000, # Time in ms for 1 week
|
|
|
|
}
|
2019-04-08 18:10:55 +02:00
|
|
|
self.hs = self.setup_test_homeserver(config=config)
|
|
|
|
|
|
|
|
return self.hs
|
|
|
|
|
|
|
|
def test_validity_period(self):
|
|
|
|
self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
# The specific endpoint doesn't matter, all we need is an authenticated
|
|
|
|
# endpoint.
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", "/sync", access_token=tok)
|
2019-04-08 18:10:55 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
|
|
|
|
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", "/sync", access_token=tok)
|
2019-04-08 18:10:55 +02:00
|
|
|
|
|
|
|
self.assertEquals(channel.result["code"], b"403", channel.result)
|
|
|
|
self.assertEquals(
|
2019-05-10 07:12:11 +02:00
|
|
|
channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
|
2019-04-08 18:10:55 +02:00
|
|
|
)
|
2019-04-10 18:58:47 +02:00
|
|
|
|
2019-04-16 21:13:59 +02:00
|
|
|
def test_manual_renewal(self):
|
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
|
|
|
|
|
|
|
|
# If we register the admin user at the beginning of the test, it will
|
|
|
|
# expire at the same time as the normal user and the renewal request
|
|
|
|
# will be denied.
|
|
|
|
self.register_user("admin", "adminpassword", admin=True)
|
|
|
|
admin_tok = self.login("admin", "adminpassword")
|
|
|
|
|
2020-11-25 22:26:11 +01:00
|
|
|
url = "/_synapse/admin/v1/account_validity/validity"
|
2019-05-10 07:12:11 +02:00
|
|
|
params = {"user_id": user_id}
|
2019-04-16 21:13:59 +02:00
|
|
|
request_data = json.dumps(params)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
|
2019-04-16 21:13:59 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
# The specific endpoint doesn't matter, all we need is an authenticated
|
|
|
|
# endpoint.
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", "/sync", access_token=tok)
|
2019-04-16 21:13:59 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
def test_manual_expire(self):
|
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
self.register_user("admin", "adminpassword", admin=True)
|
|
|
|
admin_tok = self.login("admin", "adminpassword")
|
|
|
|
|
2020-11-25 22:26:11 +01:00
|
|
|
url = "/_synapse/admin/v1/account_validity/validity"
|
2019-04-16 21:13:59 +02:00
|
|
|
params = {
|
|
|
|
"user_id": user_id,
|
|
|
|
"expiration_ts": 0,
|
|
|
|
"enable_renewal_emails": False,
|
|
|
|
}
|
|
|
|
request_data = json.dumps(params)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
|
2019-04-16 21:13:59 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
# The specific endpoint doesn't matter, all we need is an authenticated
|
|
|
|
# endpoint.
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", "/sync", access_token=tok)
|
2019-04-16 21:13:59 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"403", channel.result)
|
|
|
|
self.assertEquals(
|
2019-05-10 07:12:11 +02:00
|
|
|
channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
|
2019-04-16 21:13:59 +02:00
|
|
|
)
|
|
|
|
|
2020-05-14 17:32:49 +02:00
|
|
|
def test_logging_out_expired_user(self):
|
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
self.register_user("admin", "adminpassword", admin=True)
|
|
|
|
admin_tok = self.login("admin", "adminpassword")
|
|
|
|
|
2020-11-25 22:26:11 +01:00
|
|
|
url = "/_synapse/admin/v1/account_validity/validity"
|
2020-05-14 17:32:49 +02:00
|
|
|
params = {
|
|
|
|
"user_id": user_id,
|
|
|
|
"expiration_ts": 0,
|
|
|
|
"enable_renewal_emails": False,
|
|
|
|
}
|
|
|
|
request_data = json.dumps(params)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
|
2020-05-14 17:32:49 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
# Try to log the user out
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", "/logout", access_token=tok)
|
2020-05-14 17:32:49 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
# Log the user in again (allowed for expired accounts)
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
# Try to log out all of the user's sessions
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"POST", "/logout/all", access_token=tok)
|
2020-05-14 17:32:49 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
2019-04-10 18:58:47 +02:00
|
|
|
|
|
|
|
class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
|
|
|
|
|
|
|
|
servlets = [
|
|
|
|
register.register_servlets,
|
2019-05-02 12:59:16 +02:00
|
|
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
2019-04-10 18:58:47 +02:00
|
|
|
login.register_servlets,
|
|
|
|
sync.register_servlets,
|
|
|
|
account_validity.register_servlets,
|
2019-06-07 16:30:54 +02:00
|
|
|
account.register_servlets,
|
2019-04-10 18:58:47 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
def make_homeserver(self, reactor, clock):
|
|
|
|
config = self.default_config()
|
2019-05-13 22:01:14 +02:00
|
|
|
|
2019-04-10 18:58:47 +02:00
|
|
|
# Test for account expiring after a week and renewal emails being sent 2
|
|
|
|
# days before expiry.
|
2019-05-13 22:01:14 +02:00
|
|
|
config["enable_registration"] = True
|
|
|
|
config["account_validity"] = {
|
|
|
|
"enabled": True,
|
|
|
|
"period": 604800000, # Time in ms for 1 week
|
|
|
|
"renew_at": 172800000, # Time in ms for 2 days
|
|
|
|
"renew_by_email_enabled": True,
|
|
|
|
"renew_email_subject": "Renew your account",
|
2019-08-01 12:00:08 +02:00
|
|
|
"account_renewed_html_path": "account_renewed.html",
|
|
|
|
"invalid_token_html_path": "invalid_token.html",
|
2019-05-13 22:01:14 +02:00
|
|
|
}
|
2019-04-10 18:58:47 +02:00
|
|
|
|
|
|
|
# Email config.
|
|
|
|
self.email_attempts = []
|
|
|
|
|
2019-12-10 12:13:15 +01:00
|
|
|
async def sendmail(*args, **kwargs):
|
2019-04-10 18:58:47 +02:00
|
|
|
self.email_attempts.append((args, kwargs))
|
|
|
|
|
2019-05-13 22:01:14 +02:00
|
|
|
config["email"] = {
|
|
|
|
"enable_notifs": True,
|
|
|
|
"template_dir": os.path.abspath(
|
|
|
|
pkg_resources.resource_filename("synapse", "res/templates")
|
|
|
|
),
|
|
|
|
"expiry_template_html": "notice_expiry.html",
|
|
|
|
"expiry_template_text": "notice_expiry.txt",
|
|
|
|
"notif_template_html": "notif_mail.html",
|
|
|
|
"notif_template_text": "notif_mail.txt",
|
|
|
|
"smtp_host": "127.0.0.1",
|
|
|
|
"smtp_port": 20,
|
|
|
|
"require_transport_security": False,
|
|
|
|
"smtp_user": None,
|
|
|
|
"smtp_pass": None,
|
|
|
|
"notif_from": "test@example.com",
|
|
|
|
}
|
|
|
|
config["public_baseurl"] = "aaa"
|
2019-04-10 18:58:47 +02:00
|
|
|
|
|
|
|
self.hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
|
|
|
|
|
|
|
|
self.store = self.hs.get_datastore()
|
|
|
|
|
|
|
|
return self.hs
|
|
|
|
|
|
|
|
def test_renewal_email(self):
|
2019-04-16 21:13:59 +02:00
|
|
|
self.email_attempts = []
|
|
|
|
|
2019-06-07 16:30:54 +02:00
|
|
|
(user_id, tok) = self.create_user()
|
2019-04-10 18:58:47 +02:00
|
|
|
|
2021-04-19 20:16:34 +02:00
|
|
|
# Move 5 days forward. This should trigger a renewal email to be sent.
|
|
|
|
self.reactor.advance(datetime.timedelta(days=5).total_seconds())
|
2019-04-10 18:58:47 +02:00
|
|
|
self.assertEqual(len(self.email_attempts), 1)
|
|
|
|
|
|
|
|
# Retrieving the URL from the email is too much pain for now, so we
|
|
|
|
# retrieve the token from the DB.
|
|
|
|
renewal_token = self.get_success(self.store.get_renewal_token_for_user(user_id))
|
|
|
|
url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", url)
|
2019-04-10 18:58:47 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
2019-08-01 12:00:08 +02:00
|
|
|
# Check that we're getting HTML back.
|
2021-04-19 20:16:34 +02:00
|
|
|
content_type = channel.headers.getRawHeaders(b"Content-Type")
|
|
|
|
self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
|
2019-08-01 12:00:08 +02:00
|
|
|
|
|
|
|
# Check that the HTML we're getting is the one we expect on a successful renewal.
|
2021-04-19 20:16:34 +02:00
|
|
|
expiration_ts = self.get_success(self.store.get_expiration_ts_for_user(user_id))
|
|
|
|
expected_html = self.hs.config.account_validity.account_validity_account_renewed_template.render(
|
|
|
|
expiration_ts=expiration_ts
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
channel.result["body"], expected_html.encode("utf8"), channel.result
|
|
|
|
)
|
|
|
|
|
|
|
|
# Move 1 day forward. Try to renew with the same token again.
|
|
|
|
url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
|
|
|
|
channel = self.make_request(b"GET", url)
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
# Check that we're getting HTML back.
|
|
|
|
content_type = channel.headers.getRawHeaders(b"Content-Type")
|
|
|
|
self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
|
|
|
|
|
|
|
|
# Check that the HTML we're getting is the one we expect when reusing a
|
|
|
|
# token. The account expiration date should not have changed.
|
|
|
|
expected_html = self.hs.config.account_validity.account_validity_account_previously_renewed_template.render(
|
|
|
|
expiration_ts=expiration_ts
|
|
|
|
)
|
2019-08-01 12:00:08 +02:00
|
|
|
self.assertEqual(
|
|
|
|
channel.result["body"], expected_html.encode("utf8"), channel.result
|
|
|
|
)
|
|
|
|
|
2019-04-10 18:58:47 +02:00
|
|
|
# Move 3 days forward. If the renewal failed, every authed request with
|
|
|
|
# our access token should be denied from now, otherwise they should
|
|
|
|
# succeed.
|
|
|
|
self.reactor.advance(datetime.timedelta(days=3).total_seconds())
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", "/sync", access_token=tok)
|
2019-04-10 18:58:47 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
2019-04-16 21:13:59 +02:00
|
|
|
|
2019-08-01 12:00:08 +02:00
|
|
|
def test_renewal_invalid_token(self):
|
|
|
|
# Hit the renewal endpoint with an invalid token and check that it behaves as
|
|
|
|
# expected, i.e. that it responds with 404 Not Found and the correct HTML.
|
|
|
|
url = "/_matrix/client/unstable/account_validity/renew?token=123"
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(b"GET", url)
|
2019-08-01 12:00:08 +02:00
|
|
|
self.assertEquals(channel.result["code"], b"404", channel.result)
|
|
|
|
|
|
|
|
# Check that we're getting HTML back.
|
2021-04-19 20:16:34 +02:00
|
|
|
content_type = channel.headers.getRawHeaders(b"Content-Type")
|
|
|
|
self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
|
2019-08-01 12:00:08 +02:00
|
|
|
|
|
|
|
# Check that the HTML we're getting is the one we expect when using an
|
|
|
|
# invalid/unknown token.
|
2021-04-19 20:16:34 +02:00
|
|
|
expected_html = (
|
|
|
|
self.hs.config.account_validity.account_validity_invalid_token_template.render()
|
|
|
|
)
|
2019-08-01 12:00:08 +02:00
|
|
|
self.assertEqual(
|
|
|
|
channel.result["body"], expected_html.encode("utf8"), channel.result
|
|
|
|
)
|
|
|
|
|
2019-04-16 21:13:59 +02:00
|
|
|
def test_manual_email_send(self):
|
|
|
|
self.email_attempts = []
|
|
|
|
|
2019-06-07 16:30:54 +02:00
|
|
|
(user_id, tok) = self.create_user()
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2019-06-07 16:30:54 +02:00
|
|
|
b"POST",
|
|
|
|
"/_matrix/client/unstable/account_validity/send_mail",
|
|
|
|
access_token=tok,
|
|
|
|
)
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
self.assertEqual(len(self.email_attempts), 1)
|
|
|
|
|
|
|
|
def test_deactivated_user(self):
|
|
|
|
self.email_attempts = []
|
|
|
|
|
|
|
|
(user_id, tok) = self.create_user()
|
|
|
|
|
|
|
|
request_data = json.dumps(
|
|
|
|
{
|
|
|
|
"auth": {
|
|
|
|
"type": "m.login.password",
|
|
|
|
"user": user_id,
|
|
|
|
"password": "monkey",
|
|
|
|
},
|
|
|
|
"erase": False,
|
|
|
|
}
|
|
|
|
)
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2019-06-07 16:30:54 +02:00
|
|
|
"POST", "account/deactivate", request_data, access_token=tok
|
|
|
|
)
|
2020-12-15 15:51:25 +01:00
|
|
|
self.assertEqual(channel.code, 200)
|
2019-06-07 16:30:54 +02:00
|
|
|
|
|
|
|
self.reactor.advance(datetime.timedelta(days=8).total_seconds())
|
|
|
|
|
|
|
|
self.assertEqual(len(self.email_attempts), 0)
|
|
|
|
|
|
|
|
def create_user(self):
|
2019-04-16 21:13:59 +02:00
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
# We need to manually add an email address otherwise the handler will do
|
|
|
|
# nothing.
|
2020-11-30 19:28:44 +01:00
|
|
|
now = self.hs.get_clock().time_msec()
|
2019-05-10 07:12:11 +02:00
|
|
|
self.get_success(
|
|
|
|
self.store.user_add_threepid(
|
|
|
|
user_id=user_id,
|
|
|
|
medium="email",
|
|
|
|
address="kermit@example.com",
|
|
|
|
validated_at=now,
|
|
|
|
added_at=now,
|
|
|
|
)
|
|
|
|
)
|
2019-08-30 17:28:26 +02:00
|
|
|
return user_id, tok
|
2019-05-17 20:37:31 +02:00
|
|
|
|
2019-06-05 17:35:05 +02:00
|
|
|
def test_manual_email_send_expired_account(self):
|
|
|
|
user_id = self.register_user("kermit", "monkey")
|
|
|
|
tok = self.login("kermit", "monkey")
|
|
|
|
|
|
|
|
# We need to manually add an email address otherwise the handler will do
|
|
|
|
# nothing.
|
2020-11-30 19:28:44 +01:00
|
|
|
now = self.hs.get_clock().time_msec()
|
2019-06-05 17:35:05 +02:00
|
|
|
self.get_success(
|
|
|
|
self.store.user_add_threepid(
|
|
|
|
user_id=user_id,
|
|
|
|
medium="email",
|
|
|
|
address="kermit@example.com",
|
|
|
|
validated_at=now,
|
|
|
|
added_at=now,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
# Make the account expire.
|
|
|
|
self.reactor.advance(datetime.timedelta(days=8).total_seconds())
|
|
|
|
|
|
|
|
# Ignore all emails sent by the automatic background task and only focus on the
|
|
|
|
# ones sent manually.
|
|
|
|
self.email_attempts = []
|
|
|
|
|
|
|
|
# Test that we're still able to manually trigger a mail to be sent.
|
2020-12-15 15:44:04 +01:00
|
|
|
channel = self.make_request(
|
2019-06-05 17:35:05 +02:00
|
|
|
b"POST",
|
|
|
|
"/_matrix/client/unstable/account_validity/send_mail",
|
|
|
|
access_token=tok,
|
|
|
|
)
|
|
|
|
self.assertEquals(channel.result["code"], b"200", channel.result)
|
|
|
|
|
|
|
|
self.assertEqual(len(self.email_attempts), 1)
|
|
|
|
|
2019-05-17 20:37:31 +02:00
|
|
|
|
|
|
|
class AccountValidityBackgroundJobTestCase(unittest.HomeserverTestCase):
|
|
|
|
|
|
|
|
servlets = [synapse.rest.admin.register_servlets_for_client_rest_resource]
|
|
|
|
|
|
|
|
def make_homeserver(self, reactor, clock):
|
|
|
|
self.validity_period = 10
|
2019-05-31 10:54:46 +02:00
|
|
|
self.max_delta = self.validity_period * 10.0 / 100.0
|
2019-05-17 20:37:31 +02:00
|
|
|
|
|
|
|
config = self.default_config()
|
|
|
|
|
|
|
|
config["enable_registration"] = True
|
|
|
|
config["account_validity"] = {"enabled": False}
|
|
|
|
|
|
|
|
self.hs = self.setup_test_homeserver(config=config)
|
2021-04-19 20:16:34 +02:00
|
|
|
|
|
|
|
# We need to set these directly, instead of in the homeserver config dict above.
|
|
|
|
# This is due to account validity-related config options not being read by
|
|
|
|
# Synapse when account_validity.enabled is False.
|
|
|
|
self.hs.get_datastore()._account_validity_period = self.validity_period
|
|
|
|
self.hs.get_datastore()._account_validity_startup_job_max_delta = self.max_delta
|
2019-05-17 20:37:31 +02:00
|
|
|
|
|
|
|
self.store = self.hs.get_datastore()
|
|
|
|
|
|
|
|
return self.hs
|
|
|
|
|
|
|
|
def test_background_job(self):
|
2019-05-28 17:47:42 +02:00
|
|
|
"""
|
|
|
|
Tests the same thing as test_background_job, except that it sets the
|
|
|
|
startup_job_max_delta parameter and checks that the expiration date is within the
|
|
|
|
allowed range.
|
|
|
|
"""
|
|
|
|
user_id = self.register_user("kermit_delta", "user")
|
|
|
|
|
|
|
|
self.hs.config.account_validity.startup_job_max_delta = self.max_delta
|
|
|
|
|
2020-11-30 19:28:44 +01:00
|
|
|
now_ms = self.hs.get_clock().time_msec()
|
2019-05-28 17:47:42 +02:00
|
|
|
self.get_success(self.store._set_expiration_date_when_missing())
|
|
|
|
|
|
|
|
res = self.get_success(self.store.get_expiration_ts_for_user(user_id))
|
|
|
|
|
2019-05-31 12:09:34 +02:00
|
|
|
self.assertGreaterEqual(res, now_ms + self.validity_period - self.max_delta)
|
|
|
|
self.assertLessEqual(res, now_ms + self.validity_period)
|