Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app/eventyay/control/forms/global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,22 @@ def __init__(self, *args, **kwargs):
validators=[MinValueValidator(0)],
),
),
(
'allow_all_users_create_organizer',
forms.BooleanField(
label=_('All registered users can create organizers'),
help_text=_('If enabled, all registered users will be allowed to create organizers. System admins can always create organizers.'),
required=False,
),
),
(
'allow_payment_users_create_organizer',
forms.BooleanField(
label=_('All accounts with payment information can create organizers'),
help_text=_('If enabled, users with valid payment information on file will be allowed to create organizers. System admins can always create organizers.'),
required=False,
),
),
]
)

Expand Down Expand Up @@ -321,6 +337,10 @@ def __init__(self, *args, **kwargs):
('maps', _('Maps'), [
'opencagedata_apikey', 'mapquest_apikey', 'leaflet_tiles', 'leaflet_tiles_attribution',
]),
('organizers', _('Organizers'), [
'allow_all_users_create_organizer',
'allow_payment_users_create_organizer',
]),
]


Expand Down
85 changes: 85 additions & 0 deletions app/eventyay/control/permissions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from urllib.parse import quote

from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _

from eventyay.base.models import Organizer
from eventyay.base.models.organizer import OrganizerBillingModel
from eventyay.base.settings import GlobalSettingsObject


def current_url(request):
if request.GET:
Expand Down Expand Up @@ -157,3 +162,83 @@ class StaffMemberRequiredMixin:
def as_view(cls, **initkwargs):
view = super(StaffMemberRequiredMixin, cls).as_view(**initkwargs)
return staff_member_required()(view)


class OrganizerCreationPermissionMixin:
"""
Mixin to check if a user has permission to create organizers.
Can be used in any view that needs to check organizer creation permissions.
"""

def _can_create_organizer(self, user):
"""
Check if the user has permission to create an organizer.

Permission precedence (highest to lowest):
1. System admins (staff with active session) - always allowed
2. Default when both settings are None - allow all users (permissive default)
3. allow_all_users_create_organizer=True - allow all authenticated users
4. allow_payment_users_create_organizer=True - allow users with payment info
5. Both False - deny (admin only)

Note: If allow_all_users=True, it takes precedence over allow_payment_users
(no need to check payment info if all users are already allowed).

Args:
user: The user to check permissions for

Returns:
bool: True if user can create organizers, False otherwise
"""
# System admins can always create organizers
if user.has_active_staff_session(self.request.session.session_key):
return True

# Get global settings
gs = GlobalSettingsObject()
allow_all_users = gs.settings.get('allow_all_users_create_organizer', None, as_type=bool)
allow_payment_users = gs.settings.get('allow_payment_users_create_organizer', None, as_type=bool)

# If neither option is explicitly set, default to allowing all users (permissive default)
if allow_all_users is None and allow_payment_users is None:
return True

# If all users are allowed (takes precedence over payment check)
if allow_all_users:
return True

# If users with payment information are allowed
if allow_payment_users:
return self._user_has_payment_info(user)

# By default, deny access if settings are explicitly set to False
return False

def _user_has_payment_info(self, user):
"""
Check if the user has valid payment information on file.

This checks if any of the user's organizers have billing records with payment method setup.
Checks for:
- stripe_customer_id: Indicates Stripe customer account
- stripe_payment_method_id: Indicates saved payment method

Args:
user: The user to check payment info for

Returns:
bool: True if user has payment info, False otherwise
"""
# Get all organizers where the user is a team member
user_organizers = Organizer.objects.filter(
teams__members=user
).distinct()

