diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f92518421e2ab12faf260aee9adff4ef54e5dbdb..dda21399b9592eb7535fba6095dfcaa813a2b228 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,8 +6,16 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
-`2.12.3` - 2023-03-07
----------------------
+`2.12.4`_ - 2023-03-19
+----------------------
+
+Fixed
+~~~~~
+
+* Some GraphQL queries could return more data than permitted in related fields.
+
+`2.12.3`_ - 2023-03-07
+----------------------
 
 Fixed
 ~~~~~
@@ -989,3 +997,4 @@ Fixed
 .. _2.12.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.1
 .. _2.12.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.2
 .. _2.12.3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.3
+.. _2.12.4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.4
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index e12744ff6df91c72f8ec541b8f0c0672d2ddf273..6fb8776532126f2b4e0c47e6e8f874273b01275b 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -35,49 +35,49 @@ rules.add_perm("core.view_persons_rule", view_persons_predicate)
 
 # View person
 view_person_predicate = has_person & (
-    has_global_perm("core.view_person") | has_object_perm("core.view_person") | is_current_person
+    is_current_person | has_global_perm("core.view_person") | has_object_perm("core.view_person")
 )
 rules.add_perm("core.view_person_rule", view_person_predicate)
 
 # View person address
 view_address_predicate = has_person & (
-    has_global_perm("core.view_address") | has_object_perm("core.view_address") | is_current_person
+    is_current_person | has_global_perm("core.view_address") | has_object_perm("core.view_address")
 )
 rules.add_perm("core.view_address_rule", view_address_predicate)
 
 # View person contact details
 view_contact_details_predicate = has_person & (
-    has_global_perm("core.view_contact_details")
+    is_current_person
+    | has_global_perm("core.view_contact_details")
     | has_object_perm("core.view_contact_details")
-    | is_current_person
 )
 rules.add_perm("core.view_contact_details_rule", view_contact_details_predicate)
 
 # View person photo
 view_photo_predicate = has_person & (
-    has_global_perm("core.view_photo") | has_object_perm("core.view_photo") | is_current_person
+    is_current_person | has_global_perm("core.view_photo") | has_object_perm("core.view_photo")
 )
 rules.add_perm("core.view_photo_rule", view_photo_predicate)
 
 # View person avatar image
 view_avatar_predicate = has_person & (
-    has_global_perm("core.view_avatar") | has_object_perm("core.view_avatar") | is_current_person
+    is_current_person | has_global_perm("core.view_avatar") | has_object_perm("core.view_avatar")
 )
 rules.add_perm("core.view_avatar_rule", view_avatar_predicate)
 
 # View persons groups
 view_groups_predicate = has_person & (
-    has_global_perm("core.view_person_groups")
+    is_current_person
+    | has_global_perm("core.view_person_groups")
     | has_object_perm("core.view_person_groups")
-    | is_current_person
 )
 rules.add_perm("core.view_person_groups_rule", view_groups_predicate)
 
 # Edit person
 edit_person_predicate = has_person & (
-    has_global_perm("core.change_person")
+    is_current_person & is_site_preference_set("account", "editable_fields_person")
+    | has_global_perm("core.change_person")
     | has_object_perm("core.change_person")
-    | is_current_person & is_site_preference_set("account", "editable_fields_person")
 )
 rules.add_perm("core.edit_person_rule", edit_person_predicate)
 
