Skip to content
Snippets Groups Projects
Unverified Commit 54dd2862 authored by Brendan Abolivier's avatar Brendan Abolivier Committed by GitHub
Browse files

Add options to disable setting profile info for prevent changes. (#7053)

parents 6640460d 751d51dd
No related branches found
No related tags found
No related merge requests found
Add options to prevent users from changing their profile or associated 3PIDs.
\ No newline at end of file
......@@ -1057,6 +1057,19 @@ account_threepid_delegates:
#email: https://example.com # Delegate email sending to example.com
#msisdn: http://localhost:8090 # Delegate SMS sending to this local process
# If disabled, don't let users set their own display names/avatars
# (unless they are a server admin) other than for the very first time.
# Useful when provisioning users based on the contents of a 3rd party
# directory and to avoid ambiguities.
#
#enable_set_displayname: true
#enable_set_avatar_url: true
# If false, stop users from trying to change the 3PIDs associated with
# their accounts.
#
#enable_3pid_changes: true
# Users who register on this homeserver will automatically be joined
# to these rooms
#
......
......@@ -129,6 +129,10 @@ class RegistrationConfig(Config):
raise ConfigError("Invalid auto_join_rooms entry %s" % (room_alias,))
self.autocreate_auto_join_rooms = config.get("autocreate_auto_join_rooms", True)
self.enable_set_displayname = config.get("enable_set_displayname", True)
self.enable_set_avatar_url = config.get("enable_set_avatar_url", True)
self.enable_3pid_changes = config.get("enable_3pid_changes", True)
self.disable_msisdn_registration = config.get(
"disable_msisdn_registration", False
)
......@@ -330,6 +334,19 @@ class RegistrationConfig(Config):
#email: https://example.com # Delegate email sending to example.com
#msisdn: http://localhost:8090 # Delegate SMS sending to this local process
# If disabled, don't let users set their own display names/avatars
# (unless they are a server admin) other than for the very first time.
# Useful when provisioning users based on the contents of a 3rd party
# directory and to avoid ambiguities.
#
#enable_set_displayname: true
#enable_set_avatar_url: true
# If false, stop users from trying to change the 3PIDs associated with
# their accounts.
#
#enable_3pid_changes: true
# Users who register on this homeserver will automatically be joined
# to these rooms
#
......
......@@ -157,6 +157,15 @@ class BaseProfileHandler(BaseHandler):
if not by_admin and target_user != requester.user:
raise AuthError(400, "Cannot set another user's displayname")
if not by_admin and not self.hs.config.enable_set_displayname:
profile = yield self.store.get_profileinfo(target_user.localpart)
if profile.display_name:
raise SynapseError(
400,
"Changing display name is disabled on this server",
Codes.FORBIDDEN,
)
if len(new_displayname) > MAX_DISPLAYNAME_LEN:
raise SynapseError(
400, "Displayname is too long (max %i)" % (MAX_DISPLAYNAME_LEN,)
......@@ -218,6 +227,13 @@ class BaseProfileHandler(BaseHandler):
if not by_admin and target_user != requester.user:
raise AuthError(400, "Cannot set another user's avatar_url")
if not by_admin and not self.hs.config.enable_set_avatar_url:
profile = yield self.store.get_profileinfo(target_user.localpart)
if profile.avatar_url:
raise SynapseError(
400, "Changing avatar is disabled on this server", Codes.FORBIDDEN
)
if len(new_avatar_url) > MAX_AVATAR_URL_LEN:
raise SynapseError(
400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN,)
......
......@@ -599,6 +599,11 @@ class ThreepidRestServlet(RestServlet):
return 200, {"threepids": threepids}
async def on_POST(self, request):
if not self.hs.config.enable_3pid_changes:
raise SynapseError(
400, "3PID changes are disabled on this server", Codes.FORBIDDEN
)
requester = await self.auth.get_user_by_req(request)
user_id = requester.user.to_string()
body = parse_json_object_from_request(request)
......@@ -643,6 +648,11 @@ class ThreepidAddRestServlet(RestServlet):
@interactive_auth_handler
async def on_POST(self, request):
if not self.hs.config.enable_3pid_changes:
raise SynapseError(
400, "3PID changes are disabled on this server", Codes.FORBIDDEN
)
requester = await self.auth.get_user_by_req(request)
user_id = requester.user.to_string()
body = parse_json_object_from_request(request)
......@@ -738,10 +748,16 @@ class ThreepidDeleteRestServlet(RestServlet):
def __init__(self, hs):
super(ThreepidDeleteRestServlet, self).__init__()
self.hs = hs
self.auth = hs.get_auth()
self.auth_handler = hs.get_auth_handler()
async def on_POST(self, request):
if not self.hs.config.enable_3pid_changes:
raise SynapseError(
400, "3PID changes are disabled on this server", Codes.FORBIDDEN
)
body = parse_json_object_from_request(request)
assert_params_in_dict(body, ["medium", "address"])
......
......@@ -19,7 +19,7 @@ from mock import Mock, NonCallableMock
from twisted.internet import defer
import synapse.types
from synapse.api.errors import AuthError
from synapse.api.errors import AuthError, SynapseError
from synapse.handlers.profile import MasterProfileHandler
from synapse.types import UserID
......@@ -70,6 +70,7 @@ class ProfileTestCase(unittest.TestCase):
yield self.store.create_profile(self.frank.localpart)
self.handler = hs.get_profile_handler()
self.hs = hs
@defer.inlineCallbacks
def test_get_my_name(self):
......@@ -90,6 +91,19 @@ class ProfileTestCase(unittest.TestCase):
"Frank Jr.",
)
@defer.inlineCallbacks
def test_set_my_name_if_disabled(self):
self.hs.config.enable_set_displayname = False
# Set first displayname is allowed, if displayname is null
yield self.store.set_profile_displayname(self.frank.localpart, "Frank")
d = self.handler.set_displayname(
self.frank, synapse.types.create_requester(self.frank), "Frank Jr."
)
yield self.assertFailure(d, SynapseError)
@defer.inlineCallbacks
def test_set_my_name_noauth(self):
d = self.handler.set_displayname(
......@@ -147,3 +161,20 @@ class ProfileTestCase(unittest.TestCase):
(yield self.store.get_profile_avatar_url(self.frank.localpart)),
"http://my.server/pic.gif",
)
@defer.inlineCallbacks
def test_set_my_avatar_if_disabled(self):
self.hs.config.enable_set_avatar_url = False
# Set first time avatar is allowed, if avatar is null
yield self.store.set_profile_avatar_url(
self.frank.localpart, "http://my.server/me.png"
)
d = self.handler.set_avatar_url(
self.frank,
synapse.types.create_requester(self.frank),
"http://my.server/pic.gif",
)
yield self.assertFailure(d, SynapseError)
......@@ -24,6 +24,7 @@ import pkg_resources
import synapse.rest.admin
from synapse.api.constants import LoginType, Membership
from synapse.api.errors import Codes
from synapse.rest.client.v1 import login, room
from synapse.rest.client.v2_alpha import account, register
......@@ -325,3 +326,305 @@ class DeactivateTestCase(unittest.HomeserverTestCase):
)
self.render(request)
self.assertEqual(request.code, 200)
class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
servlets = [
account.register_servlets,
login.register_servlets,
synapse.rest.admin.register_servlets_for_client_rest_resource,
]
def make_homeserver(self, reactor, clock):
config = self.default_config()
# Email config.
self.email_attempts = []
def sendmail(smtphost, from_addr, to_addrs, msg, **kwargs):
self.email_attempts.append(msg)
return
config["email"] = {
"enable_notifs": False,
"template_dir": os.path.abspath(
pkg_resources.resource_filename("synapse", "res/templates")
),
"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"] = "https://example.com"
self.hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
return self.hs
def prepare(self, reactor, clock, hs):
self.store = hs.get_datastore()
self.user_id = self.register_user("kermit", "test")
self.user_id_tok = self.login("kermit", "test")
self.email = "test@example.com"
self.url_3pid = b"account/3pid"
def test_add_email(self):
"""Test add mail to profile
"""
client_secret = "foobar"
session_id = self._request_token(self.email, client_secret)
self.assertEquals(len(self.email_attempts), 1)
link = self._get_link_from_email()
self._validate_token(link)
request, channel = self.make_request(
"POST",
b"/_matrix/client/unstable/account/3pid/add",
{
"client_secret": client_secret,
"sid": session_id,
"auth": {
"type": "m.login.password",
"user": self.user_id,
"password": "test",
},
},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
self.assertEqual(self.email, channel.json_body["threepids"][0]["address"])
def test_add_email_if_disabled(self):
"""Test add mail to profile if disabled
"""
self.hs.config.enable_3pid_changes = False
client_secret = "foobar"
session_id = self._request_token(self.email, client_secret)
self.assertEquals(len(self.email_attempts), 1)
link = self._get_link_from_email()
self._validate_token(link)
request, channel = self.make_request(
"POST",
b"/_matrix/client/unstable/account/3pid/add",
{
"client_secret": client_secret,
"sid": session_id,
"auth": {
"type": "m.login.password",
"user": self.user_id,
"password": "test",
},
},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertFalse(channel.json_body["threepids"])
def test_delete_email(self):
"""Test delete mail from profile
"""
# Add a threepid
self.get_success(
self.store.user_add_threepid(
user_id=self.user_id,
medium="email",
address=self.email,
validated_at=0,
added_at=0,
)
)
request, channel = self.make_request(
"POST",
b"account/3pid/delete",
{"medium": "email", "address": self.email},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertFalse(channel.json_body["threepids"])
def test_delete_email_if_disabled(self):
"""Test delete mail from profile if disabled
"""
self.hs.config.enable_3pid_changes = False
# Add a threepid
self.get_success(
self.store.user_add_threepid(
user_id=self.user_id,
medium="email",
address=self.email,
validated_at=0,
added_at=0,
)
)
request, channel = self.make_request(
"POST",
b"account/3pid/delete",
{"medium": "email", "address": self.email},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
self.assertEqual(self.email, channel.json_body["threepids"][0]["address"])
def test_cant_add_email_without_clicking_link(self):
"""Test that we do actually need to click the link in the email
"""
client_secret = "foobar"
session_id = self._request_token(self.email, client_secret)
self.assertEquals(len(self.email_attempts), 1)
# Attempt to add email without clicking the link
request, channel = self.make_request(
"POST",
b"/_matrix/client/unstable/account/3pid/add",
{
"client_secret": client_secret,
"sid": session_id,
"auth": {
"type": "m.login.password",
"user": self.user_id,
"password": "test",
},
},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertFalse(channel.json_body["threepids"])
def test_no_valid_token(self):
"""Test that we do actually need to request a token and can't just
make a session up.
"""
client_secret = "foobar"
session_id = "weasle"
# Attempt to add email without even requesting an email
request, channel = self.make_request(
"POST",
b"/_matrix/client/unstable/account/3pid/add",
{
"client_secret": client_secret,
"sid": session_id,
"auth": {
"type": "m.login.password",
"user": self.user_id,
"password": "test",
},
},
access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
# Get user
request, channel = self.make_request(
"GET", self.url_3pid, access_token=self.user_id_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertFalse(channel.json_body["threepids"])
def _request_token(self, email, client_secret):
request, channel = self.make_request(
"POST",
b"account/3pid/email/requestToken",
{"client_secret": client_secret, "email": email, "send_attempt": 1},
)
self.render(request)
self.assertEquals(200, channel.code, channel.result)
return channel.json_body["sid"]
def _validate_token(self, link):
# Remove the host
path = link.replace("https://example.com", "")
request, channel = self.make_request("GET", path, shorthand=False)
self.render(request)
self.assertEquals(200, channel.code, channel.result)
def _get_link_from_email(self):
assert self.email_attempts, "No emails have been sent"
raw_msg = self.email_attempts[-1].decode("UTF-8")
mail = Parser().parsestr(raw_msg)
text = None
for part in mail.walk():
if part.get_content_type() == "text/plain":
text = part.get_payload(decode=True).decode("UTF-8")
break
if not text:
self.fail("Could not find text portion of email to parse")
match = re.search(r"https://example.com\S+", text)
assert match, "Could not find link in email"
return match.group(0)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment