diff --git a/src/backoffice/views/assemblies.py b/src/backoffice/views/assemblies.py
index b2ca54024d054b3679937bb6d482162fceadd89a..04f3360b4156a35c6cbc94a92db2ff7f72f5b46c 100644
--- a/src/backoffice/views/assemblies.py
+++ b/src/backoffice/views/assemblies.py
@@ -59,7 +59,7 @@ class CreateAssemblyView(ConferenceLoginRequiredMixin, CreateView):
 
     def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
         member = ConferenceMember.objects.get(conference=self.conference, user=request.user)
-        if (member.is_staff and member.has_perm('core.assembly_team')) or self.conference.is_open:
+        if (member.is_staff and member.has_perms('core.assembly_team')) or self.conference.is_open:
             return super().dispatch(request, *args, **kwargs)
 
         raise PermissionDenied
diff --git a/src/backoffice/views/mixins.py b/src/backoffice/views/mixins.py
index 4fae86fa37db9c33a4e01433dc8794ad3dda9f60..b47660a1d6a07c65f62a5f21ad5e198d2d696abc 100644
--- a/src/backoffice/views/mixins.py
+++ b/src/backoffice/views/mixins.py
@@ -19,6 +19,8 @@ class ConferenceRequiredMixin(PermissionRequiredMixin):
     _conferencemember: ConferenceMember | None = None
 
     permission_required = []
+    require_all_permissions = True
+    require_staff = False
 
     def __init__(self, *args, **kwargs):
         self._conference = kwargs.pop('conference', None)
@@ -66,14 +68,14 @@ class ConferenceRequiredMixin(PermissionRequiredMixin):
 
     @property
     def is_assembly_team(self):
-        return self.conferencemember.user.is_authenticated and self.conferencemember.has_staff_permission('core.assembly_team')
+        return self.conferencemember.user.is_authenticated and self.conferencemember.has_perms('core.assembly_team', require_staff=True)
 
     @property
     def is_channel_team(self):
         return (
             self.conference.support_channels
             and self.conferencemember.user.is_authenticated
-            and self.conferencemember.has_staff_permission(self.conference, 'core.channel_team')
+            and self.conferencemember.has_perms('core.channel_team', require_staff=True)
         )
 
     def dispatch(self, request, *args, **kwargs):
@@ -107,11 +109,12 @@ class ConferenceRequiredMixin(PermissionRequiredMixin):
                     'has_sos': self.conferencemember is not None,
                     'has_assemblies': self.is_assembly_team,
                     'has_channel': self.is_channel_team,
-                    'has_pages': self.conferencemember.has_staff_permission('core.static_pages'),
-                    'has_map': self.conferencemember.has_staff_permission('core.map_edit'),
-                    'has_moderation': self.conferencemember.has_staff_permission('core.moderation'),
-                    'has_schedules': self.conferencemember.has_staff_permission('core.scheduleadmin'),
-                    'has_workadventure': settings.INTEGRATIONS_WORKADVENTURE and self.conferencemember.has_staff_permission('core.workadventure_admin'),
+                    'has_pages': self.conferencemember.has_perms('core.static_pages', require_staff=True),
+                    'has_map': self.conferencemember.has_perms('core.map_edit', require_staff=True),
+                    'has_moderation': self.conferencemember.has_perms('core.moderation', require_staff=True),
+                    'has_schedules': self.conferencemember.has_perms('core.scheduleadmin', require_staff=True),
+                    'has_workadventure': settings.INTEGRATIONS_WORKADVENTURE
+                    and self.conferencemember.has_perms('core.workadventure_admin', require_staff=True),
                 }
             )
         else:
@@ -130,12 +133,20 @@ class ConferenceRequiredMixin(PermissionRequiredMixin):
         return context
 
     def has_permission(self):
-        required = self.get_permission_required()
-        if not required:
+        perms = self.get_permission_required()
+        if not perms:
             return True
 
         cm = self.conferencemember
-        return cm.has_perms(required) if cm is not None else False
+        return (
+            cm.has_perms(
+                *perms,
+                require_all=self.require_all_permissions,
+                require_staff=self.require_staff,
+            )
+            if cm is not None
+            else False
+        )
 
 
 class ConferenceLoginRequiredMixin(LoginRequiredMixin, ConferenceRequiredMixin):