# Single query to check if any billing record has payment info
# Check for either stripe_customer_id OR stripe_payment_method_id
return OrganizerBillingModel.objects.filter(
organizer__in=user_organizers
).filter(
(Q(stripe_customer_id__isnull=False) & ~Q(stripe_customer_id='')) |
(Q(stripe_payment_method_id__isnull=False) & ~Q(stripe_payment_method_id=''))
).exists()
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ <h1>{% trans "Organizers" %}</h1>
</button>
</div>
</form>
{% if can_create_organizer %}
<p>
<a href='{% url "control:organizers.add" %}' class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new organizer" %}
</a>
</p>
{% endif %}
<table class="table table-condensed table-hover">
<thead>
<tr>
Expand Down
11 changes: 8 additions & 3 deletions app/eventyay/control/views/organizer_views/organizer_view.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.files import File
from django.db import transaction
from django.db.models import Max, Min, Prefetch, ProtectedError
Expand Down Expand Up @@ -33,6 +33,7 @@
)
from eventyay.control.permissions import (
AdministratorPermissionRequiredMixin,
OrganizerCreationPermissionMixin,
OrganizerPermissionRequiredMixin,
)
from eventyay.control.signals import nav_organizer
Expand All @@ -52,13 +53,16 @@
logger = logging.getLogger(__name__)


class OrganizerCreate(CreateView):
class OrganizerCreate(OrganizerCreationPermissionMixin, CreateView):
model = Organizer
form_class = OrganizerForm
template_name = 'pretixcontrol/organizers/create.html'
context_object_name = 'organizer'

def dispatch(self, request, *args, **kwargs):
# Check if user has permission to create organizers
if not self._can_create_organizer(request.user):
raise PermissionDenied(_('You do not have permission to create organizers. Please contact an administrator.'))
return super().dispatch(request, *args, **kwargs)

@transaction.atomic
Expand Down Expand Up @@ -362,7 +366,7 @@ def get_object(self, queryset=None) -> Organizer:
return self.request.organizer


class OrganizerList(PaginationMixin, ListView):
class OrganizerList(OrganizerCreationPermissionMixin, PaginationMixin, ListView):
model = Organizer
context_object_name = 'organizers'
template_name = 'pretixcontrol/organizers/index.html'
Expand All @@ -379,6 +383,7 @@ def get_queryset(self):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
ctx['can_create_organizer'] = self._can_create_organizer(self.request.user)
return ctx

@cached_property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ <h1>{% translate "Organizers" %}</h1>
</button>
</div>
</form>
{% if staff_session %}
{% if can_create_organizer %}
<p>
<a href='{% url "eventyay_common:organizers.add" %}' class="btn btn-default">
<span class="fa fa-plus"></span>
Expand Down
15 changes: 10 additions & 5 deletions app/eventyay/eventyay_common/views/organizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

from eventyay.base.models import Organizer, Team
from eventyay.control.forms.filter import OrganizerFilterForm
from eventyay.control.permissions import (
OrganizerCreationPermissionMixin,
OrganizerPermissionRequiredMixin,
)
from eventyay.control.views import CreateView, PaginationMixin, UpdateView

from ...control.forms.organizer_forms import OrganizerForm, OrganizerUpdateForm
from ...control.permissions import OrganizerPermissionRequiredMixin

logger = logging.getLogger(__name__)


class OrganizerList(PaginationMixin, ListView):
class OrganizerList(OrganizerCreationPermissionMixin, PaginationMixin, ListView):
model = Organizer
context_object_name = 'organizers'
template_name = 'eventyay_common/organizers/index.html'
Expand All @@ -37,22 +40,24 @@ def get_queryset(self):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
ctx['can_create_organizer'] = self._can_create_organizer(self.request.user)
return ctx

@cached_property
def filter_form(self):
return OrganizerFilterForm(data=self.request.GET, request=self.request)


class OrganizerCreate(CreateView):
class OrganizerCreate(OrganizerCreationPermissionMixin, CreateView):
model = Organizer
form_class = OrganizerForm
template_name = 'eventyay_common/organizers/create.html'
context_object_name = 'organizer'

def dispatch(self, request, *args, **kwargs):
if not request.user.has_active_staff_session(self.request.session.session_key):
raise PermissionDenied()
# Check if user has permission to create organizers
if not self._can_create_organizer(request.user):
raise PermissionDenied(_('You do not have permission to create organizers. Please contact an administrator.'))
return super().dispatch(request, *args, **kwargs)

@transaction.atomic
Expand Down