diff --git a/chrono/api/views.py b/chrono/api/views.py index bd183d54..31d4ffbb 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -721,6 +721,16 @@ def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_li ) +def get_extra_data(request, payload): + extra_data = {} + for k, v in request.data.items(): + if k not in payload: + if isinstance(v, (list, dict)): + raise APIErrorBadRequest(N_('wrong type for extra_data %s value') % k) + extra_data[k] = v + return extra_data + + class Agendas(APIView): serializer_class = serializers.AgendaSerializer @@ -1412,10 +1422,7 @@ class Fillslots(APIView): if cancel_error: raise APIError(N_(cancel_error)) - extra_data = {} - for k, v in request.data.items(): - if k not in serializer.validated_data: - extra_data[k] = v + extra_data = get_extra_data(request, serializer.validated_data) available_desk = None color = None @@ -1791,7 +1798,7 @@ class RecurringFillslots(APIView): ) ) - extra_data = {k: v for k, v in request.data.items() if k not in payload} + extra_data = get_extra_data(request, payload) # don't reload agendas and events types for event in events_to_book: event.agenda = agendas_by_id[event.agenda_id] @@ -2001,7 +2008,7 @@ class EventsFillslots(APIView): ) waiting_list_event_ids = [event.pk for event in events if event.in_waiting_list] - extra_data = {k: v for k, v in request.data.items() if k not in payload} + extra_data = get_extra_data(request, payload) bookings = [make_booking(event, payload, extra_data) for event in events] bookings_to_cancel_out_of_min_delay = Booking.objects.filter( @@ -2336,7 +2343,7 @@ class SubscriptionsAPI(ListAPIView): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) - extra_data = {k: v for k, v in request.data.items() if k not in serializer.validated_data} + extra_data = get_extra_data(request, serializer.validated_data) date_start = serializer.validated_data['date_start'] date_end = serializer.validated_data['date_end'] @@ -2607,7 +2614,7 @@ class BookingAPI(APIView): raise APIErrorBadRequest(N_('user is marked as absent, can not set presence reason'), err=6) serializer.save() - extra_data = {k: v for k, v in request.data.items() if k not in serializer.validated_data} + extra_data = get_extra_data(request, serializer.validated_data) if extra_data: self.booking.extra_data = self.booking.extra_data or {} self.booking.extra_data.update(extra_data) diff --git a/chrono/manager/views.py b/chrono/manager/views.py index efa6abc4..11c3ab02 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -2543,7 +2543,7 @@ class EventCheckView(ViewableAgendaMixin, DetailView): ).values_list('extra_data', flat=True) for extra_data in list(extra_data_from_booked) + list(extra_data_from_subscriptions): for k, v in extra_data.items(): - if k in agenda_filters: + if k in agenda_filters and not isinstance(v, (list, dict)): filters[k].add(v) filters = sorted(filters.items()) filters = {k: sorted(list(v)) for k, v in filters} diff --git a/tests/api/fillslot/test_all.py b/tests/api/fillslot/test_all.py index c9c7fffe..62079ee4 100644 --- a/tests/api/fillslot/test_all.py +++ b/tests/api/fillslot/test_all.py @@ -152,6 +152,22 @@ def test_booking_api(app, user): assert len(resp.json['errors']) == 1 assert 'user_last_name' in resp.json['errors'] + # extra_data list/dict values are refused + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'foo': ['bar', 'baz']}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'foo': {'bar': 'baz'}}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + # test parameters wrongly passed in query are refused resp = app.post_json( '/api/agenda/%s/fillslot/%s/?backoffice_url=https://example.com&label=test' % (agenda.id, event.id), @@ -540,6 +556,22 @@ def test_booking_api_fillslots(app, user): assert len(resp.json['errors']) == 1 assert 'user_last_name' in resp.json['errors'] + # extra_data list/dict values are refused + resp = app.post_json( + '/api/agenda/%s/fillslots/' % agenda.id, + params={'slots': events_ids, 'foo': ['bar', 'baz']}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + resp = app.post_json( + '/api/agenda/%s/fillslots/' % agenda.id, + params={'slots': events_ids, 'foo': {'bar': 'baz'}}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + # empty or missing slots resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': []}, status=400) assert resp.json['err'] == 1 diff --git a/tests/api/fillslot/test_events.py b/tests/api/fillslot/test_events.py index 867c3999..1aa770bf 100644 --- a/tests/api/fillslot/test_events.py +++ b/tests/api/fillslot/test_events.py @@ -122,6 +122,21 @@ def test_api_events_fillslots(app, user): resp = app.post('/api/agenda/foobar/events/fillslots/', status=404) resp = app.post('/api/agenda/0/events/fillslots/', status=404) + params = {'user_external_id': 'user_id', 'slots': 'event,event-2', 'foo': 'bar'} + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 2 + assert [b.extra_data for b in Booking.objects.order_by('-pk')[:2]] == [{'foo': 'bar'}, {'foo': 'bar'}] + + params.update({'foo': ['bar', 'baz']}) + resp = app.post_json(fillslots_url, params=params, status=400) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + + params.update({'foo': {'bar': 'baz'}}) + resp = app.post_json(fillslots_url, params=params, status=400) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_with_cancelled(app, user): diff --git a/tests/api/fillslot/test_events_multiple_agendas.py b/tests/api/fillslot/test_events_multiple_agendas.py index 7ce99e1a..a2daf1ac 100644 --- a/tests/api/fillslot/test_events_multiple_agendas.py +++ b/tests/api/fillslot/test_events_multiple_agendas.py @@ -251,6 +251,25 @@ def test_api_events_fillslots_multiple_agendas(app, user): resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) assert resp.json['errors']['slots'] == ['Missing agenda slug in slot @event'] + params = {'user_external_id': 'user_id', 'slots': event_slugs, 'foo': 'bar'} + resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params) + assert resp.json['booking_count'] == 1 + assert Booking.objects.order_by('-pk')[0].extra_data == {'foo': 'bar'} + + params.update({'foo': ['bar', 'baz']}) + resp = app.post_json( + '/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400 + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + + params.update({'foo': {'bar': 'baz'}}) + resp = app.post_json( + '/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400 + ) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas_with_cancelled(app, user): diff --git a/tests/api/fillslot/test_recurring_events.py b/tests/api/fillslot/test_recurring_events.py index 65665a94..74612d86 100644 --- a/tests/api/fillslot/test_recurring_events.py +++ b/tests/api/fillslot/test_recurring_events.py @@ -169,6 +169,25 @@ def test_recurring_events_api_fillslots(app, user, freezer, action): assert resp.json['err_desc'] == 'invalid payload' assert resp.json['errors']['action'] == ['"invalid" is not a valid choice.'] + params = { + 'user_external_id': 'user_id', + 'slots': 'foo-bar@event:1', + 'foo': 'bar', + } + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 2 + assert [b.extra_data for b in Booking.objects.order_by('-pk')[:2]] == [{'foo': 'bar'}, {'foo': 'bar'}] + + params.update({'foo': ['bar', 'baz']}) + resp = app.post_json(fillslots_url, params=params, status=400) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + + params.update({'foo': {'bar': 'baz'}}) + resp = app.post_json(fillslots_url, params=params, status=400) + assert resp.json['err'] == 1 + assert resp.json['err_class'] == 'wrong type for extra_data foo value' + def test_recurring_events_api_fillslots_waiting_list(app, user, freezer): freezer.move_to('2021-09-06 12:00') diff --git a/tests/api/test_booking.py b/tests/api/test_booking.py index 99fbe215..41bdf73b 100644 --- a/tests/api/test_booking.py +++ b/tests/api/test_booking.py @@ -1021,6 +1021,11 @@ def test_booking_patch_api_extra_data(app, user): assert booking.user_was_present is True # not changed assert booking.extra_data == {'foo': None, 'foooo': 'baz'} # not changed + resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'foo': ['bar', 'baz']}, status=400) + assert resp.json['err_desc'] == 'wrong type for extra_data foo value' + resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'foo': {'bar': 'baz'}}, status=400) + assert resp.json['err_desc'] == 'wrong type for extra_data foo value' + def test_booking_cancellation_api(app, user): agenda = Agenda.objects.create(kind='events') diff --git a/tests/api/test_subscription.py b/tests/api/test_subscription.py index c40ed2a4..5e11a332 100644 --- a/tests/api/test_subscription.py +++ b/tests/api/test_subscription.py @@ -224,6 +224,20 @@ def test_api_create_subscription(app, user): resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400) assert resp.json['errors']['non_field_errors'][0] == 'start_datetime must be before end_datetime' + params = { + 'user_external_id': 'xxx', + 'user_first_name': 'Foo', + 'user_last_name': 'BAR', + 'date_start': '2021-10-01', + 'date_end': '2021-11-01', + 'foo': ['bar', 'baz'], + } + resp = app.post_json('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400) + assert resp.json['err_desc'] == 'wrong type for extra_data foo value' + params.update({'foo': {'bar': 'baz'}}) + resp = app.post_json('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400) + assert resp.json['err_desc'] == 'wrong type for extra_data foo value' + def test_api_create_subscription_check_dates(app, user): agenda = Agenda.objects.create(label='Foo bar', kind='events') diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py index 2cdf57f1..8bc2c011 100644 --- a/tests/manager/test_event.py +++ b/tests/manager/test_event.py @@ -1758,7 +1758,7 @@ def test_event_check_filters(check_types, app, admin_user): user_external_id='user:1', user_first_name='User', user_last_name='foo-val1 bar-none presence', - extra_data={'foo': 'val1'}, + extra_data={'foo': 'val1', 'bar': ['val1']}, # bar is ignored, wrong value user_was_present=True, ) Booking.objects.create( @@ -1837,7 +1837,7 @@ def test_event_check_filters(check_types, app, admin_user): user_external_id='subscription:1', user_first_name='Subscription', user_last_name='foo-val1 bar-none', - extra_data={'foo': 'val1'}, + extra_data={'foo': 'val1', 'bar': ['val1']}, # bar is ignored, wrong value date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), )