@@ -170,13 +181,13 @@ class AssemblyMixin(ConferenceLoginRequiredMixin):
         assembly = Assembly.objects.get(conference=self.conference, pk=self.request.resolver_match.kwargs.get(self.assembly_url_param))
 
         # check if it's the assembly team
-        if self.conferencemember.has_staff_permission('assembly_team'):
+        if self.conferencemember.has_perms('assembly_team', require_staff=True):
             self._assembly_staff_access = True
             self._staff_access = self._staff_access or assembly.state_assembly != Assembly.State.NONE
             self._staff_mode = True
 
         # check if it's the channel team
-        if self.conferencemember.has_staff_permission('channel_team'):
+        if self.conferencemember.has_perms('channel_team', require_staff=True):
             self._channels_staff_access = True
             self._staff_access = self._staff_access or assembly.state_channel != Assembly.State.NONE
             self._staff_mode = True
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 1ec7d59b9a9048444795f225b8cda60807e48789..0b41c019b0eb2800ac6f6aff8677aa169d774f7e 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -31,7 +31,7 @@ from core.validators import FileSizeValidator, ImageDimensionValidator
 
 
 class AssemblyManager(ConferenceManagerMixin['Assembly']):
-    staff_permissions = ['assembly_team']
+    staff_permissions = ['core.assembly_team']
     assembly_filter = 'self'
 
     def apply_public_filter(self, queryset: 'QuerySet[Assembly]', member: ConferenceMember | None = None) -> 'QuerySet[Assembly]':
@@ -402,7 +402,7 @@ class Assembly(TaggedItemMixin, models.Model):
         if not user.is_authenticated:
             return False
 
-        if staff_can_manage and user.has_conference_staffpermission(self.conference, 'assembly_team', 'channel_team'):
+        if staff_can_manage and user.has_conference_staff_permission(self.conference, 'assembly_team', 'channel_team'):
             return True
 
         return self.members.filter(member=user, can_manage_assembly=True).exists()
@@ -504,7 +504,7 @@ class Assembly(TaggedItemMixin, models.Model):
 
 
 class AssemblyLinkManager(ConferenceManagerMixin['AssemblyLink']):
-    staff_permissions = ['assembly_team']
+    staff_permissions = ['core.assembly_team']
     conference_filter = 'a__conference'
 
     def apply_public_filter(self, queryset: 'QuerySet[AssemblyLink]', member: ConferenceMember) -> 'QuerySet[AssemblyLink]':
@@ -548,7 +548,7 @@ class AssemblyLink(models.Model):
 
 
 class AssemblyMemberManager(ConferenceManagerMixin['AssemblyMember']):
-    staff_permissions = ['assembly_team']
+    staff_permissions = ['core.assembly_team']
     conference_filter = 'assembly__conference'
     assembly_filter = 'assembly'
 
diff --git a/src/core/models/base_managers.py b/src/core/models/base_managers.py
index aa2ff05a5a2e27911d2baaf3814e7dc75b484f4c..7463848f84b1ff3534dacd28ad3dc1ee56973986 100644
--- a/src/core/models/base_managers.py
+++ b/src/core/models/base_managers.py
@@ -184,7 +184,7 @@ class ConferenceManagerMixin(models.Manager, Generic[_ModelType]):
         if not isinstance(member, ConferenceMember):
             return self.conference_accessible(conference=conference, member=member) if show_public else qs.none()
         if member.is_staff and staff_can_manage:
