diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py index a9a28902caffb7a90eb837e27e832e8932380055..db629a7edf4c0072820a67757cdb43afd496e44a 100644 --- a/aleksis/core/preferences.py +++ b/aleksis/core/preferences.py @@ -18,7 +18,7 @@ from dynamic_preferences.types import ( from oauth2_provider.models import AbstractApplication from .mixins import CalendarEventMixin, PublicFilePreferenceMixin -from .models import Group, Person +from .models import Group, GroupType, Person from .registries import person_preferences_registry, site_preferences_registry from .util.notifications import get_notification_choices_lazy @@ -544,3 +544,47 @@ class DisallowedUids(LongStringPreference): ) required = False verbose_name = _("Comma-separated list of disallowed usernames") + + +@site_preferences_registry.register +class GroupTypesGroupsOwners(ModelMultipleChoicePreference): + section = general + name = "group_types_groups_owners" + required = False + default = [] + model = GroupType + verbose_name = _("User is allowed to see groups the user is an owner of with these group types") + + +@site_preferences_registry.register +class GroupTypesGroupMembersOwners(ModelMultipleChoicePreference): + section = general + name = "group_types_group_members_owners" + required = False + default = [] + model = GroupType + verbose_name = _( + "User is allowed to see members of groups the user is an owner of with these group types" + ) + + +@site_preferences_registry.register +class GroupTypesGroupMembersOwnersAllowedInformation(MultipleChoicePreference): + section = general + name = "group_types_group_members_owners_allowed_information" + default = [] + widget = SelectMultiple + verbose_name = _( + "Information user is allowed to see about members of groups the user is an owner" + ) + required = False + + field_attribute = {"initial": []} + choices = [ + ("personal_details", _("Personal details")), + ("address", _("Address")), + ("contact_details", _("Contact details")), + ("photo", _("Photo")), + ("avatar", _("Avatar")), + ("groups", _("Groups")), + ] diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 8dd99d60482bfe62bed8d72a38938e442c68592f..06dc986c4d8f24722387dcc121fd5239c570754e 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -1,15 +1,20 @@ import rules from rules import is_superuser -from .models import Announcement, Group, GroupType, Holiday, Person +from .models import Announcement, GroupType, Holiday from .util.predicates import ( + has_any_group, has_any_object, + has_any_person, has_global_perm, has_object_perm, has_person, is_anonymous, is_current_person, is_group_owner, + is_group_owner_allowed_information, + is_group_owner_of_person_with_group_type, + is_group_owner_with_group_type, is_notification_recipient, is_own_celery_task, is_personal_event_owner, @@ -54,20 +59,24 @@ search_predicate = has_person & has_global_perm("core.search") rules.add_perm("core.search_rule", search_predicate) # View persons -view_persons_predicate = has_person & ( - has_global_perm("core.view_person") | has_any_object("core.view_person", Person) -) +view_persons_predicate = has_person & (has_global_perm("core.view_person") | has_any_person) rules.add_perm("core.view_persons_rule", view_persons_predicate) # View person view_person_predicate = has_person & ( - is_current_person | has_global_perm("core.view_person") | has_object_perm("core.view_person") + is_current_person + | has_global_perm("core.view_person") + | has_object_perm("core.view_person") + | is_group_owner_of_person_with_group_type ) rules.add_perm("core.view_person_rule", view_person_predicate) # View person address view_address_predicate = has_person & ( - is_current_person | has_global_perm("core.view_address") | has_object_perm("core.view_address") + is_current_person + | has_global_perm("core.view_address") + | has_object_perm("core.view_address") + | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("address")) ) rules.add_perm("core.view_address_rule", view_address_predicate) @@ -76,28 +85,39 @@ view_contact_details_predicate = has_person & ( is_current_person | has_global_perm("core.view_contact_details") | has_object_perm("core.view_contact_details") + | ( + is_group_owner_of_person_with_group_type + & is_group_owner_allowed_information("contact_details") + ) ) rules.add_perm("core.view_contact_details_rule", view_contact_details_predicate) # View person photo view_photo_predicate = has_person & ( - is_current_person | has_global_perm("core.view_photo") | has_object_perm("core.view_photo") + is_current_person + | has_global_perm("core.view_photo") + | has_object_perm("core.view_photo") + | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("photo")) ) rules.add_perm("core.view_photo_rule", view_photo_predicate) # View person avatar image view_avatar_predicate = has_person & ( - is_current_person | has_global_perm("core.view_avatar") | has_object_perm("core.view_avatar") + is_current_person + | has_global_perm("core.view_avatar") + | has_object_perm("core.view_avatar") + | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("avatar")) ) rules.add_perm("core.view_avatar_rule", view_avatar_predicate) # View persons groups -view_groups_predicate = has_person & ( +view_person_groups_predicate = has_person & ( is_current_person | has_global_perm("core.view_person_groups") | has_object_perm("core.view_person_groups") + | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("groups")) ) -rules.add_perm("core.view_person_groups_rule", view_groups_predicate) +rules.add_perm("core.view_person_groups_rule", view_person_groups_predicate) # Edit person edit_person_predicate = has_person & ( @@ -114,14 +134,14 @@ delete_person_predicate = has_person & ( rules.add_perm("core.delete_person_rule", delete_person_predicate) # View groups -view_groups_predicate = has_person & ( - has_global_perm("core.view_group") | has_any_object("core.view_group", Group) -) +view_groups_predicate = has_person & (has_global_perm("core.view_group") | has_any_group) rules.add_perm("core.view_groups_rule", view_groups_predicate) # View group view_group_predicate = has_person & ( - has_global_perm("core.view_group") | has_object_perm("core.view_group") + is_group_owner_with_group_type + | has_global_perm("core.view_group") + | has_object_perm("core.view_group") ) rules.add_perm("core.view_group_rule", view_group_predicate) @@ -195,6 +215,10 @@ view_personal_details_predicate = has_person & ( is_current_person | has_global_perm("core.view_personal_details") | has_object_perm("core.view_personal_details") + | ( + is_group_owner_of_person_with_group_type + & is_group_owner_allowed_information("personal_details") + ) ) rules.add_perm("core.view_personal_details_rule", view_personal_details_predicate) @@ -232,6 +256,9 @@ view_group_type_predicate = has_person & ( ) rules.add_perm("core.view_grouptype_rule", view_group_type_predicate) +fetch_group_types_predicate = has_person +rules.add_perm("core.fetch_grouptypes_rule", fetch_group_types_predicate) + # Edit group type change_group_type_predicate = has_person & ( has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype") @@ -273,6 +300,10 @@ rules.add_perm("core.create_group_rule", create_group_predicate) view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm") rules.add_perm("core.view_schoolterm_rule", view_school_term_predicate) +fetch_school_terms_predicate = has_person +rules.add_perm("core.fetch_schoolterms_rule", fetch_school_terms_predicate) + + create_school_term_predicate = has_person & has_global_perm("core.add_schoolterm") rules.add_perm("core.create_schoolterm_rule", create_school_term_predicate) @@ -286,7 +317,9 @@ rules.add_perm("core.delete_schoolterm_rule", delete_schoolterm_predicate) # View group stats view_group_stats_predicate = has_person & ( - has_global_perm("core.view_group_stats") | has_object_perm("core.view_group_stats") + is_group_owner_with_group_type + | has_global_perm("core.view_group_stats") + | has_object_perm("core.view_group_stats") ) rules.add_perm("core.view_group_stats_rule", view_group_stats_predicate) diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py index 41eac3b485ed01454fe7e7f09bff0f7a7e73a7e7..12b8bb1f112b1e46971beb36347c1523613f578f 100644 --- a/aleksis/core/schema/__init__.py +++ b/aleksis/core/schema/__init__.py @@ -21,7 +21,13 @@ from ..models import ( TaskUserAssignment, ) from ..util.apps import AppConfig -from ..util.core_helpers import get_allowed_object_ids, get_app_module, get_app_packages, has_person +from ..util.core_helpers import ( + get_allowed_object_ids, + get_app_module, + get_app_packages, + get_site_preferences, + has_person, +) from .base import FilterOrderList from .calendar import CalendarBaseType, SetCalendarStatusMutation from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType @@ -130,12 +136,25 @@ class Query(graphene.ObjectType): ) def resolve_persons(root, info, **kwargs): - return get_objects_for_user(info.context.user, "core.view_person", Person.objects.all()) + qs = get_objects_for_user(info.context.user, "core.view_person", Person.objects.all()) + if has_person(info.context.user): + qs = qs | Person.objects.filter(id=info.context.user.person.id) + return ( + qs + | Person.objects.filter( + member_of__in=Group.objects.filter( + owners=info.context.user.person, + group_type__in=get_site_preferences()[ + "general__group_types_group_members_owners" + ], + ), + ) + ).distinct() def resolve_person_by_id(root, info, id): # noqa person = Person.objects.get(pk=id) if not info.context.user.has_perm("core.view_person_rule", person): - raise PermissionDenied() + return None return person def resolve_person_by_id_or_me(root, info, **kwargs): # noqa @@ -145,12 +164,19 @@ class Query(graphene.ObjectType): person = Person.objects.get(pk=kwargs["id"]) if not info.context.user.has_perm("core.view_person_rule", person): - raise PermissionDenied() + return None return person @staticmethod def resolve_groups(root, info, **kwargs): - return get_objects_for_user(info.context.user, "core.view_group", Group) + qs = get_objects_for_user(info.context.user, "core.view_group", Group) + return ( + qs + | Group.objects.filter( + owners=info.context.user.person, + group_type__in=get_site_preferences()["general__group_types_groups_owners"], + ) + ).distinct() @staticmethod def resolve_group_by_id(root, info, id): # noqa diff --git a/aleksis/core/schema/group.py b/aleksis/core/schema/group.py index a5b0d96d3564d23baafffb089117dc0a5229c7d9..2a79b8b95a0cf09f068d339577b226c2e572a4d4 100644 --- a/aleksis/core/schema/group.py +++ b/aleksis/core/schema/group.py @@ -1,11 +1,9 @@ -from django.core.exceptions import PermissionDenied - import graphene from graphene_django import DjangoObjectType from guardian.shortcuts import get_objects_for_user from ..models import Group, Person -from ..util.core_helpers import has_person +from ..util.core_helpers import get_site_preferences, has_person from .base import BaseBatchDeleteMutation, DjangoFilterMixin, PermissionsTypeMixin @@ -38,14 +36,27 @@ class GroupType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): @staticmethod def resolve_parent_groups(root, info, **kwargs): - return get_objects_for_user(info.context.user, "core.view_group", root.parent_groups.all()) + qs = root.parent_groups.all() + if info.context.user.has_perm("core.view_group_rule", root): + return qs + return get_objects_for_user(info.context.user, "core.view_group", qs) @staticmethod def resolve_child_groups(root, info, **kwargs): - return get_objects_for_user(info.context.user, "core.view_group", root.child_groups.all()) + qs = root.child_groups.all() + if info.context.user.has_perm("core.view_group_rule", root): + return qs + return get_objects_for_user(info.context.user, "core.view_group", qs) @staticmethod def resolve_members(root, info, **kwargs): + if ( + has_person(info.context.user) + and root.group_type + in get_site_preferences()["general__group_types_group_members_owners"] + and info.context.user.person in root.owners.all() + ): + return root.members.all() persons = get_objects_for_user(info.context.user, "core.view_person", root.members.all()) if has_person(info.context.user) and [ m for m in root.members.all() if m.pk == info.context.user.person.pk @@ -62,14 +73,10 @@ class GroupType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): persons = (persons | Person.objects.filter(pk=info.context.user.person.pk)).distinct() return persons - @staticmethod - def resolve_group_type(root, info, **kwargs): - if info.context.user.has_perm("core.view_grouptype_rule", root.group_type): - return root.group_type - raise PermissionDenied() - @staticmethod def resolve_statistics(root: Group, info, **kwargs): + if not info.context.user.has_perm("core.view_group_stats_rule", root): + return None return root.get_group_stats diff --git a/aleksis/core/schema/group_type.py b/aleksis/core/schema/group_type.py index 4d5e00cff5d187bd6fd63ce5e80244b597ec0a55..77febfb9099398adb23359350b1bc982b4ca568e 100644 --- a/aleksis/core/schema/group_type.py +++ b/aleksis/core/schema/group_type.py @@ -1,5 +1,4 @@ from graphene_django import DjangoObjectType -from guardian.shortcuts import get_objects_for_user from ..models import GroupType from .base import ( @@ -22,7 +21,9 @@ class GroupTypeType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): @classmethod def get_queryset(cls, queryset, info): - return get_objects_for_user(info.context.user, "core.view_grouptype", GroupType) + if not info.context.user.has_perm("core.fetch_grouptypes_rule"): + return [] + return queryset class GroupTypeBatchCreateMutation(BaseBatchCreateMutation): diff --git a/aleksis/core/schema/person.py b/aleksis/core/schema/person.py index 6e62933255957fa3199a6f935244d02b5115ff45..3b24bbafea5e9558dfdf2718d0d74cfe2d0561b6 100644 --- a/aleksis/core/schema/person.py +++ b/aleksis/core/schema/person.py @@ -1,11 +1,9 @@ from typing import Union -from django.core.exceptions import PermissionDenied from django.utils import timezone import graphene from graphene_django import DjangoObjectType -from guardian.shortcuts import get_objects_for_user from ..filters import PersonFilter from ..models import DummyPerson, Person @@ -137,28 +135,28 @@ class PersonType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): def resolve_children(root, info, **kwargs): # noqa if info.context.user.has_perm("core.view_personal_details_rule", root): - return get_objects_for_user(info.context.user, "core.view_person", root.children.all()) + return root.children.all() return [] def resolve_guardians(root, info, **kwargs): # noqa if info.context.user.has_perm("core.view_personal_details_rule", root): - return get_objects_for_user(info.context.user, "core.view_person", root.guardians.all()) + return root.guardians.all() return [] def resolve_member_of(root, info, **kwargs): # noqa if info.context.user.has_perm("core.view_person_groups_rule", root): - return get_objects_for_user(info.context.user, "core.view_group", root.member_of.all()) + return root.member_of.all() return [] def resolve_owner_of(root, info, **kwargs): # noqa if info.context.user.has_perm("core.view_person_groups_rule", root): - return get_objects_for_user(info.context.user, "core.view_group", root.owner_of.all()) + return root.owner_of.all() return [] def resolve_primary_group(root, info, **kwargs): # noqa - if info.context.user.has_perm("core.view_group_rule", root.primary_group): + if info.context.user.has_perm("core.view_person_groups_rule", root): return root.primary_group - raise PermissionDenied() + return None def resolve_username(root, info, **kwargs): # noqa return root.user.username if root.user else None diff --git a/aleksis/core/schema/school_term.py b/aleksis/core/schema/school_term.py index a8b64d80faa99d95d93e8468586eb8f12e310de6..8dd36b47a8978bd448a28ba08f8948f2564cd2bd 100644 --- a/aleksis/core/schema/school_term.py +++ b/aleksis/core/schema/school_term.py @@ -1,5 +1,3 @@ -from django.core.exceptions import PermissionDenied - from graphene_django import DjangoObjectType from ..models import SchoolTerm @@ -24,9 +22,8 @@ class SchoolTermType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): @classmethod def get_queryset(cls, queryset, info, **kwargs): - if not info.context.user.has_perm("core.view_schoolterm_rule"): - raise PermissionDenied - + if not info.context.user.has_perm("core.fetch_schoolterms_rule"): + return [] return queryset diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index e161cc7f9736d60d56a7f47852d0e859b52fd37f..6cc71cd032335313f46eae1d64ae2afa5363d4f1 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -434,9 +434,7 @@ REST_FRAMEWORK = { } # Configuration for GraphQL framework -GRAPHENE = { - "SCHEMA": "aleksis.core.schema.schema", -} +GRAPHENE = {"SCHEMA": "aleksis.core.schema.schema", "TESTING_ENDPOINT": "/graphql/"} # LDAP config if _settings.get("ldap.uri", None): diff --git a/aleksis/core/tests/schema/test_groups.py b/aleksis/core/tests/schema/test_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..3ca647404d3e73e6dbd0e904620cb3704389beb6 --- /dev/null +++ b/aleksis/core/tests/schema/test_groups.py @@ -0,0 +1,72 @@ +import json +import pytest +from graphql.error.graphql_error import GraphQLError + +from aleksis.core.models import Group, GroupType, Person +from django.contrib.auth.models import Permission + +from aleksis.core.util.core_helpers import get_site_preferences + + +pytestmark = pytest.mark.django_db + + +def test_groups_query(client_query): + p = Person.objects.first() + correct_group_type = GroupType.objects.create(name="correct") + wrong_group_type = GroupType.objects.create(name="wrong") + + group_not_owner = Group.objects.create(name="not_owner") + group_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type) + + group2_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type) + group_wrong_group_type_owner = Group.objects.create(name="wrong_group_type_owner", group_type=wrong_group_type) + group_no_group_type_owner = Group.objects.create(name="no_group_type_owner") + + for g in (group_correct_group_type_owner, group2_correct_group_type_owner, group_wrong_group_type_owner, group_no_group_type_owner): + g.owners.add(p) + + get_site_preferences()["general__group_types_groups_owners"] = [] + + response, content = client_query( + "{groups{id}}" + ) + assert len(content["data"]["groups"]) == 0 + + for g in Group.objects.all(): + with pytest.raises(GraphQLError): + response, content = client_query( + "query groupById($id: ID) {object: groupById(id: $id) { id } }", + variables={"id": g.id}, + ) + + global_permission = Permission.objects.get(codename="view_group", content_type__app_label="core") + p.user.user_permissions.add(global_permission) + + response, content = client_query( + "{groups{id}}" + ) + assert set(int(g["id"]) for g in content["data"]["groups"]) == set(Group.objects.values_list("id", flat=True)) + + p.user.user_permissions.remove(global_permission) + + get_site_preferences()["general__group_types_groups_owners"] = [correct_group_type] + + response, content = client_query( + "{groups{id}}" + ) + assert set(int(g["id"]) for g in content["data"]["groups"]) == {group_correct_group_type_owner.id, group2_correct_group_type_owner.id} + + for g in (group_correct_group_type_owner, group2_correct_group_type_owner): + response, content = client_query( + "query groupById($id: ID) {object: groupById(id: $id) { id } }", + variables={"id": g.id}, + ) + assert content["data"]["object"]["id"] == str(g.id) + + for g in (group_not_owner, group_wrong_group_type_owner, group_no_group_type_owner): + with pytest.raises(GraphQLError): + response, content = client_query( + "query groupById($id: ID) {object: groupById(id: $id) { id } }", + variables={"id": g.id}, + ) diff --git a/aleksis/core/tests/schema/test_persons.py b/aleksis/core/tests/schema/test_persons.py new file mode 100644 index 0000000000000000000000000000000000000000..565d90cabaa65ad17af7810f82299167b0c125d0 --- /dev/null +++ b/aleksis/core/tests/schema/test_persons.py @@ -0,0 +1,83 @@ +import json +import pytest +from graphql.error.graphql_error import GraphQLError + +from aleksis.core.models import Group, GroupType, Person +from django.contrib.auth.models import Permission + +from aleksis.core.util.core_helpers import get_site_preferences + + +pytestmark = pytest.mark.django_db + + +def test_persons_query(client_query): + p = Person.objects.first() + correct_group_type = GroupType.objects.create(name="correct") + wrong_group_type = GroupType.objects.create(name="wrong") + + group_not_owner = Group.objects.create(name="not_owner") + group_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type) + + group2_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type) + group_wrong_group_type_owner = Group.objects.create(name="wrong_group_type_owner", group_type=wrong_group_type) + group_no_group_type_owner = Group.objects.create(name="no_group_type_owner") + + for g in (group_correct_group_type_owner, group2_correct_group_type_owner, group_wrong_group_type_owner, group_no_group_type_owner): + g.owners.add(p) + + correct_member = Person.objects.create(first_name="correct_member", last_name="correct_member") + correct_member_2 = Person.objects.create(first_name="correct_member_2", last_name="correct_member_2") + wrong_member = Person.objects.create(first_name="wrong_member", last_name="wrong_member") + + for g in (group_correct_group_type_owner, group2_correct_group_type_owner): + g.members.add(correct_member, correct_member_2) + + for g in (group_not_owner, group_wrong_group_type_owner, group_no_group_type_owner): + g.members.add(wrong_member) + + + get_site_preferences()["general__group_types_group_members_owners"] = [] + + response, content = client_query( + "{persons{id}}" + ) + assert len(content["data"]["persons"]) == 1 + assert content["data"]["persons"][0]["id"] == str(p.id) + + for g in Person.objects.exclude(pk=p.id): + response, content = client_query( + "query personById($id: ID) {object: personById(id: $id) { id } }", + variables={"id": g.id}, + ) + assert content["data"]["object"] == None + + global_permission = Permission.objects.get(codename="view_person", content_type__app_label="core") + p.user.user_permissions.add(global_permission) + + response, content = client_query( + "{persons{id}}" + ) + assert set(int(g["id"]) for g in content["data"]["persons"]) == set(Person.objects.values_list("id", flat=True)) + + p.user.user_permissions.remove(global_permission) + + get_site_preferences()["general__group_types_group_members_owners"] = [correct_group_type] + + response, content = client_query( + "{persons{id}}" + ) + assert set(int(g["id"]) for g in content["data"]["persons"]) == {p.id, correct_member.id, correct_member_2.id} + + for g in (correct_member, correct_member_2): + response, content = client_query( + "query personById($id: ID) {object: personById(id: $id) { id } }", + variables={"id": g.id}, + ) + assert content["data"]["object"]["id"] == str(g.id) + + response, content = client_query( + "query personById($id: ID) {object: personById(id: $id) { id } }", + variables={"id": wrong_member.id}, + ) + assert content["data"]["object"] == None diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py index 3d91751938c785baf694f379b4126fb879e6a0fc..c01d7ca940930ceb22b1bcd6b67a5bf428c93fc0 100644 --- a/aleksis/core/util/predicates.py +++ b/aleksis/core/util/predicates.py @@ -11,7 +11,7 @@ from guardian.shortcuts import get_objects_for_user from rules import predicate from ..mixins import ExtensibleModel -from ..models import Group, PersonalEvent +from ..models import Group, Person, PersonalEvent from .core_helpers import get_content_type_by_perm, get_site_preferences, queryset_rules_filter from .core_helpers import has_person as has_person_helper @@ -177,3 +177,59 @@ def is_own_celery_task(user: User, obj: Model) -> bool: def is_personal_event_owner(user: User, personal_event: PersonalEvent) -> bool: """Predicate which checks if the user is the owner of the provided custom event.""" return user.person == personal_event.owner + + +@predicate +def is_group_owner_with_group_type(user: User, group: Group) -> bool: + """Predicate whick checks if the user is a owner of the group (with allowed group type).""" + if group.group_type not in get_site_preferences()["general__group_types_groups_owners"]: + return False + return user.person in group.owners.all() + + +@predicate +def is_group_owner_of_person_with_group_type(user: User, person: Person) -> bool: + """Check if the user is a group owner of the person (with allowed group type).""" + return person.member_of.filter( + group_type__in=get_site_preferences()["general__group_types_group_members_owners"], + owners=user.person, + ).exists() + + +@predicate +def is_group_owner_allowed_information(key: str): + """Predicate which checks if the information is allowed for group owners.""" + name = f"is_group_owner_allowed_information:{key}" + + @predicate(name) + def fn() -> bool: + return ( + key + in get_site_preferences()[ + "general__group_types_group_members_owners_allowed_information" + ] + ) + + return fn + + +@predicate +def has_any_group(user: User): + qs = get_objects_for_user(user, "core.view_group", Group) + qs = qs | Group.objects.filter( + owners=user.person, + group_type__in=get_site_preferences()["general__group_types_groups_owners"], + ) + return qs.exists() + + +@predicate +def has_any_person(user: User): + qs = get_objects_for_user(user, "core.view_person", Person.objects.all()) + qs = qs | Person.objects.filter( + member_of__in=Group.objects.filter( + owners=user.person, + group_type__in=get_site_preferences()["general__group_types_group_members_owners"], + ), + ) + return qs.exists() diff --git a/aleksis/core/views.py b/aleksis/core/views.py index cca5f2e36e17e4fc8d3880f1963089e75b911bf6..9d3864a1d3b3e2c9a8c8e935262a93a4b50d5412 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -274,6 +274,13 @@ def groups(request: HttpRequest) -> HttpResponse: # Get all groups groups = get_objects_for_user(request.user, "core.view_group", Group) + groups = ( + groups + | Group.objects.filter( + owners=request.user.person, + group_type__in=get_site_preferences()["general__group_types_groups_owners"], + ) + ).distinct() # Get filter groups_filter = GroupFilter(request.GET, queryset=groups) diff --git a/conftest.py b/conftest.py index e0de0cc90762c0d5230d0aaa7c296d0b7d121b24..1a448a6f93b2cc6ae536c916c8a9ab56d5b9fe5a 100644 --- a/conftest.py +++ b/conftest.py @@ -1 +1,66 @@ +import json + +import pytest +from graphene_django.settings import graphene_settings + pytest_plugins = ("celery.contrib.pytest",) + + +def graphql_query( + query, + operation_name=None, + input_data=None, + variables=None, + headers=None, + client=None, +): + """Do a GraphQL query for testing.""" + graphql_url = graphene_settings.TESTING_ENDPOINT + body = {"query": query} + if operation_name: + body["operationName"] = operation_name + if variables: + body["variables"] = variables + if input_data: + if "variables" in body: + body["variables"]["input"] = input_data + else: + body["variables"] = {"input": input_data} + if headers: + header_params = {"headers": headers} + resp = client.post( + graphql_url, + json.dumps([body]), + content_type="application/json", + **header_params, + ) + else: + resp = client.post( + graphql_url, json.dumps([body]), content_type="application/json" + ) + content = json.loads(resp.content)[0] + return resp, content + + +@pytest.fixture +def logged_in_client(client, django_user_model): + """Provide a logged-in client for testing.""" + from aleksis.core.models import Person + username = "foo" + password = "bar" + + user = django_user_model.objects.create_user(username=username, password=password) + Person.objects.create(user=user, first_name="John", last_name="Doe") + + client.login(username=username, password=password) + + return client + + +@pytest.fixture +def client_query(logged_in_client): + """Do a GraphQL query with a logged-in client.""" + def func(*args, **kwargs): + return graphql_query(*args, **kwargs, client=logged_in_client) + + return func