@@ -166,9 +166,9 @@ rules.add_perm(
 
 # View person personal details
 view_personal_details_predicate = has_person & (
-    has_global_perm("core.view_personal_details")
+    is_current_person
+    | has_global_perm("core.view_personal_details")
     | has_object_perm("core.view_personal_details")
-    | is_current_person
 )
 rules.add_perm("core.view_personal_details_rule", view_personal_details_predicate)
 
@@ -181,9 +181,9 @@ rules.add_perm("core.change_site_preferences_rule", change_site_preferences)
 
 # Change person preferences
 change_person_preferences = has_person & (
-    has_global_perm("core.change_person_preferences")
+    is_current_person
+    | has_global_perm("core.change_person_preferences")
     | has_object_perm("core.change_person_preferences")
-    | is_current_person
 )
 rules.add_perm("core.change_person_preferences_rule", change_person_preferences)
 
@@ -222,6 +222,12 @@ view_additional_fields_predicate = has_person & (
 )
 rules.add_perm("core.view_additionalfields_rule", view_additional_fields_predicate)
 
+# View group type
+view_group_type_predicate = has_person & (
+    has_global_perm("core.view_grouptype") | has_object_perm("core.view_grouptype")
+)
+rules.add_perm("core.view_grouptype_rule", view_group_type_predicate)
+
 # Edit group type
 change_group_type_predicate = has_person & (
     has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype")
diff --git a/aleksis/core/schema.py b/aleksis/core/schema.py
index fa4c41dfa02badcf36f13fd47a0412d3e7e26b6e..ca8291c93bd808de86adb38e7cdfb155e40939c3 100644
--- a/aleksis/core/schema.py
+++ b/aleksis/core/schema.py
@@ -13,21 +13,190 @@ from .util.frontend_helpers import get_language_cookie
 class NotificationType(DjangoObjectType):
     class Meta:
         model = Notification
+        fields = [
+            "sender",
+            "recipient",
+            "title",
+            "description",
+            "link",
+            "icon",
+            "send_at",
+            "read",
+            "sent",
+            "created",
+            "modified",
+        ]
+
+        @staticmethod
+        def resolve_recipient(root, info, **kwargs):
+            if info.context.user.has_perm("core.view_person_rule", root.recipient):
+                return root.recipient
+            raise PermissionDenied()
 
 
 class PersonType(DjangoObjectType):
     class Meta:
         model = Person
+        fields = [
+            "user",
+            "first_name",
+            "last_name",
+            "additional_name",
+            "short_name",
+            "street",
+            "housenumber",
+            "postal_code",
+            "place",
+            "phone_number",
+            "mobile_number",
+            "email",
+            "date_of_birth",
+            "place_of_birth",
+            "sex",
+            "photo",
+            "avatar",
+            "guardians",
+            "primary_group",
+            "description",
+            "children",
+            "owner_of",
+            "member_of",
+        ]
 
     full_name = graphene.Field(graphene.String)
 
     def resolve_full_name(root: Person, info, **kwargs):
         return root.full_name
 
+    def resolve_street(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_address_rule", root):
+            return root.street
+        return None
+
+    def resolve_housenumber(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_address_rule", root):
+            return root.housenumber
+        return None
+
+    def resolve_postal_code(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_address_rule", root):
+            return root.postal_code
+        return None
+
+    def resolve_place(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_address_rule", root):
+            return root.place
+        return None
+
+    def resolve_phone_number(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_contact_details_rule", root):
+            return root.phone_number
+        return None
+
+    def resolve_mobile_number(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_contact_details_rule", root):
+            return root.mobile_number
+        return None
+
+    def resolve_email(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_contact_details_rule", root):
+            return root.email
+        return None
+
+    def resolve_date_of_birth(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_personal_details_rule", root):
+            return root.date_of_birth
+        return None
+
+    def resolve_place_of_birth(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_personal_details_rule", root):
+            return root.place_of_birth
+        return None
+
+    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 []
+
+    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 []
+
+    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 []
+
+    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 []
+
+    def resolve_primary_group(root, info, **kwargs):  # noqa
+        if info.context.user.has_perm("core.view_group_rule", root.primary_group):
+            return root.primary_group
+        raise PermissionDenied()
+
+    def resolve_photo(root, info, **kwargs):
+        if info.context.user.has_perm("core.view_photo_rule", root):
+            return root.photo
+        return None
+
+    def resolve_avatar(root, info, **kwargs):
+        if info.context.user.has_perm("core.view_avatar_rule", root):
+            return root.avatar
+        return None
+
 
 class GroupType(DjangoObjectType):
     class Meta:
         model = Group
+        fields = [
+            "name",
+            "short_name",
+            "members",
+            "owners",
+            "parent_groups",
+            "group_type",
+            "additional_fields",
+            "photo",
+            "avatar",
+        ]
+
+    @staticmethod
+    def resolve_parent_groups(root, info, **kwargs):
+        return get_objects_for_user(info.context.user, "core.view_group", root.parent_groups.all())
+
+    @staticmethod
+    def resolve_members(root, info, **kwargs):
+        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
+        ]:
+            persons = (persons | Person.objects.get(pk=info.context.user.person.pk)).distinct()
+        return persons
+
+    @staticmethod
+    def resolve_owners(root, info, **kwargs):
+        persons = get_objects_for_user(info.context.user, "core.view_person", root.owners.all())
+        if has_person(info.context.user) and [
+            o for o in root.owners.all() if o.pk == info.context.user.person.pk
+        ]:
+            persons = (persons | Person.objects.get(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_additional_fields(root, info, **kwargs):
+        return get_objects_for_user(
+            info.context.user, "core.view_additionalfield", root.additional_fields.all()
+        )
 
 
 class LanguageType(ObjectType):