Skip to content

Commit

Permalink
Merge pull request ckan#8622 from SyeKlu/feature/allow-plugin-update-…
Browse files Browse the repository at this point in the history
…user-name

feat: allow plugins to patch user name
  • Loading branch information
amercader authored Feb 26, 2025
2 parents a9f0630 + ab4e2b1 commit 85161b5
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 23 deletions.
1 change: 1 addition & 0 deletions changes/8622.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allows plugins to change the user name during ``user_patch`` or ``user_update`` by passing ``ignore_auth: True`` in the context. ``user_show`` also returns restricted properties like ``email`` or ``apikey`` when passing ``ignore_auth: True`` in the context.
2 changes: 1 addition & 1 deletion ckan/lib/dictization/model_dictize.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ def user_dictize(
result_dict['apikey'] = apikey
result_dict['email'] = email

if authz.is_sysadmin(requester):
if authz.is_sysadmin(requester) or context.get("ignore_auth") is True:
result_dict['apikey'] = apikey
result_dict['email'] = email

Expand Down
3 changes: 2 additions & 1 deletion ckan/logic/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,8 @@ def user_name_validator(key: FlattenKey, data: FlattenDataDict,
errors[key].append(_('The username is already associated with another account. Please use a different username.'))
elif user_obj_from_context:
requester = context.get('auth_user_obj', None)
if requester and authz.is_sysadmin(requester.name):
ignore_auth = context.get('ignore_auth', None)
if ignore_auth or (requester and authz.is_sysadmin(requester.name)):
return
old_user = model.User.get(user_obj_from_context.id)
if old_user is not None and old_user.state != model.State.PENDING:
Expand Down
33 changes: 29 additions & 4 deletions ckan/tests/logic/action/test_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,12 @@ class TestUserList(object):
def test_user_list_default_values(self):
user = factories.User()

got_users = helpers.call_action("user_list")
got_users = helpers.call_action(
"user_list",
# call_action will set ignore_auth: True by default, which will
# include restricted fields like apikey
context={"ignore_auth": False}
)

assert len(got_users) == 1
got_user = got_users[0]
Expand Down Expand Up @@ -1049,7 +1054,13 @@ def test_user_show_default_values(self):

user = factories.User()

got_user = helpers.call_action("user_show", id=user["id"])
got_user = helpers.call_action(
"user_show",
id=user["id"],
# call_action will set ignore_auth: True by default, which will
# include restricted fields like apikey
context={"ignore_auth": False}
)

assert got_user["id"] == user["id"]
assert got_user["name"] == user["name"]
Expand All @@ -1071,7 +1082,14 @@ def test_user_show_keep_email(self):
user = factories.User()

got_user = helpers.call_action(
"user_show", context={"keep_email": True}, id=user["id"]
"user_show",
id=user["id"],
# call_action will set ignore_auth: True by default, which will
# include restricted fields like apikey
context={
"keep_email": True,
"ignore_auth": False
}
)

assert got_user["email"] == user["email"]
Expand All @@ -1084,7 +1102,14 @@ def test_user_show_keep_apikey(self):
user = factories.User()

got_user = helpers.call_action(
"user_show", context={"keep_apikey": True}, id=user["id"]
"user_show",
id=user["id"],
# call_action will set ignore_auth: True by default, which will
# include restricted fields like apikey
context={
"keep_apikey": True,
"ignore_auth": False
}
)

assert "email" not in got_user
Expand Down
27 changes: 27 additions & 0 deletions ckan/tests/logic/action/test_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,33 @@ def test_user_patch_updating_single_field(self):
assert user2["fullname"] == "Mr. Test User"
assert user2["about"] == "somethingnew"

def test_extensions_successful_patch_updating_user_name(self):
user = factories.User()

updated_user = helpers.call_action(
"user_patch",
context={"user": user["name"], "ignore_auth": True},
id=user["id"],
name="somethingnew"
)

assert updated_user["name"] == "somethingnew"

user2 = helpers.call_action("user_show", id=user["id"])

assert user2["name"] == "somethingnew"

def test_extensions_failed_patch_updating_user_name(self):
user = factories.User()

with pytest.raises(ValidationError):
helpers.call_action(
"user_patch",
context={"user": user["name"], "ignore_auth": False},
id=user["id"],
name="somethingnew2"
)

def test_package_patch_for_update(self):

dataset = factories.Dataset()
Expand Down
19 changes: 2 additions & 17 deletions ckan/tests/logic/action/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ def test_user_update_name(self):

# 1. Setup.
user = factories.User()
context = {"user": user["name"], "ignore_auth": False}
user["name"] = "updated"

# 2. Make assertions about the return value and/or side-effects.
with pytest.raises(logic.ValidationError):
helpers.call_action("user_update", **user)
helpers.call_action("user_update", context=context, **user)

# END-BEFORE

Expand Down Expand Up @@ -265,22 +266,6 @@ def test_user_update_does_not_return_password(self):
updated_user = helpers.call_action("user_update", **params)
assert "password" not in updated_user

def test_user_update_does_not_return_apikey(self):
"""The user dict that user_update returns should not include the user's
API key."""

user = factories.User()
params = {
"id": user["id"],
"fullname": "updated full name",
"about": "updated about",
"email": user["email"],
"password": factories.User.stub().password,
}

updated_user = helpers.call_action("user_update", **params)
assert "apikey" not in updated_user

def test_user_update_does_not_return_reset_key(self):
"""The user dict that user_update returns should not include the user's
reset key."""
Expand Down

0 comments on commit 85161b5

Please sign in to comment.