-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Update the check whether a password may be set #9636
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Checks if passwords are allowed before setting it for the user. | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1210,6 +1210,7 @@ def set_user_deactivated_status_txn(self, txn, user_id: str, deactivated: bool): | |
self._invalidate_cache_and_stream( | ||
txn, self.get_user_deactivated_status, (user_id,) | ||
) | ||
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,)) | ||
txn.call_after(self.is_guest.invalidate, (user_id,)) | ||
Comment on lines
+1213
to
1214
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! 👍 I don't see why we're invalidating the guest status here, but it seems like it has always been that way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had started with implementing unit tests. The tests had always failed. That was the reason for catch this bug. |
||
|
||
@cached() | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1003,12 +1003,23 @@ class UserRestTestCase(unittest.HomeserverTestCase): | |||||
|
||||||
def prepare(self, reactor, clock, hs): | ||||||
self.store = hs.get_datastore() | ||||||
self.auth_handler = hs.get_auth_handler() | ||||||
|
||||||
# create users and get access tokens | ||||||
# regardless of whether password login or SSO is allowed | ||||||
self.admin_user = self.register_user("admin", "pass", admin=True) | ||||||
self.admin_user_tok = self.login("admin", "pass") | ||||||
self.admin_user_tok = self.get_success( | ||||||
self.auth_handler.get_access_token_for_user_id( | ||||||
self.admin_user, device_id=None, valid_until_ms=None | ||||||
) | ||||||
) | ||||||
|
||||||
self.other_user = self.register_user("user", "pass", displayname="User") | ||||||
self.other_user_token = self.login("user", "pass") | ||||||
self.other_user_token = self.get_success( | ||||||
self.auth_handler.get_access_token_for_user_id( | ||||||
self.other_user, device_id=None, valid_until_ms=None | ||||||
) | ||||||
) | ||||||
self.url_other_user = "/_synapse/admin/v2/users/%s" % urllib.parse.quote( | ||||||
self.other_user | ||||||
) | ||||||
|
@@ -1081,7 +1092,7 @@ def test_create_server_admin(self): | |||||
self.assertEqual("Bob's name", channel.json_body["displayname"]) | ||||||
self.assertEqual("email", channel.json_body["threepids"][0]["medium"]) | ||||||
self.assertEqual("[email protected]", channel.json_body["threepids"][0]["address"]) | ||||||
self.assertEqual(True, channel.json_body["admin"]) | ||||||
self.assertTrue(channel.json_body["admin"]) | ||||||
self.assertEqual("mxc://fibble/wibble", channel.json_body["avatar_url"]) | ||||||
|
||||||
# Get user | ||||||
|
@@ -1096,9 +1107,9 @@ def test_create_server_admin(self): | |||||
self.assertEqual("Bob's name", channel.json_body["displayname"]) | ||||||
self.assertEqual("email", channel.json_body["threepids"][0]["medium"]) | ||||||
self.assertEqual("[email protected]", channel.json_body["threepids"][0]["address"]) | ||||||
self.assertEqual(True, channel.json_body["admin"]) | ||||||
self.assertEqual(False, channel.json_body["is_guest"]) | ||||||
self.assertEqual(False, channel.json_body["deactivated"]) | ||||||
self.assertTrue(channel.json_body["admin"]) | ||||||
self.assertFalse(channel.json_body["is_guest"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertEqual("mxc://fibble/wibble", channel.json_body["avatar_url"]) | ||||||
|
||||||
def test_create_user(self): | ||||||
|
@@ -1130,7 +1141,7 @@ def test_create_user(self): | |||||
self.assertEqual("Bob's name", channel.json_body["displayname"]) | ||||||
self.assertEqual("email", channel.json_body["threepids"][0]["medium"]) | ||||||
self.assertEqual("[email protected]", channel.json_body["threepids"][0]["address"]) | ||||||
self.assertEqual(False, channel.json_body["admin"]) | ||||||
self.assertFalse(channel.json_body["admin"]) | ||||||
self.assertEqual("mxc://fibble/wibble", channel.json_body["avatar_url"]) | ||||||
|
||||||
# Get user | ||||||
|
@@ -1145,10 +1156,10 @@ def test_create_user(self): | |||||
self.assertEqual("Bob's name", channel.json_body["displayname"]) | ||||||
self.assertEqual("email", channel.json_body["threepids"][0]["medium"]) | ||||||
self.assertEqual("[email protected]", channel.json_body["threepids"][0]["address"]) | ||||||
self.assertEqual(False, channel.json_body["admin"]) | ||||||
self.assertEqual(False, channel.json_body["is_guest"]) | ||||||
self.assertEqual(False, channel.json_body["deactivated"]) | ||||||
self.assertEqual(False, channel.json_body["shadow_banned"]) | ||||||
self.assertFalse(channel.json_body["admin"]) | ||||||
self.assertFalse(channel.json_body["is_guest"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertFalse(channel.json_body["shadow_banned"]) | ||||||
self.assertEqual("mxc://fibble/wibble", channel.json_body["avatar_url"]) | ||||||
|
||||||
@override_config( | ||||||
|
@@ -1197,7 +1208,7 @@ def test_create_user_mau_limit_reached_active_admin(self): | |||||
|
||||||
self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@bob:test", channel.json_body["name"]) | ||||||
self.assertEqual(False, channel.json_body["admin"]) | ||||||
self.assertFalse(channel.json_body["admin"]) | ||||||
|
||||||
@override_config( | ||||||
{"limit_usage_by_mau": True, "max_mau_value": 2, "mau_trial_days": 0} | ||||||
|
@@ -1237,7 +1248,7 @@ def test_create_user_mau_limit_reached_passive_admin(self): | |||||
# Admin user is not blocked by mau anymore | ||||||
self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@bob:test", channel.json_body["name"]) | ||||||
self.assertEqual(False, channel.json_body["admin"]) | ||||||
self.assertFalse(channel.json_body["admin"]) | ||||||
|
||||||
@override_config( | ||||||
{ | ||||||
|
@@ -1429,7 +1440,7 @@ def test_deactivate_user(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(False, channel.json_body["deactivated"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertEqual("[email protected]", channel.json_body["threepids"][0]["address"]) | ||||||
self.assertEqual("mxc://servername/mediaid", channel.json_body["avatar_url"]) | ||||||
self.assertEqual("User", channel.json_body["displayname"]) | ||||||
|
@@ -1446,7 +1457,8 @@ def test_deactivate_user(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["deactivated"]) | ||||||
self.assertTrue(channel.json_body["deactivated"]) | ||||||
self.assertIsNone(channel.json_body["password_hash"]) | ||||||
self.assertEqual(0, len(channel.json_body["threepids"])) | ||||||
self.assertEqual("mxc://servername/mediaid", channel.json_body["avatar_url"]) | ||||||
self.assertEqual("User", channel.json_body["displayname"]) | ||||||
|
@@ -1461,7 +1473,8 @@ def test_deactivate_user(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["deactivated"]) | ||||||
self.assertTrue(channel.json_body["deactivated"]) | ||||||
self.assertIsNone(channel.json_body["password_hash"]) | ||||||
self.assertEqual(0, len(channel.json_body["threepids"])) | ||||||
self.assertEqual("mxc://servername/mediaid", channel.json_body["avatar_url"]) | ||||||
self.assertEqual("User", channel.json_body["displayname"]) | ||||||
|
@@ -1489,11 +1502,11 @@ def test_change_name_deactivate_user_user_directory(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["deactivated"]) | ||||||
self.assertTrue(channel.json_body["deactivated"]) | ||||||
|
||||||
# is not in user directory | ||||||
profile = self.get_success(self.store.get_user_in_directory(self.other_user)) | ||||||
self.assertTrue(profile is None) | ||||||
self.assertIsNone(profile) | ||||||
|
||||||
# Set new displayname user | ||||||
body = json.dumps({"displayname": "Foobar"}) | ||||||
|
@@ -1507,41 +1520,89 @@ def test_change_name_deactivate_user_user_directory(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["deactivated"]) | ||||||
self.assertTrue(channel.json_body["deactivated"]) | ||||||
self.assertEqual("Foobar", channel.json_body["displayname"]) | ||||||
|
||||||
# is not in user directory | ||||||
profile = self.get_success(self.store.get_user_in_directory(self.other_user)) | ||||||
self.assertTrue(profile is None) | ||||||
self.assertIsNone(profile) | ||||||
|
||||||
def test_reactivate_user(self): | ||||||
""" | ||||||
Test reactivating another user. | ||||||
""" | ||||||
|
||||||
# Deactivate the user. | ||||||
self._deactivate_user("@user:test") | ||||||
|
||||||
# Attempt to reactivate the user (without a password). | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": True}).encode(encoding="utf_8"), | ||||||
content=json.dumps({"deactivated": False}).encode(encoding="utf_8"), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can pass just the dictionary in here instead of worrying about dumping and encoding (and the framework will handle that). It should simplify some of the code in here a bit! (There's a bunch of places to make this change, I only marked this one.)
Suggested change
|
||||||
) | ||||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
|
||||||
# Reactivate the user. | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": False, "password": "foo"}).encode( | ||||||
encoding="utf_8" | ||||||
), | ||||||
) | ||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertIsNotNone(channel.json_body["password_hash"]) | ||||||
self._is_erased("@user:test", False) | ||||||
d = self.store.mark_user_erased("@user:test") | ||||||
self.assertIsNone(self.get_success(d)) | ||||||
self._is_erased("@user:test", True) | ||||||
|
||||||
# Attempt to reactivate the user (without a password). | ||||||
@override_config({"password_config": {"localdb_enabled": False}}) | ||||||
def test_reactivate_user_localdb_disabled(self): | ||||||
""" | ||||||
Test reactivating another user when using SSO/SAML | ||||||
dklimpel marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
""" | ||||||
|
||||||
# Deactivate the user. | ||||||
self._deactivate_user("@user:test") | ||||||
|
||||||
# Reactivate the user with a password | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": False, "password": "foo"}).encode( | ||||||
encoding="utf_8" | ||||||
), | ||||||
) | ||||||
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"]) | ||||||
|
||||||
# Reactivate the user without a password. | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": False}).encode(encoding="utf_8"), | ||||||
) | ||||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertIsNone(channel.json_body["password_hash"]) | ||||||
self._is_erased("@user:test", False) | ||||||
|
||||||
# Reactivate the user. | ||||||
@override_config({"password_config": {"enabled": False}}) | ||||||
def test_reactivate_user_password_disabled(self): | ||||||
""" | ||||||
Test reactivating another user when using SSO/SAML | ||||||
dklimpel marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
""" | ||||||
|
||||||
# Deactivate the user. | ||||||
self._deactivate_user("@user:test") | ||||||
|
||||||
# Reactivate the user with a password | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
|
@@ -1550,18 +1611,20 @@ def test_reactivate_user(self): | |||||
encoding="utf_8" | ||||||
), | ||||||
) | ||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"]) | ||||||
|
||||||
# Get user | ||||||
# Reactivate the user without a password. | ||||||
channel = self.make_request( | ||||||
"GET", | ||||||
"PUT", | ||||||
self.url_other_user, | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": False}).encode(encoding="utf_8"), | ||||||
) | ||||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(False, channel.json_body["deactivated"]) | ||||||
self.assertFalse(channel.json_body["deactivated"]) | ||||||
self.assertIsNone(channel.json_body["password_hash"]) | ||||||
self._is_erased("@user:test", False) | ||||||
|
||||||
def test_set_user_as_admin(self): | ||||||
|
@@ -1581,7 +1644,7 @@ def test_set_user_as_admin(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["admin"]) | ||||||
self.assertTrue(channel.json_body["admin"]) | ||||||
|
||||||
# Get user | ||||||
channel = self.make_request( | ||||||
|
@@ -1592,7 +1655,7 @@ def test_set_user_as_admin(self): | |||||
|
||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertEqual("@user:test", channel.json_body["name"]) | ||||||
self.assertEqual(True, channel.json_body["admin"]) | ||||||
self.assertTrue(channel.json_body["admin"]) | ||||||
|
||||||
def test_accidental_deactivation_prevention(self): | ||||||
""" | ||||||
|
@@ -1653,14 +1716,32 @@ def test_accidental_deactivation_prevention(self): | |||||
# Ensure they're still alive | ||||||
self.assertEqual(0, channel.json_body["deactivated"]) | ||||||
|
||||||
def _is_erased(self, user_id, expect): | ||||||
def _is_erased(self, user_id: str, expect: bool) -> None: | ||||||
"""Assert that the user is erased or not""" | ||||||
d = self.store.is_user_erased(user_id) | ||||||
if expect: | ||||||
self.assertTrue(self.get_success(d)) | ||||||
else: | ||||||
self.assertFalse(self.get_success(d)) | ||||||
|
||||||
def _deactivate_user(self, user_id: str) -> None: | ||||||
"""Deactivate user and set as erased""" | ||||||
|
||||||
# Deactivate the user. | ||||||
channel = self.make_request( | ||||||
"PUT", | ||||||
"/_synapse/admin/v2/users/%s" % urllib.parse.quote(user_id), | ||||||
access_token=self.admin_user_tok, | ||||||
content=json.dumps({"deactivated": True}).encode(encoding="utf_8"), | ||||||
) | ||||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) | ||||||
self.assertTrue(channel.json_body["deactivated"]) | ||||||
self.assertIsNone(channel.json_body["password_hash"]) | ||||||
self._is_erased(user_id, False) | ||||||
d = self.store.mark_user_erased(user_id) | ||||||
self.assertIsNone(self.get_success(d)) | ||||||
Comment on lines
+1723
to
+1724
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused at why we're erasing data here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To test the erase was part of #8362. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, so the erasure isn't really only needed for some of the tests, but we do it for all just to simplify? That sounds fine! |
||||||
self._is_erased(user_id, True) | ||||||
|
||||||
|
||||||
class UserMembershipRestTestCase(unittest.HomeserverTestCase): | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should consolidate the various three (?) PRs we've made around this into a single changelog entry. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we can do it. But the first one (#9587) is already in 1.30.0rc1 included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, well maybe just for the last two then!