diff --git a/src/pretix/api/urls.py b/src/pretix/api/urls.py index 0a8fce1e2b..56be1365c5 100644 --- a/src/pretix/api/urls.py +++ b/src/pretix/api/urls.py @@ -67,5 +67,8 @@ urlpatterns = [ url(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"), url(r"^oauth/token$", oauth.TokenView.as_view(), name="token"), url(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"), - url(r"^device/initialize", device.InitializeView.as_view(), name="device.initialize"), + url(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"), + url(r"^device/update$", device.UpdateView.as_view(), name="device.update"), + url(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"), + url(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"), ] diff --git a/src/pretix/api/views/device.py b/src/pretix/api/views/device.py index 3a51ae3e70..b8fefdc20d 100644 --- a/src/pretix/api/views/device.py +++ b/src/pretix/api/views/device.py @@ -6,6 +6,7 @@ from rest_framework.exceptions import ValidationError from rest_framework.response import Response from rest_framework.views import APIView +from pretix.api.auth.device import DeviceTokenAuthentication from pretix.base.models import Device from pretix.base.models.devices import generate_api_token @@ -20,6 +21,13 @@ class InitializationRequestSerializer(serializers.Serializer): software_version = serializers.CharField(max_length=190) +class UpdateRequestSerializer(serializers.Serializer): + hardware_brand = serializers.CharField(max_length=190) + hardware_model = serializers.CharField(max_length=190) + software_brand = serializers.CharField(max_length=190) + software_version = serializers.CharField(max_length=190) + + class DeviceSerializer(serializers.ModelSerializer): organizer = serializers.SlugRelatedField(slug_field='slug', read_only=True) @@ -55,5 +63,51 @@ class InitializeView(APIView): device.api_token = generate_api_token() device.save() + device.log_action('pretix.device.initialized', data=serializer.validated_data, auth=device) + + serializer = DeviceSerializer(device) + return Response(serializer.data) + + +class UpdateView(APIView): + authentication_classes = (DeviceTokenAuthentication,) + + def post(self, request, format=None): + serializer = UpdateRequestSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + device = request.auth + device.hardware_brand = serializer.validated_data.get('hardware_brand') + device.hardware_model = serializer.validated_data.get('hardware_model') + device.software_brand = serializer.validated_data.get('software_brand') + device.software_version = serializer.validated_data.get('software_version') + device.save() + device.log_action('pretix.device.updated', data=serializer.validated_data, auth=device) + + serializer = DeviceSerializer(device) + return Response(serializer.data) + + +class RollKeyView(APIView): + authentication_classes = (DeviceTokenAuthentication,) + + def post(self, request, format=None): + device = request.auth + device.api_token = generate_api_token() + device.save() + device.log_action('pretix.device.keyroll', auth=device) + + serializer = DeviceSerializer(device) + return Response(serializer.data) + + +class RevokeKeyView(APIView): + authentication_classes = (DeviceTokenAuthentication,) + + def post(self, request, format=None): + device = request.auth + device.api_token = None + device.save() + device.log_action('pretix.device.keyroll', auth=device) + serializer = DeviceSerializer(device) return Response(serializer.data) diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 14ecef8528..e18eebb1ad 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -25,7 +25,7 @@ from pretix.api.serializers.order import ( OrderRefundSerializer, OrderSerializer, ) from pretix.base.models import ( - Invoice, Order, OrderPayment, OrderPosition, OrderRefund, Quota, + Device, Invoice, Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken, ) from pretix.base.payment import PaymentException @@ -177,6 +177,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): order, user=request.user if request.user.is_authenticated else None, api_token=request.auth if isinstance(request.auth, TeamAPIToken) else None, + device=request.auth if isinstance(request.auth, Device) else None, oauth_application=request.auth.application if isinstance(request.auth, OAuthAccessToken) else None, send_mail=send_mail ) @@ -191,7 +192,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): approve_order( order, user=request.user if request.user.is_authenticated else None, - auth=request.auth if isinstance(request.auth, (TeamAPIToken, OAuthAccessToken)) else None, + auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None, send_mail=send_mail, ) except Quota.QuotaExceededException as e: @@ -210,7 +211,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): deny_order( order, user=request.user if request.user.is_authenticated else None, - auth=request.auth if isinstance(request.auth, (TeamAPIToken, OAuthAccessToken)) else None, + auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None, send_mail=send_mail, comment=comment, ) @@ -267,7 +268,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): mark_order_refunded( order, user=request.user if request.user.is_authenticated else None, - api_token=(request.auth if isinstance(request.auth, TeamAPIToken) else None), + auth=(request.auth if isinstance(request.auth, (TeamAPIToken, OAuthAccessToken, Device)) else None), ) return self.retrieve(request, [], **kwargs) diff --git a/src/pretix/base/migrations/0099_auto_20180912_1035.py b/src/pretix/base/migrations/0099_auto_20180912_1035.py index fea4153cea..8094d39c20 100644 --- a/src/pretix/base/migrations/0099_auto_20180912_1035.py +++ b/src/pretix/base/migrations/0099_auto_20180912_1035.py @@ -37,4 +37,9 @@ class Migration(migrations.Migration): name='device', unique_together={('organizer', 'device_id')}, ), + migrations.AddField( + model_name='logentry', + name='device', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.Device'), + ), ] diff --git a/src/pretix/base/models/base.py b/src/pretix/base/models/base.py index 98abf70924..c4b783b08c 100644 --- a/src/pretix/base/models/base.py +++ b/src/pretix/base/models/base.py @@ -47,6 +47,7 @@ class LoggingMixin: """ from .log import LogEntry from .event import Event + from .devices import Device from pretix.api.models import OAuthAccessToken, OAuthApplication from .organizer import TeamAPIToken from ..notifications import get_all_notification_types @@ -67,6 +68,8 @@ class LoggingMixin: kwargs['oauth_application'] = auth elif isinstance(auth, TeamAPIToken): kwargs['api_token'] = auth + elif isinstance(auth, Device): + kwargs['device'] = auth elif isinstance(api_token, TeamAPIToken): kwargs['api_token'] = api_token @@ -96,4 +99,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', 'oauth_application', 'api_token') + ).select_related('user', 'event', 'oauth_application', 'api_token', 'device') diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index fb5de7d2fd..2f86753ac2 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -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) + device = models.ForeignKey('Device', 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) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index e67a4ea2cb..bfa15dc2ee 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -21,7 +21,7 @@ from pretix.base.i18n import ( LazyCurrencyNumber, LazyDate, LazyLocaleException, LazyNumber, language, ) from pretix.base.models import ( - CartPosition, Event, Item, ItemVariation, Order, OrderPayment, + CartPosition, Device, Event, Item, ItemVariation, Order, OrderPayment, OrderPosition, Quota, User, Voucher, ) from pretix.base.models.event import SubEvent @@ -307,7 +307,7 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None): @transaction.atomic -def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, oauth_application=None): +def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device=None, oauth_application=None): """ Mark this order as canceled :param order: The order to change @@ -319,6 +319,8 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, oauth_ user = User.objects.get(pk=user) if isinstance(api_token, int): api_token = TeamAPIToken.objects.get(pk=api_token) + if isinstance(device, int): + device = Device.objects.get(pk=device) if isinstance(oauth_application, int): oauth_application = OAuthApplication.objects.get(pk=oauth_application) with order.event.lock(): @@ -327,7 +329,7 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, oauth_ order.status = Order.STATUS_CANCELED order.save() - order.log_action('pretix.event.order.canceled', user=user, auth=api_token or oauth_application) + order.log_action('pretix.event.order.canceled', user=user, auth=api_token or oauth_application or device) i = order.invoices.filter(is_cancellation=False).last() if i: generate_cancellation(i) diff --git a/src/pretix/control/templates/pretixcontrol/event/index.html b/src/pretix/control/templates/pretixcontrol/event/index.html index 2f71270283..c77932eac3 100644 --- a/src/pretix/control/templates/pretixcontrol/event/index.html +++ b/src/pretix/control/templates/pretixcontrol/event/index.html @@ -167,6 +167,9 @@
{{ log.oauth_application.name }} {% endif %} + {% elif log.device %} + + {{ log.device.name }} {% elif log.api_token %} {{ log.api_token.name }} diff --git a/src/pretix/control/templates/pretixcontrol/event/logs.html b/src/pretix/control/templates/pretixcontrol/event/logs.html index 3ad186eaaf..e95ab7dc2e 100644 --- a/src/pretix/control/templates/pretixcontrol/event/logs.html +++ b/src/pretix/control/templates/pretixcontrol/event/logs.html @@ -54,6 +54,9 @@
{{ log.oauth_application.name }} {% endif %} + {% elif log.device %} + + {{ log.device.name }} {% elif log.api_token %} {{ log.api_token.name }} diff --git a/src/pretix/control/templates/pretixcontrol/includes/logs.html b/src/pretix/control/templates/pretixcontrol/includes/logs.html index aa2c4ad339..3bdffd840c 100644 --- a/src/pretix/control/templates/pretixcontrol/includes/logs.html +++ b/src/pretix/control/templates/pretixcontrol/includes/logs.html @@ -19,6 +19,9 @@ {{ log.oauth_application.name }} {% endif %} + {% elif log.device %} + + {{ log.device.name }} {% elif log.api_token %} {{ log.api_token.name }} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/devices.html b/src/pretix/control/templates/pretixcontrol/organizers/devices.html index 234ac3d7cd..4d4c4d0ac0 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/devices.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/devices.html @@ -41,7 +41,9 @@ {{ d.device_id }} + {% if d.initialized and not d.api_token %}{% endif %} {{ d.name }} + {% if d.initialized and not d.api_token %}{% endif %} {{ d.hardware_brand|default_if_none:"" }} {{ d.hardware_model|default_if_none:"" }} @@ -55,6 +57,9 @@ {% else %} {% trans "Not yet initialized" %} {% endif %} + {% if d.initialized and not d.api_token %} + {% trans "Revoked" %} + {% endif %} {% if d.all_events %} diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py index 257817f46c..ba77dae1d2 100644 --- a/src/pretix/control/views/dashboards.py +++ b/src/pretix/control/views/dashboards.py @@ -254,7 +254,8 @@ 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', 'api_token', 'oauth_application').order_by('-datetime') + qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application', + 'device').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)) diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 193da1d5ce..50d7636d04 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -875,7 +875,7 @@ class EventLog(EventPermissionRequiredMixin, ListView): def get_queryset(self): qs = self.request.event.logentry_set.all().select_related( - 'user', 'content_type', 'api_token', 'oauth_application' + 'user', 'content_type', 'api_token', 'oauth_application', 'device' ).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', diff --git a/src/pretix/plugins/banktransfer/api.py b/src/pretix/plugins/banktransfer/api.py index e3e02de52d..73af4dae8d 100644 --- a/src/pretix/plugins/banktransfer/api.py +++ b/src/pretix/plugins/banktransfer/api.py @@ -5,6 +5,7 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.mixins import CreateModelMixin from rest_framework.response import Response +from pretix.base.models import Device from pretix.base.models.organizer import TeamAPIToken from .models import BankImportJob, BankTransaction @@ -68,7 +69,7 @@ class BankImportJobViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): return serializer.save() def create(self, request, *args, **kwargs): - perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user) + perm_holder = (request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user) if not perm_holder.has_organizer_permission(request.organizer, 'can_change_orders'): raise PermissionDenied('Invalid set of permissions') serializer = self.get_serializer(data=request.data)