-            if self.staff_permissions is None or (self.staff_permissions and member.has_staff_permission(*self.staff_permissions)):
+            if self.staff_permissions is None or (self.staff_permissions and member.has_perms(*self.staff_permissions, require_staff=True)):
                 return qs
         id_list = (
             self.apply_assembly_rights_filter(qs, member, only_manageable=only_manageable)
diff --git a/src/core/models/conference.py b/src/core/models/conference.py
index f1d5bbe2abbf68f5255a0c6949fbb7cf364f62c1..e06c906f0036790fb484cfe13d44df785184c37f 100644
--- a/src/core/models/conference.py
+++ b/src/core/models/conference.py
@@ -120,7 +120,10 @@ class ConferenceMember(models.Model):
                 self.user = PlatformUser.get_anonymous_user() if user is None else user
                 self.roles = []
 
-            def has_staff_permission(self, *perms, need_all=False) -> bool:
+            def has_perms(
+                self,
+                *perms,
+            ) -> bool:
                 return False
 
             is_authenticated = False
@@ -131,26 +134,22 @@ class ConferenceMember(models.Model):
 
         return AnonMember()
 
-    def has_staff_permission(self, *perms, need_all=False) -> bool:
-        """
-        Check if the user is staff and has the given permission(s) in the context of this conference.
-        :param perms: Permissions to check for.
-        :param need_all: If True, all given permissions must be present, otherwise at least one.
+    def has_perms(self, *perms: str, require_all: bool = True, require_staff: bool = False) -> bool:
+        """Check if a user has a set of permissions.
+
+        Args:
+            *perms (str): The list of permissions to check.
+            require_all (bool, optional): Defines, if all permission are required or just one. Defaults to True.
+            require_staff (bool, optional): If a staff status is also required. Defaults to False.
 
-        :return: True if the user is staff and has the given permission(s), False otherwise.
+        Returns:
+            bool: True if the user has all permissions.
         """
-        if not self.is_staff:
+        if require_staff and not self.is_staff:
             return False
-        if need_all:
-            return all(self.has_perm(('core.' + perm) if '.' not in perm else perm) for perm in perms)
-        else:
-            return any(self.has_perm(('core.' + perm) if '.' not in perm else perm) for perm in perms)
-
-    def has_perm(self, perm):
-        return perm in self.all_permissions
-
-    def has_perms(self, perm_list):
-        return all(perm in self.all_permissions for perm in perm_list)
+        if require_all:
+            return all(perm in self.all_permissions for perm in perms)
+        return any(perm in self.all_permissions for perm in perms)
 
     def __str__(self):
         return f'{self.user.username}@{self.conference.slug}'
diff --git a/src/core/models/events.py b/src/core/models/events.py
index bc1439f10660859c8d9f5911f2003e29d0e558ae..b04f29c132a757c8a58047b5ff57119ee0d34067 100644
--- a/src/core/models/events.py
+++ b/src/core/models/events.py
@@ -60,7 +60,7 @@ class EventDurationField(models.DurationField):
 
 
 class EventManager(ConferenceManagerMixin['Event']):
-    staff_permissions = ['assembly_team', 'scheduleadmin']
+    staff_permissions = ['core.assembly_team', 'core.scheduleadmin']
     conference_filter = 'assembly__conference'
     assembly_filter = 'assembly'
 
diff --git a/src/core/models/pages.py b/src/core/models/pages.py
index 6577a977bd561dd7b120ce4875d26b06a0dcd8f7..1d5c480f93fddee344d22b24b9978c0cecbd8b1f 100644
--- a/src/core/models/pages.py
+++ b/src/core/models/pages.py
@@ -185,7 +185,7 @@ class StaticPageManager(models.Manager):
             return qs
 
         # content team can access non-public pages
-        if user.has_conference_staffpermission(conference, 'static_pages'):
+        if user.has_conference_staff_permission(conference, 'core.static_pages'):
             return qs
 
         # return only pages which are marked public
@@ -231,7 +231,7 @@ class StaticPageManager(models.Manager):
 
         # If Page does not exist, User can create it
         if sp is None:
-            if group_check_failed and not user.has_conference_staffpermission(conference, 'static_pages'):
+            if group_check_failed and not user.has_conference_staff_permission(conference, 'core.static_pages'):
                 return None, True
 
             new_page = StaticPage(
@@ -243,7 +243,7 @@ class StaticPageManager(models.Manager):
 
         # If Page is protected, check if user has staff permission to ignore it
         if group_check_failed or sp.protection == StaticPage.Protection.PERM:
-            if not user.has_conference_staffpermission(conference, 'static_pages'):
+            if not user.has_conference_staff_permission(conference, 'core.static_pages'):
                 return None, True
 
         return sp, True
diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py
index c1c137845ba9350d4fb3557fe1e1fc106f15e3ad..2a1e393205b428293052c90cf5e0517b60b69b90 100644
--- a/src/core/models/rooms.py
+++ b/src/core/models/rooms.py
@@ -39,7 +39,7 @@ class RoomType(models.TextChoices):
 
 
 class RoomManager(ConferenceManagerMixin['Room']):
-    staff_permissions = ['assembly_team']
+    staff_permissions = ['core.assembly_team']
     conference_filter = 'assembly__conference'
     assembly_filter = 'assembly'
 
@@ -446,7 +446,7 @@ class RoomShare(models.Model):
 
 
 class RoomLinkManager(ConferenceManagerMixin['RoomLink']):
-    staff_permissions = ['assembly_team']
+    staff_permissions = ['core.assembly_team']
     conference_filter = 'room__assembly__conference'
     assembly_filter = 'room__assembly'
 
diff --git a/src/core/models/users.py b/src/core/models/users.py
index b6b580e58d8d2ce51654d9fa5d171a955dbd4d90..e46cfcdffa6c61af0f14550fc8ea8b8f7a71d8d0 100644
--- a/src/core/models/users.py
+++ b/src/core/models/users.py
@@ -450,11 +450,17 @@ class PlatformUser(AbstractUser):
 
         return super().save(*args, update_fields=update_fields, **kwargs)
 
-    def has_conference_staffpermission(self, conference, *perms, need_all=False):
-        """
-        Returns True if this user is a staff member of the given conference and has,
-        depending upon need_all, either at least one of the supplied permissions or all of them.
-        Global superuser and staff always return True.
+    def has_conference_staff_permission(self, conference: 'Conference', *perms: str, require_all: bool = False):
+        """Returns True if this user is a staff member of the given conference and has,
+        depending upon require_all, either at least one of the supplied permissions or all of them.
+
+        Args:
+            conference (Conference): The Conference instance to check against.
+            *perms (str): The permissions to check for.
+            require_all (bool, optional): Defines, if all permission are required or just one. Defaults to False.
+
+        Returns:
+            bool: True if the user has all permissions.
         """
 
         if conference is None:
@@ -463,7 +469,7 @@ class PlatformUser(AbstractUser):
 
         try:
             cm: ConferenceMember = conference.users.get(user=self)
-            return cm.has_staff_permission(*perms, need_all=need_all)
+            return cm.has_perms(*perms, require_all=require_all, require_staff=True)
         except ObjectDoesNotExist:
             return False
 
diff --git a/src/core/models/workadventure.py b/src/core/models/workadventure.py
index f96769d71dd2309e7cf1ae0b41b55ced70fe6f09..75f93037f8054af4e3f383ac20e94692de755dca 100644
--- a/src/core/models/workadventure.py
+++ b/src/core/models/workadventure.py
@@ -104,7 +104,7 @@ class WorkadventureSession(models.Model):
             # if this fails the user is no conference member and shall not be able to use the WA anyway
             conference_member = ConferenceMember.objects.get(conference_id=self.conference_id, user_id=self.user_id)
 
-        is_admin = conference_member.is_staff and conference_member.has_perm('core.workadventure_admin')
+        is_admin = conference_member.is_staff and conference_member.has_perms('core.workadventure_admin')
         is_angel = conference_member.active_angel
 
         user_tags = set(data.get('tags', []))
diff --git a/src/plainui/tests/test_views.py b/src/plainui/tests/test_views.py
index 151c19846ac762190881e6700e7d31fc0f6ff89e..0fe5b37e36cebf5a549be6e1745160de762594d9 100644
--- a/src/plainui/tests/test_views.py
+++ b/src/plainui/tests/test_views.py
@@ -9,7 +9,7 @@ from freezegun import freeze_time
 
 from django import forms
 from django.contrib import messages
-from django.contrib.auth.models import Permission
+from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from django.core import mail
 from django.core.exceptions import ImproperlyConfigured
@@ -90,6 +90,10 @@ TEST_CONF_ID_3 = uuid.uuid4()
 
 @override_settings(SELECTED_CONFERENCE_ID=TEST_CONF_ID)
 class ViewsTestBase(TestCase):
+    fixtures = [
+        'bootstrap_auth_groups',
+    ]
+
     def setUp(self):
         self.conf = Conference(
             id=TEST_CONF_ID,
@@ -723,11 +727,11 @@ class ViewsTest(ViewsTestBase):
                 self.conference_member.save()
 
             # namespace required, user doesn't have the required namespace but has static pages permission
-            perm, _unused = Permission.objects.get_or_create(content_type=ContentType.objects.get_for_model(ConferenceMember), codename='static_pages')
+            static_pages_group = Group.objects.get(name='Wiki Team')
             try:
-                self.user.user_permissions.add(perm)
                 self.conference_member.is_staff = True
                 self.conference_member.save()
+                self.conference_member.permission_groups.add(static_pages_group)
                 resp = self.client.get(reverse('plainui:static_page_edit', kwargs={'page_slug': sp.slug}))
                 self.assertEqual(resp.context_data['page'], sp)
                 self.assertEqual(resp.context_data['page_slug'], sp.slug)
@@ -737,7 +741,7 @@ class ViewsTest(ViewsTestBase):
                 self.assertEqual(resp.context_data['form']['title'].value(), r2.title)
                 self.assertEqual(resp.context_data['form']['body'].value(), r2.body)
             finally:
-                self.user.user_permissions.remove(perm)
+                self.conference_member.permission_groups.remove(static_pages_group)
                 self.conference_member.is_staff = False
                 self.conference_member.save()
 
@@ -909,11 +913,10 @@ class ViewsTest(ViewsTestBase):
                     self.conference_member.static_page_groups = []
                     self.conference_member.save()
 
-                # namespace required, user doesn't have that namespace but has static pages staff permission
                 # namespace required, user doesn't have the required namespace but has static pages permission
-                perm, _unused = Permission.objects.get_or_create(content_type=ContentType.objects.get_for_model(ConferenceMember), codename='static_pages')
+                static_pages_group = Group.objects.get(name='Wiki Team')
                 try:
-                    self.user.user_permissions.add(perm)
+                    self.conference_member.permission_groups.add(static_pages_group)
                     self.conference_member.is_staff = True
                     self.conference_member.save()
                     lock = Lock(
@@ -936,7 +939,7 @@ class ViewsTest(ViewsTestBase):
                     self.assertEqual(sp.body, 'New Body5')
                     self.assertEqual(sp_de.revisions.count(), 1)
                 finally:
-                    self.user.user_permissions.remove(perm)
+                    self.conference_member.permission_groups.remove(static_pages_group)
                     self.conference_member.is_staff = False
                     self.conference_member.save()
 
diff --git a/src/plainui/views/static_pages.py b/src/plainui/views/static_pages.py
index e8052657ce8453896fab805dc9eb0aeacae056c1..97bb25a859bab2abe26f95ea9231da92cf2f5529 100644
--- a/src/plainui/views/static_pages.py
+++ b/src/plainui/views/static_pages.py
@@ -85,7 +85,7 @@ class StaticPageView(ConferenceRequiredMixin, TemplateView):
             return context
 
         if static_page.privacy == StaticPage.Privacy.PERM:
-            if not self.request.user.is_authenticated or not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+            if not self.request.user.is_authenticated or not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 context['page_no_permission'] = True
                 context['page_can_edit'] = False
                 context['page'] = None
@@ -147,7 +147,7 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
             # TODO: after redeem, redirect to edit view for page_slug
             return redirect(reverse('plainui:redeem_token'))
 
-        if static_page.privacy == StaticPage.Privacy.PERM and not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+        if static_page.privacy == StaticPage.Privacy.PERM and not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
             messages.error(request, gettext('You do not have the required permissions to edit this page.'))
             return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
@@ -239,7 +239,7 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
             return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         if static_page.privacy == StaticPage.Privacy.PERM:
-            if not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+            if not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         form = StaticPageBodyForm(request.POST)
@@ -345,7 +345,7 @@ class StaticPageHistoryView(ConferenceRequiredMixin, TemplateView):
             return self.handle_no_permission()
 
         if self.static_page.privacy == StaticPage.Privacy.PERM:
-            if not self.request.user.is_authenticated or not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+            if not self.request.user.is_authenticated or not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         return super().get(request, page_slug=page_slug, **kwargs)
@@ -373,7 +373,7 @@ class StaticPageDiffView(ConferenceRequiredMixin, TemplateView):
             return self.handle_no_permission()
 
         if self.static_page.privacy == StaticPage.Privacy.PERM:
-            if not self.request.user.is_authenticated or not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+            if not self.request.user.is_authenticated or not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         return super().get(request, page_slug=page_slug, **kwargs)
@@ -420,7 +420,7 @@ class StaticPageGlobalHistoryView(ConferenceRequiredMixin, TemplateView):
         allowed_privacy = [StaticPage.Privacy.NONE]
         if self.has_ticket(self.request):
             allowed_privacy.append(StaticPage.Privacy.CONFERENCE)
-        if self.request.user.is_authenticated and self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
+        if self.request.user.is_authenticated and self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
             allowed_privacy.append(StaticPage.Privacy.PERM)
 
         static_pages = static_pages.filter(privacy__in=allowed_privacy)