diff --git a/src/django_simple_nav/permissions.py b/src/django_simple_nav/permissions.py index 5e71f45..9ce9ca8 100644 --- a/src/django_simple_nav/permissions.py +++ b/src/django_simple_nav/permissions.py @@ -14,7 +14,7 @@ class User(Protocol): is_superuser: bool def has_perm(self, perm: str) -> bool: - ... + ... # pragma: no cover def check_item_permissions(item: NavGroup | NavItem, user: User) -> bool: @@ -24,18 +24,18 @@ def check_item_permissions(item: NavGroup | NavItem, user: User) -> bool: if not user_perm: return False - if hasattr(item, "items"): - sub_items = [ - sub_item - for sub_item in item.items - if check_item_permissions(sub_item, user) - ] - if not sub_items: - return False - if not idx == len(item.permissions) - 1: continue + if hasattr(item, "items"): + sub_items = [ + sub_item + for sub_item in item.items + if check_item_permissions(sub_item, user) + ] + if not sub_items and not item.url: + return False + return True @@ -45,7 +45,11 @@ def user_has_perm(user: User, perm: str) -> bool: has_perm = False if perm in ["is_authenticated", "is_staff", "is_superuser"]: - has_perm = getattr(user, perm, False) + is_superuser = getattr(user, "is_superuser", False) + if is_superuser: + has_perm = True + else: + has_perm = getattr(user, perm, False) elif user.has_perm(perm): has_perm = True diff --git a/tests/test_nav.py b/tests/test_nav.py index 4c8c332..2315334 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -64,7 +64,7 @@ def test_dotted_path_rendering(req): [ ("", 10), # regular authenticated user ("is_staff", 13), - ("is_superuser", 16), + ("is_superuser", 19), ("tests.dummy_perm", 13), ], ) diff --git a/tests/test_permissions.py b/tests/test_permissions.py new file mode 100644 index 0000000..b987db3 --- /dev/null +++ b/tests/test_permissions.py @@ -0,0 +1,331 @@ +from __future__ import annotations + +import pytest +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser +from model_bakery import baker + +from django_simple_nav.nav import NavGroup +from django_simple_nav.nav import NavItem +from django_simple_nav.permissions import check_item_permissions +from django_simple_nav.permissions import user_has_perm + +pytestmark = pytest.mark.django_db + + +def test_check_anonymous_user_has_perm(): + user = AnonymousUser() + + assert not user_has_perm(user, "is_authenticated") + + +def test_check_authenticated_user_has_perm(): + user = baker.make(get_user_model()) + + assert user_has_perm(user, "is_authenticated") + + +def test_check_staff_user_has_perm(): + user = baker.make(get_user_model(), is_staff=True) + + assert user_has_perm(user, "is_staff") + + +def test_check_superuser_user_has_perm(): + user = baker.make(get_user_model(), is_superuser=True) + + assert user_has_perm(user, "is_superuser") + + +def test_check_auth_permission_user_has_perm(): + user = baker.make(get_user_model()) + + dummy_perm = baker.make( + "auth.Permission", + codename="dummy_perm", + name="Dummy Permission", + content_type=baker.make("contenttypes.ContentType", app_label="tests"), + ) + + user.user_permissions.add(dummy_perm) + + assert user_has_perm(user, "tests.dummy_perm") + + +# anonymous user +@pytest.mark.parametrize( + "item,expected", + [ + # nav item + (NavItem("Test", "/test"), True), + (NavItem("Test", "/test", permissions=["is_authenticated"]), False), + (NavItem("Test", "/test", permissions=["is_staff"]), False), + (NavItem("Test", "/test", permissions=["is_superuser"]), False), + (NavItem("Test", "/test", permissions=["tests.dummy_perm"]), False), + # nav group with url + (NavGroup("Test", [], "/test"), True), + (NavGroup("Test", [], "/test", permissions=["is_authenticated"]), False), + (NavGroup("Test", [], "/test", permissions=["is_staff"]), False), + (NavGroup("Test", [], "/test", permissions=["is_superuser"]), False), + (NavGroup("Test", [], "/test", permissions=["tests.dummy_perm"]), False), + # nav group with no url and items with perms + (NavGroup("Test", [NavItem("Test", "/test")]), True), + ( + NavGroup( + "Test", + [NavItem("Test", "/test", permissions=["is_authenticated"])], + ), + False, + ), + (NavGroup("Test", [NavItem("Test", "/test", permissions=["is_staff"])]), False), + ( + NavGroup("Test", [NavItem("Test", "/test", permissions=["is_superuser"])]), + False, + ), + ( + NavGroup( + "Test", [NavItem("Test", "/test", permissions=["tests.dummy_perm"])] + ), + False, + ), + # multiple perms + (NavItem("Test", "/test", permissions=["is_authenticated", "is_staff"]), False), + ( + NavItem( + "Test", + "/test", + permissions=["is_authenticated", "is_staff", "is_superuser"], + ), + False, + ), + ], +) +def test_check_item_permissions_anonymous(item, expected): + user = AnonymousUser() + + assert check_item_permissions(item, user) == expected + + +# authenticated user +@pytest.mark.parametrize( + "item,expected", + [ + # nav item + (NavItem("Test", "/test"), True), + (NavItem("Test", "/test", permissions=["is_authenticated"]), True), + (NavItem("Test", "/test", permissions=["is_staff"]), False), + (NavItem("Test", "/test", permissions=["is_superuser"]), False), + (NavItem("Test", "/test", permissions=["tests.dummy_perm"]), False), + # nav group with url + (NavGroup("Test", [], "/test"), True), + (NavGroup("Test", [], "/test", permissions=["is_authenticated"]), True), + (NavGroup("Test", [], "/test", permissions=["is_staff"]), False), + (NavGroup("Test", [], "/test", permissions=["is_superuser"]), False), + (NavGroup("Test", [], "/test", permissions=["tests.dummy_perm"]), False), + # nav group with no url and items with perms + (NavGroup("Test", [NavItem("Test", "/test")], "/test"), True), + ( + NavGroup( + "Test", + [NavItem("Test", "/test", permissions=["is_authenticated"])], + ), + True, + ), + (NavGroup("Test", [NavItem("Test", "/test", permissions=["is_staff"])]), False), + ( + NavGroup("Test", [NavItem("Test", "/test", permissions=["is_superuser"])]), + False, + ), + ( + NavGroup( + "Test", [NavItem("Test", "/test", permissions=["tests.dummy_perm"])] + ), + False, + ), + # multiple perms + (NavItem("Test", "/test", permissions=["is_authenticated", "is_staff"]), False), + ( + NavItem( + "Test", + "/test", + permissions=["is_authenticated", "is_staff", "is_superuser"], + ), + False, + ), + ], +) +def test_check_item_permissions_is_authenticated(item, expected): + user = baker.make(get_user_model()) + + assert check_item_permissions(item, user) == expected + + +# staff user +@pytest.mark.parametrize( + "item,expected", + [ + # nav item + (NavItem("Test", "/test"), True), + (NavItem("Test", "/test", permissions=["is_authenticated"]), True), + (NavItem("Test", "/test", permissions=["is_staff"]), True), + (NavItem("Test", "/test", permissions=["is_superuser"]), False), + (NavItem("Test", "/test", permissions=["tests.dummy_perm"]), False), + # nav group with url + (NavGroup("Test", [], "/test"), True), + (NavGroup("Test", [], "/test", permissions=["is_authenticated"]), True), + (NavGroup("Test", [], "/test", permissions=["is_staff"]), True), + (NavGroup("Test", [], "/test", permissions=["is_superuser"]), False), + (NavGroup("Test", [], "/test", permissions=["tests.dummy_perm"]), False), + # nav group with no url and items with perms + (NavGroup("Test", [NavItem("Test", "/test")]), True), + ( + NavGroup( + "Test", + [NavItem("Test", "/test", permissions=["is_authenticated"])], + ), + True, + ), + (NavGroup("Test", [NavItem("Test", "/test", permissions=["is_staff"])]), True), + ( + NavGroup("Test", [NavItem("Test", "/test", permissions=["is_superuser"])]), + False, + ), + ( + NavGroup( + "Test", [NavItem("Test", "/test", permissions=["tests.dummy_perm"])] + ), + False, + ), + # multiple perms + (NavItem("Test", "/test", permissions=["is_authenticated", "is_staff"]), True), + ( + NavItem( + "Test", + "/test", + permissions=["is_authenticated", "is_staff", "is_superuser"], + ), + False, + ), + ], +) +def test_check_item_permissions_is_staff(item, expected): + user = baker.make(get_user_model(), is_staff=True) + + assert check_item_permissions(item, user) == expected + + +# superuser +@pytest.mark.parametrize( + "item,expected", + [ + # nav item + (NavItem("Test", "/test"), True), + (NavItem("Test", "/test", permissions=["is_authenticated"]), True), + (NavItem("Test", "/test", permissions=["is_staff"]), True), + (NavItem("Test", "/test", permissions=["is_superuser"]), True), + (NavItem("Test", "/test", permissions=["tests.dummy_perm"]), True), + # nav group with url + (NavGroup("Test", [], "/test"), True), + (NavGroup("Test", [], "/test", permissions=["is_authenticated"]), True), + (NavGroup("Test", [], "/test", permissions=["is_staff"]), True), + (NavGroup("Test", [], "/test", permissions=["is_superuser"]), True), + (NavGroup("Test", [], "/test", permissions=["tests.dummy_perm"]), True), + # nav group with no url and items with perms + (NavGroup("Test", [NavItem("Test", "/test")], "/test"), True), + ( + NavGroup( + "Test", + [NavItem("Test", "/test", permissions=["is_authenticated"])], + ), + True, + ), + (NavGroup("Test", [NavItem("Test", "/test", permissions=["is_staff"])]), True), + ( + NavGroup("Test", [NavItem("Test", "/test", permissions=["is_superuser"])]), + True, + ), + ( + NavGroup( + "Test", [NavItem("Test", "/test", permissions=["tests.dummy_perm"])] + ), + True, + ), + # multiple perms + (NavItem("Test", "/test", permissions=["is_authenticated", "is_staff"]), True), + ( + NavItem( + "Test", + "/test", + permissions=["is_authenticated", "is_staff", "is_superuser"], + ), + True, + ), + ], +) +def test_check_item_permissions_is_superuser(item, expected): + user = baker.make(get_user_model(), is_superuser=True) + + assert check_item_permissions(item, user) == expected + + +# user with specific auth.Permission +@pytest.mark.parametrize( + "item,expected", + [ + # nav item + (NavItem("Test", "/test"), True), + (NavItem("Test", "/test", permissions=["is_authenticated"]), True), + (NavItem("Test", "/test", permissions=["is_staff"]), False), + (NavItem("Test", "/test", permissions=["is_superuser"]), False), + (NavItem("Test", "/test", permissions=["tests.dummy_perm"]), True), + # nav group with url + (NavGroup("Test", [], "/test"), True), + (NavGroup("Test", [], "/test", permissions=["is_authenticated"]), True), + (NavGroup("Test", [], "/test", permissions=["is_staff"]), False), + (NavGroup("Test", [], "/test", permissions=["is_superuser"]), False), + (NavGroup("Test", [], "/test", permissions=["tests.dummy_perm"]), True), + # nav group with no url and items with perms + (NavGroup("Test", [NavItem("Test", "/test")], "/test"), True), + ( + NavGroup( + "Test", + [NavItem("Test", "/test", permissions=["is_authenticated"])], + ), + True, + ), + (NavGroup("Test", [NavItem("Test", "/test", permissions=["is_staff"])]), False), + ( + NavGroup("Test", [NavItem("Test", "/test", permissions=["is_superuser"])]), + False, + ), + ( + NavGroup( + "Test", [NavItem("Test", "/test", permissions=["tests.dummy_perm"])] + ), + True, + ), + # multiple perms + (NavItem("Test", "/test", permissions=["is_authenticated", "is_staff"]), False), + ( + NavItem( + "Test", + "/test", + permissions=["is_authenticated", "is_staff", "is_superuser"], + ), + False, + ), + ], +) +def test_check_item_permissions_auth_permission(item, expected): + user = baker.make(get_user_model()) + + dummy_perm = baker.make( + "auth.Permission", + codename="dummy_perm", + name="Dummy Permission", + content_type=baker.make("contenttypes.ContentType", app_label="tests"), + ) + + user.user_permissions.add(dummy_perm) + + assert check_item_permissions(item, user) == expected