forked from CGM_Public/pretix_original
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:
@@ -21,7 +21,6 @@ def set_pids(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0092_auto_20180511_1224'),
|
||||
]
|
||||
|
||||
15
src/pretix/base/migrations/0094_auto_20180604_1119.py
Normal file
15
src/pretix/base/migrations/0094_auto_20180604_1119.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.13 on 2018-06-04 11:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0093_auto_20180528_1432'),
|
||||
('pretixapi', '0001_initial')
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
21
src/pretix/base/migrations/0095_auto_20180604_1129.py
Normal file
21
src/pretix/base/migrations/0095_auto_20180604_1129.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.13 on 2018-06-04 11:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0094_auto_20180604_1119'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='logentry',
|
||||
name='oauth_application',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT,
|
||||
to='pretixapi.OAuthApplication'),
|
||||
),
|
||||
]
|
||||
@@ -25,13 +25,13 @@ class UserManager(BaseUserManager):
|
||||
model documentation to see what's so special about our user model.
|
||||
"""
|
||||
|
||||
def create_user(self, email: str, password: str=None, **kwargs):
|
||||
def create_user(self, email: str, password: str = None, **kwargs):
|
||||
user = self.model(email=email, **kwargs)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, email: str, password: str=None): # NOQA
|
||||
def create_superuser(self, email: str, password: str = None): # NOQA
|
||||
# Not used in the software but required by Django
|
||||
if password is None:
|
||||
raise Exception("You must provide a password")
|
||||
|
||||
@@ -36,7 +36,7 @@ def cached_file_delete(sender, instance, **kwargs):
|
||||
|
||||
class LoggingMixin:
|
||||
|
||||
def log_action(self, action, data=None, user=None, api_token=None, save=True):
|
||||
def log_action(self, action, data=None, user=None, api_token=None, auth=None, save=True):
|
||||
"""
|
||||
Create a LogEntry object that is related to this object.
|
||||
See the LogEntry documentation for details.
|
||||
@@ -47,6 +47,8 @@ class LoggingMixin:
|
||||
"""
|
||||
from .log import LogEntry
|
||||
from .event import Event
|
||||
from pretix.api.models import OAuthAccessToken, OAuthApplication
|
||||
from .organizer import TeamAPIToken
|
||||
from ..notifications import get_all_notification_types
|
||||
from ..services.notifications import notify
|
||||
|
||||
@@ -57,7 +59,18 @@ class LoggingMixin:
|
||||
event = self.event
|
||||
if user and not user.is_authenticated:
|
||||
user = None
|
||||
logentry = LogEntry(content_object=self, user=user, action_type=action, event=event, api_token=api_token)
|
||||
|
||||
kwargs = {}
|
||||
if isinstance(auth, OAuthAccessToken):
|
||||
kwargs['oauth_application'] = auth.application
|
||||
elif isinstance(auth, OAuthApplication):
|
||||
kwargs['oauth_application'] = auth
|
||||
elif isinstance(auth, TeamAPIToken):
|
||||
kwargs['api_token'] = auth
|
||||
elif isinstance(api_token, TeamAPIToken):
|
||||
kwargs['api_token'] = api_token
|
||||
|
||||
logentry = LogEntry(content_object=self, user=user, action_type=action, event=event, **kwargs)
|
||||
if data:
|
||||
logentry.data = json.dumps(data, cls=CustomJSONEncoder)
|
||||
if save:
|
||||
@@ -83,4 +96,4 @@ class LoggedModel(models.Model, LoggingMixin):
|
||||
|
||||
return LogEntry.objects.filter(
|
||||
content_type=ContentType.objects.get_for_model(type(self)), object_id=self.pk
|
||||
).select_related('user', 'event')
|
||||
).select_related('user', 'event', 'oauth_application', 'api_token')
|
||||
|
||||
@@ -41,6 +41,7 @@ class LogEntry(models.Model):
|
||||
datetime = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
user = models.ForeignKey('User', null=True, blank=True, on_delete=models.PROTECT)
|
||||
api_token = models.ForeignKey('TeamAPIToken', null=True, blank=True, on_delete=models.PROTECT)
|
||||
oauth_application = models.ForeignKey('pretixapi.OAuthApplication', null=True, blank=True, on_delete=models.PROTECT)
|
||||
event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
action_type = models.CharField(max_length=255)
|
||||
data = models.TextField(default='{}')
|
||||
|
||||
@@ -77,7 +77,7 @@ class WaitingListEntry(LoggedModel):
|
||||
WaitingListEntry.clean_itemvar(self.event, self.item, self.variation)
|
||||
WaitingListEntry.clean_subevent(self.event, self.subevent)
|
||||
|
||||
def send_voucher(self, quota_cache=None, user=None, api_token=None):
|
||||
def send_voucher(self, quota_cache=None, user=None, auth=None):
|
||||
availability = (
|
||||
self.variation.check_quotas(count_waitinglist=False, subevent=self.subevent, _cache=quota_cache)
|
||||
if self.variation
|
||||
@@ -114,8 +114,8 @@ class WaitingListEntry(LoggedModel):
|
||||
'email': self.email,
|
||||
'waitinglistentry': self.pk,
|
||||
'subevent': self.subevent.pk if self.subevent else None,
|
||||
}, user=user, api_token=api_token)
|
||||
self.log_action('pretix.waitinglist.voucher', user=user, api_token=api_token)
|
||||
}, user=user, auth=auth)
|
||||
self.log_action('pretix.waitinglist.voucher', user=user, auth=auth)
|
||||
self.voucher = v
|
||||
self.save()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from pretix.api.models import OAuthApplication
|
||||
from pretix.base.i18n import (
|
||||
LazyCurrencyNumber, LazyDate, LazyLocaleException, LazyNumber, language,
|
||||
)
|
||||
@@ -80,7 +81,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
|
||||
force: bool=False, send_mail: bool=True, user: User=None, mail_text='',
|
||||
count_waitinglist=True, api_token=None) -> Order:
|
||||
count_waitinglist=True, auth=None) -> Order:
|
||||
"""
|
||||
Marks an order as paid. This sets the payment provider, info and date and returns
|
||||
the order object.
|
||||
@@ -123,7 +124,7 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
|
||||
'date': date or now_dt,
|
||||
'manual': manual,
|
||||
'force': force
|
||||
}, user=user, api_token=api_token)
|
||||
}, user=user, auth=auth)
|
||||
order_paid.send(order.event, order=order)
|
||||
|
||||
invoice = None
|
||||
@@ -173,7 +174,7 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
|
||||
return order
|
||||
|
||||
|
||||
def extend_order(order: Order, new_date: datetime, force: bool=False, user: User=None, api_token=None):
|
||||
def extend_order(order: Order, new_date: datetime, force: bool=False, user: User=None, auth=None):
|
||||
"""
|
||||
Extends the deadline of an order. If the order is already expired, the quota will be checked to
|
||||
see if this is actually still possible. If ``force`` is set to ``True``, the result of this check
|
||||
@@ -187,7 +188,7 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
|
||||
order.log_action(
|
||||
'pretix.event.order.expirychanged',
|
||||
user=user,
|
||||
api_token=api_token,
|
||||
auth=auth,
|
||||
data={
|
||||
'expires': order.expires,
|
||||
'state_change': False
|
||||
@@ -203,7 +204,7 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
|
||||
order.log_action(
|
||||
'pretix.event.order.expirychanged',
|
||||
user=user,
|
||||
api_token=api_token,
|
||||
auth=auth,
|
||||
data={
|
||||
'expires': order.expires,
|
||||
'state_change': True
|
||||
@@ -237,24 +238,21 @@ def mark_order_refunded(order, user=None, api_token=None):
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def mark_order_expired(order, user=None, api_token=None):
|
||||
def mark_order_expired(order, user=None, auth=None):
|
||||
"""
|
||||
Mark this order as expired. This sets the payment status and returns the order object.
|
||||
:param order: The order to change
|
||||
:param user: The user that performed the change
|
||||
:param api_token: The API token used to performed the change
|
||||
"""
|
||||
if isinstance(order, int):
|
||||
order = Order.objects.get(pk=order)
|
||||
if isinstance(user, int):
|
||||
user = User.objects.get(pk=user)
|
||||
if isinstance(api_token, int):
|
||||
api_token = TeamAPIToken.objects.get(pk=api_token)
|
||||
with order.event.lock():
|
||||
order.status = Order.STATUS_EXPIRED
|
||||
order.save()
|
||||
|
||||
order.log_action('pretix.event.order.expired', user=user, api_token=api_token)
|
||||
order.log_action('pretix.event.order.expired', user=user, auth=auth)
|
||||
i = order.invoices.filter(is_cancellation=False).last()
|
||||
if i:
|
||||
generate_cancellation(i)
|
||||
@@ -263,7 +261,7 @@ def mark_order_expired(order, user=None, api_token=None):
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def _cancel_order(order, user=None, send_mail: bool=True, api_token=None):
|
||||
def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, oauth_application=None):
|
||||
"""
|
||||
Mark this order as canceled
|
||||
:param order: The order to change
|
||||
@@ -275,13 +273,15 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None):
|
||||
user = User.objects.get(pk=user)
|
||||
if isinstance(api_token, int):
|
||||
api_token = TeamAPIToken.objects.get(pk=api_token)
|
||||
if isinstance(oauth_application, int):
|
||||
oauth_application = OAuthApplication.objects.get(pk=oauth_application)
|
||||
with order.event.lock():
|
||||
if not order.cancel_allowed():
|
||||
raise OrderError(_('You cannot cancel this order.'))
|
||||
order.status = Order.STATUS_CANCELED
|
||||
order.save()
|
||||
|
||||
order.log_action('pretix.event.order.canceled', user=user, api_token=api_token)
|
||||
order.log_action('pretix.event.order.canceled', user=user, auth=api_token or oauth_application)
|
||||
i = order.invoices.filter(is_cancellation=False).last()
|
||||
if i:
|
||||
generate_cancellation(i)
|
||||
@@ -1163,10 +1163,10 @@ def perform_order(self, event: str, payment_provider: str, positions: List[str],
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None):
|
||||
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None):
|
||||
try:
|
||||
try:
|
||||
return _cancel_order(order, user, send_mail, api_token)
|
||||
return _cancel_order(order, user, send_mail, api_token, oauth_application)
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
|
||||
Reference in New Issue
Block a user