Compare commits
No commits in common. "184aa0630d43804bdda19cfbee4c02ec4405bca5" and "09161ff072b74e5eb4b22bed21ff9703fbd7eb38" have entirely different histories.
184aa0630d
...
09161ff072
5 changed files with 2 additions and 236 deletions
|
|
@ -5,12 +5,6 @@ All notable changes to wuttaweb will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v0.30.2 (2026-03-22)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- add support for merging 2 roles
|
|
||||||
|
|
||||||
## v0.30.1 (2026-03-21)
|
## v0.30.1 (2026-03-21)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "WuttaWeb"
|
name = "WuttaWeb"
|
||||||
version = "0.30.2"
|
version = "0.30.1"
|
||||||
description = "Web App for Wutta Framework"
|
description = "Web App for Wutta Framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||||
|
|
|
||||||
|
|
@ -3300,7 +3300,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
removing_data,
|
removing_data,
|
||||||
keeping_data,
|
keeping_data,
|
||||||
self.merge_get_final_data(removing_data, keeping_data),
|
self.merge_get_final_data(removing_data, keeping_data),
|
||||||
fields=self.merge_get_all_fields(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
context = {"removing": removing, "keeping": keeping, "diff": diff}
|
context = {"removing": removing, "keeping": keeping, "diff": diff}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2026 Lance Edgar
|
# Copyright © 2024-2025 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
|
@ -59,9 +59,6 @@ class RoleView(MasterView): # pylint: disable=abstract-method
|
||||||
}
|
}
|
||||||
sort_defaults = "name"
|
sort_defaults = "name"
|
||||||
|
|
||||||
mergeable = True
|
|
||||||
merge_additive_fields = ["permission_count", "user_count"]
|
|
||||||
|
|
||||||
wutta_permissions = None
|
wutta_permissions = None
|
||||||
|
|
||||||
# TODO: master should handle this, possibly via configure_form()
|
# TODO: master should handle this, possibly via configure_form()
|
||||||
|
|
@ -287,83 +284,6 @@ class RoleView(MasterView): # pylint: disable=abstract-method
|
||||||
else:
|
else:
|
||||||
auth.revoke_permission(role, pkey)
|
auth.revoke_permission(role, pkey)
|
||||||
|
|
||||||
def merge_get_data(self, obj): # pylint: disable=empty-docstring
|
|
||||||
""" """
|
|
||||||
data = super().merge_get_data(obj)
|
|
||||||
role = obj
|
|
||||||
|
|
||||||
data["permissions"] = role.permissions
|
|
||||||
data["permission_count"] = len(data["permissions"])
|
|
||||||
|
|
||||||
data["usernames"] = [user.username for user in role.users]
|
|
||||||
data["user_count"] = len(data["usernames"])
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def merge_get_final_data(
|
|
||||||
self, removing, keeping
|
|
||||||
): # pylint: disable=empty-docstring
|
|
||||||
""" """
|
|
||||||
final = super().merge_get_final_data(removing, keeping)
|
|
||||||
|
|
||||||
permissions = set(removing["permissions"] + keeping["permissions"])
|
|
||||||
final["permission_count"] = len(permissions)
|
|
||||||
|
|
||||||
usernames = set(removing["usernames"] + keeping["usernames"])
|
|
||||||
final["user_count"] = len(usernames)
|
|
||||||
|
|
||||||
return final
|
|
||||||
|
|
||||||
def merge_why_not(self, removing, keeping):
|
|
||||||
"""
|
|
||||||
This checks to ensure the "removing" role is not one of the
|
|
||||||
special built-in roles (Administrator, Authenticated,
|
|
||||||
Anonymous).
|
|
||||||
|
|
||||||
See also parent method:
|
|
||||||
:meth:`~wuttaweb.views.master.MasterView.merge_why_not()`
|
|
||||||
"""
|
|
||||||
auth = self.app.get_auth_handler()
|
|
||||||
session = self.Session()
|
|
||||||
|
|
||||||
if removing is auth.get_role_administrator(session):
|
|
||||||
return "Cannot remove the Administrator role."
|
|
||||||
|
|
||||||
if removing is auth.get_role_anonymous(session):
|
|
||||||
return "Cannot remove the Anonymous role."
|
|
||||||
|
|
||||||
if removing is auth.get_role_authenticated(session):
|
|
||||||
return "Cannot remove the Authenticated role."
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def merge_execute(self, removing, keeping):
|
|
||||||
"""
|
|
||||||
The logic to merge 2 roles is extended as follows:
|
|
||||||
|
|
||||||
Any users belonging to the "removing" role will be added to
|
|
||||||
the "keeping" role (if not already present).
|
|
||||||
|
|
||||||
Any permissions belonging to the "removing" role will be added
|
|
||||||
to the "keeping" role (if not already present).
|
|
||||||
|
|
||||||
See also parent method:
|
|
||||||
:meth:`~wuttaweb.views.master.MasterView.merge_execute()`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# transfer permissions
|
|
||||||
for perm in list(removing.permissions):
|
|
||||||
if perm not in keeping.permissions:
|
|
||||||
keeping.permissions.append(perm)
|
|
||||||
|
|
||||||
# transfer users
|
|
||||||
for user in list(removing.users):
|
|
||||||
if user not in keeping.users:
|
|
||||||
keeping.users.append(user)
|
|
||||||
|
|
||||||
# continue default merge
|
|
||||||
super().merge_execute(removing, keeping)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def defaults(cls, config): # pylint: disable=empty-docstring
|
def defaults(cls, config): # pylint: disable=empty-docstring
|
||||||
""" """
|
""" """
|
||||||
|
|
|
||||||
|
|
@ -284,153 +284,6 @@ class TestRoleView(WebTestCase):
|
||||||
self.assertIs(role, blokes)
|
self.assertIs(role, blokes)
|
||||||
self.assertEqual(blokes.permissions, ["widgets.polish", "widgets.view"])
|
self.assertEqual(blokes.permissions, ["widgets.polish", "widgets.view"])
|
||||||
|
|
||||||
def test_merge_get_data(self):
|
|
||||||
model = self.app.model
|
|
||||||
auth = self.app.get_auth_handler()
|
|
||||||
|
|
||||||
role = model.Role(name="whatever")
|
|
||||||
auth.grant_permission(role, "people.list")
|
|
||||||
auth.grant_permission(role, "people.view")
|
|
||||||
auth.grant_permission(role, "people.edit")
|
|
||||||
self.session.add(role)
|
|
||||||
|
|
||||||
user1 = model.User(username="user1")
|
|
||||||
user1.roles.append(role)
|
|
||||||
self.session.add(user1)
|
|
||||||
|
|
||||||
user2 = model.User(username="user2")
|
|
||||||
user2.roles.append(role)
|
|
||||||
self.session.add(user2)
|
|
||||||
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(len(role.permissions), 3)
|
|
||||||
self.assertEqual(len(role.users), 2)
|
|
||||||
|
|
||||||
view = self.make_view()
|
|
||||||
with patch.object(view, "Session", return_value=self.session):
|
|
||||||
data = view.merge_get_data(role)
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(data["permissions"]),
|
|
||||||
["people.edit", "people.list", "people.view"],
|
|
||||||
)
|
|
||||||
self.assertEqual(data["permission_count"], 3)
|
|
||||||
self.assertEqual(data["usernames"], ["user1", "user2"])
|
|
||||||
self.assertEqual(data["user_count"], 2)
|
|
||||||
|
|
||||||
def test_merge_get_final_data(self):
|
|
||||||
model = self.app.model
|
|
||||||
auth = self.app.get_auth_handler()
|
|
||||||
|
|
||||||
role1 = model.Role(name="whatever1")
|
|
||||||
auth.grant_permission(role1, "people.list")
|
|
||||||
auth.grant_permission(role1, "people.view")
|
|
||||||
auth.grant_permission(role1, "people.edit")
|
|
||||||
self.session.add(role1)
|
|
||||||
|
|
||||||
role2 = model.Role(name="whatever2")
|
|
||||||
auth.grant_permission(role2, "people.list")
|
|
||||||
auth.grant_permission(role2, "people.view")
|
|
||||||
self.session.add(role2)
|
|
||||||
|
|
||||||
user1 = model.User(username="user1")
|
|
||||||
user1.roles.append(role1)
|
|
||||||
self.session.add(user1)
|
|
||||||
|
|
||||||
user2 = model.User(username="user2")
|
|
||||||
user2.roles.append(role1)
|
|
||||||
user2.roles.append(role2)
|
|
||||||
self.session.add(user2)
|
|
||||||
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(len(role1.permissions), 3)
|
|
||||||
self.assertEqual(len(role1.users), 2)
|
|
||||||
self.assertEqual(len(role2.permissions), 2)
|
|
||||||
self.assertEqual(len(role2.users), 1)
|
|
||||||
|
|
||||||
view = self.make_view()
|
|
||||||
with patch.object(view, "Session", return_value=self.session):
|
|
||||||
removing = view.merge_get_data(role1)
|
|
||||||
keeping = view.merge_get_data(role2)
|
|
||||||
final = view.merge_get_final_data(removing, keeping)
|
|
||||||
self.assertEqual(final["permission_count"], 3)
|
|
||||||
self.assertEqual(final["user_count"], 2)
|
|
||||||
|
|
||||||
def test_merge_why_not(self):
|
|
||||||
model = self.app.model
|
|
||||||
auth = self.app.get_auth_handler()
|
|
||||||
|
|
||||||
role1 = model.Role(name="whatever1")
|
|
||||||
self.session.add(role1)
|
|
||||||
role2 = model.Role(name="whatever2")
|
|
||||||
self.session.add(role2)
|
|
||||||
self.session.commit()
|
|
||||||
|
|
||||||
view = self.make_view()
|
|
||||||
with patch.object(view, "Session", return_value=self.session):
|
|
||||||
|
|
||||||
# normal merge is allowed
|
|
||||||
self.assertIsNone(view.merge_why_not(role1, role2))
|
|
||||||
|
|
||||||
# special roles can be part of a merge if they are being "kept"
|
|
||||||
# but not if being "removed"
|
|
||||||
|
|
||||||
admin = auth.get_role_administrator(self.session)
|
|
||||||
self.assertIsNone(view.merge_why_not(role1, admin))
|
|
||||||
reason = view.merge_why_not(admin, role1)
|
|
||||||
self.assertEqual(reason, "Cannot remove the Administrator role.")
|
|
||||||
|
|
||||||
authed = auth.get_role_authenticated(self.session)
|
|
||||||
self.assertIsNone(view.merge_why_not(role1, authed))
|
|
||||||
reason = view.merge_why_not(authed, role1)
|
|
||||||
self.assertEqual(reason, "Cannot remove the Authenticated role.")
|
|
||||||
|
|
||||||
anon = auth.get_role_anonymous(self.session)
|
|
||||||
self.assertIsNone(view.merge_why_not(role1, anon))
|
|
||||||
reason = view.merge_why_not(anon, role1)
|
|
||||||
self.assertEqual(reason, "Cannot remove the Anonymous role.")
|
|
||||||
|
|
||||||
def test_merge_execute(self):
|
|
||||||
model = self.app.model
|
|
||||||
auth = self.app.get_auth_handler()
|
|
||||||
|
|
||||||
role1 = model.Role(name="whatever1")
|
|
||||||
auth.grant_permission(role1, "people.list")
|
|
||||||
auth.grant_permission(role1, "people.view")
|
|
||||||
auth.grant_permission(role1, "people.edit")
|
|
||||||
self.session.add(role1)
|
|
||||||
|
|
||||||
role2 = model.Role(name="whatever2")
|
|
||||||
auth.grant_permission(role2, "people.list")
|
|
||||||
auth.grant_permission(role2, "people.view")
|
|
||||||
self.session.add(role2)
|
|
||||||
|
|
||||||
user1 = model.User(username="user1")
|
|
||||||
user1.roles.append(role1)
|
|
||||||
self.session.add(user1)
|
|
||||||
|
|
||||||
user2 = model.User(username="user2")
|
|
||||||
user2.roles.append(role1)
|
|
||||||
user2.roles.append(role2)
|
|
||||||
self.session.add(user2)
|
|
||||||
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Role).count(), 2)
|
|
||||||
self.assertEqual(len(role1.permissions), 3)
|
|
||||||
self.assertEqual(len(role1.users), 2)
|
|
||||||
self.assertEqual(len(role2.permissions), 2)
|
|
||||||
self.assertEqual(len(role2.users), 1)
|
|
||||||
|
|
||||||
view = self.make_view()
|
|
||||||
with patch.object(view, "Session", return_value=self.session):
|
|
||||||
view.merge_execute(role1, role2)
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Role).count(), 1)
|
|
||||||
self.assertNotIn(role1, self.session)
|
|
||||||
self.assertIn(role2, self.session)
|
|
||||||
self.assertIs(role2, self.session.query(model.Role).one())
|
|
||||||
self.assertEqual(len(role2.permissions), 3)
|
|
||||||
self.assertEqual(len(role2.users), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPermissionView(WebTestCase):
|
class TestPermissionView(WebTestCase):
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue