mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Implement OAuth2 provider (#927)
- [x] Application management - [x] Link - [ ] Tests - [x] Authorize flow - [x] Tests - [x] Refresh token handling - [x] Tests - [x] Revocation endpoint - [x] Tests - [x] Mitigate: https://github.com/jazzband/django-oauth-toolkit/issues/585 - [x] API authenticator / permission driver - [x] Test - [x] Enforce organizer restriction - [x] Tests - [x] Enforce scope restriction - [x] Tests - [x] Show current applications to user - [x] Revoke - [x] Tests - [x] Log new authorizations - [x] notify user - [x] Ensure other grant types are not available - [x] Documentation - [x] check if revoking access toking, then refreshing gets rid of organizer constraint - [x] Show logentry foo
This commit is contained in:
@@ -250,7 +250,7 @@ def event_index(request, organizer, event):
|
||||
|
||||
can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
|
||||
request=request)
|
||||
qs = request.event.logentry_set.all().select_related('user', 'content_type').order_by('-datetime')
|
||||
qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application').order_by('-datetime')
|
||||
qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
|
||||
if not request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request=request):
|
||||
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
|
||||
|
||||
@@ -347,6 +347,7 @@ class EventSettingsFormView(EventPermissionRequiredMixin, FormView):
|
||||
for k in form.changed_data
|
||||
}
|
||||
)
|
||||
self.form_success()
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
@@ -824,7 +825,9 @@ class EventLog(EventPermissionRequiredMixin, ListView):
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.logentry_set.all().select_related('user', 'content_type').order_by('-datetime')
|
||||
qs = self.request.event.logentry_set.all().select_related(
|
||||
'user', 'content_type', 'api_token', 'oauth_application'
|
||||
).order_by('-datetime')
|
||||
qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
|
||||
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders',
|
||||
request=self.request):
|
||||
|
||||
140
src/pretix/control/views/oauth.py
Normal file
140
src/pretix/control/views/oauth.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import DetailView, ListView
|
||||
from oauth2_provider.generators import generate_client_secret
|
||||
from oauth2_provider.models import get_application_model
|
||||
from oauth2_provider.scopes import get_scopes_backend
|
||||
from oauth2_provider.views import (
|
||||
ApplicationDelete, ApplicationDetail, ApplicationList,
|
||||
ApplicationRegistration, ApplicationUpdate,
|
||||
)
|
||||
|
||||
from pretix.api.models import (
|
||||
OAuthAccessToken, OAuthApplication, OAuthRefreshToken,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OAuthApplicationListView(ApplicationList):
|
||||
template_name = 'pretixcontrol/oauth/app_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(active=True)
|
||||
|
||||
|
||||
class OAuthApplicationRegistrationView(ApplicationRegistration):
|
||||
template_name = 'pretixcontrol/oauth/app_register.html'
|
||||
|
||||
def get_form_class(self):
|
||||
return forms.modelform_factory(
|
||||
get_application_model(),
|
||||
fields=(
|
||||
"name", "redirect_uris"
|
||||
)
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.client_type = 'confidential'
|
||||
form.instance.authorization_grant_type = 'authorization-code'
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ApplicationUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = OAuthApplication
|
||||
fields = ("name", "client_id", "client_secret", "redirect_uris")
|
||||
|
||||
def clean_client_id(self):
|
||||
return self.instance.client_id
|
||||
|
||||
def clean_client_secret(self):
|
||||
return self.instance.client_secret
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['client_id'].widget.attrs['readonly'] = True
|
||||
self.fields['client_secret'].widget.attrs['readonly'] = True
|
||||
|
||||
|
||||
class OAuthApplicationUpdateView(ApplicationUpdate):
|
||||
template_name = 'pretixcontrol/oauth/app_update.html'
|
||||
|
||||
def get_form_class(self):
|
||||
return ApplicationUpdateForm
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(active=True)
|
||||
|
||||
|
||||
class OAuthApplicationRollView(ApplicationDetail):
|
||||
template_name = 'pretixcontrol/oauth/app_rollkeys.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
messages.success(request, _('A new client secret has been generated and is now effective.'))
|
||||
self.object.client_secret = generate_client_secret()
|
||||
self.object.save()
|
||||
return HttpResponseRedirect(self.object.get_absolute_url())
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(active=True)
|
||||
|
||||
|
||||
class OAuthApplicationDeleteView(ApplicationDelete):
|
||||
template_name = 'pretixcontrol/oauth/app_delete.html'
|
||||
success_url = reverse_lazy("control:user.settings.oauth.apps")
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(active=True)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.object.active = False
|
||||
self.object.save()
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
class AuthorizationListView(ListView):
|
||||
template_name = 'pretixcontrol/oauth/authorized.html'
|
||||
context_object_name = 'tokens'
|
||||
|
||||
def get_queryset(self):
|
||||
return OAuthAccessToken.objects.filter(
|
||||
user=self.request.user
|
||||
).select_related('application').prefetch_related('organizers')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
all_scopes = get_scopes_backend().get_all_scopes()
|
||||
for t in ctx['tokens']:
|
||||
t.scopes_descriptions = [all_scopes[scope] for scope in t.scopes]
|
||||
return ctx
|
||||
|
||||
|
||||
class AuthorizationRevokeView(DetailView):
|
||||
template_name = 'pretixcontrol/oauth/auth_revoke.html'
|
||||
success_url = reverse_lazy("control:user.settings.oauth.list")
|
||||
|
||||
def get_queryset(self):
|
||||
return OAuthAccessToken.objects.filter(user=self.request.user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['application'] = self.get_object().application
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
o = self.get_object()
|
||||
for rt in OAuthRefreshToken.objects.filter(access_token=o):
|
||||
rt.revoke()
|
||||
o.delete()
|
||||
|
||||
messages.success(request, _('Access for the selected application has been revoked.'))
|
||||
return redirect(self.success_url)
|
||||
Reference in New Issue
Block a user