Repository: fossasia/open-event-server

Issue:
Discrepancies in cancelling order
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
- The `send_notif_ticket_cancel` function is having missing fields in the body
- The check applied in `before_update_object` in `app/api/orders.py` is incorrect
  Reason: `getattr(order, element, None)` has different output than that of `data[element]`

**To Reproduce**
Steps to reproduce the behavior:
1. Order from a different account than that of the organizer
2. Go to `Manage Events > Tickets > Orders`
3. Try to cancel an order
4. See error

**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
The order should be canceled without any error

**Additional context**
<!-- Add any other context about the problem here. -->
On it



Discussion:

Resolving patch:
diff --git a/app/api/helpers/notification.py b/app/api/helpers/notification.py
--- a/app/api/helpers/notification.py
+++ b/app/api/helpers/notification.py
@@ -304,11 +304,15 @@ def send_notif_ticket_cancel(order):
     send_notification(
         user=order.event.owner,
         title=NOTIFS[TICKET_CANCELLED_ORGANIZER]['title'].format(
-            invoice_id=order.invoice_number
+            invoice_id=order.invoice_number,
+            event_name=order.event.name
         ),
         message=NOTIFS[TICKET_CANCELLED_ORGANIZER]['message'].format(
             cancel_note=order.cancel_note,
-            invoice_id=order.invoice_number
+            invoice_id=order.invoice_number,
+            event_name=order.event.name,
+            cancel_order_page=make_frontend_url('/events/{identifier}/tickets/orders/cancelled'
+                                                .format(identifier=order.event.identifier))
         )
     )
 
diff --git a/app/api/orders.py b/app/api/orders.py
--- a/app/api/orders.py
+++ b/app/api/orders.py
@@ -40,6 +40,20 @@
 alipay_blueprint = Blueprint('alipay_blueprint', __name__, url_prefix='/v1/alipay')
 
 
+def check_event_user_ticket_holders(order, data, element):
+    if element in ['event', 'user'] and data[element]\
+            != str(getattr(order, element, None).id):
+        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                 "You cannot update {} of an order".format(element))
+    elif element == 'ticket_holders':
+        ticket_holders = []
+        for ticket_holder in order.ticket_holders:
+            ticket_holders.append(str(ticket_holder.id))
+        if data[element] != ticket_holders and element not in get_updatable_fields():
+            raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                     "You cannot update {} of an order".format(element))
+
+
 class OrdersListPost(ResourceList):
     """
     OrderListPost class for OrderSchema
@@ -284,26 +298,33 @@ def before_update_object(self, order, data, view_kwargs):
             if current_user.id == order.user_id:
                 # Order created from the tickets tab.
                 for element in data:
-                    if data[element] and data[element]\
-                            != getattr(order, element, None) and element not in get_updatable_fields():
-                        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
-                                                 "You cannot update {} of an order".format(element))
+                    if data[element]:
+                        if element not in ['event', 'ticket_holders', 'user'] and data[element]\
+                                != getattr(order, element, None) and element not in get_updatable_fields():
+                            raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                     "You cannot update {} of an order".format(element))
+                        else:
+                            check_event_user_ticket_holders(order, data, element)
 
             else:
                 # Order created from the public pages.
                 for element in data:
-                    if data[element] and data[element] != getattr(order, element, None):
-                        if element != 'status' and element != 'deleted_at':
-                            raise ForbiddenException({'pointer': 'data/{}'.format(element)},
-                                                     "You cannot update {} of an order".format(element))
-                        elif element == 'status' and order.amount and order.status == 'completed':
-                            # Since we don't have a refund system.
-                            raise ForbiddenException({'pointer': 'data/status'},
-                                                     "You cannot update the status of a completed paid order")
-                        elif element == 'status' and order.status == 'cancelled':
-                            # Since the tickets have been unlocked and we can't revert it.
-                            raise ForbiddenException({'pointer': 'data/status'},
-                                                     "You cannot update the status of a cancelled order")
+                    if data[element]:
+                        if element not in ['event', 'ticket_holders', 'user'] and data[element]\
+                                != getattr(order, element, None):
+                            if element != 'status' and element != 'deleted_at':
+                                raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                         "You cannot update {} of an order".format(element))
+                            elif element == 'status' and order.amount and order.status == 'completed':
+                                # Since we don't have a refund system.
+                                raise ForbiddenException({'pointer': 'data/status'},
+                                                         "You cannot update the status of a completed paid order")
+                            elif element == 'status' and order.status == 'cancelled':
+                                # Since the tickets have been unlocked and we can't revert it.
+                                raise ForbiddenException({'pointer': 'data/status'},
+                                                         "You cannot update the status of a cancelled order")
+                        else:
+                            check_event_user_ticket_holders(order, data, element)
 
         elif current_user.id == order.user_id:
             if order.status != 'initializing' and order.status != 'pending':
@@ -311,14 +332,17 @@ def before_update_object(self, order, data, view_kwargs):
                                          "You cannot update a non-initialized or non-pending order")
             else:
                 for element in data:
-                    if element == 'is_billing_enabled' and order.status == 'completed' and data[element]\
-                            and data[element] != getattr(order, element, None):
-                        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
-                                                 "You cannot update {} of a completed order".format(element))
-                    elif data[element] and data[element]\
-                            != getattr(order, element, None) and element not in get_updatable_fields():
-                        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
-                                                 "You cannot update {} of an order".format(element))
+                    if data[element]:
+                        if element == 'is_billing_enabled' and order.status == 'completed'\
+                                and data[element] != getattr(order, element, None):
+                            raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                     "You cannot update {} of a completed order".format(element))
+                        elif element not in ['event', 'ticket_holders', 'user'] and data[element]\
+                                != getattr(order, element, None) and element not in get_updatable_fields():
+                            raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                     "You cannot update {} of an order".format(element))
+                        else:
+                            check_event_user_ticket_holders(order, data, element)
 
         if has_access('is_organizer', event_id=order.event_id) and 'order_notes' in data:
             if order.order_notes and data['order_notes'] not in order.order_notes.split(","):


Test patch: