Repository: fossasia/open-event-server

Issue:
Modify permissions for updating Order
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
We need to modify the permissions for updating the order so that we can follow the plan added in the parent issue.
Also remove the required field constraints from the schema.

Parent: #4852 


Discussion:

Resolving patch:
diff --git a/app/api/orders.py b/app/api/orders.py
--- a/app/api/orders.py
+++ b/app/api/orders.py
@@ -191,20 +191,50 @@ def before_get_object(self, view_kwargs):
 
     def before_update_object(self, order, data, view_kwargs):
         """
+        before update object method of order details
+        1. admin can update all the fields.
+        2. event organizer
+            a. own orders: he/she can update selected fields.
+            b. other's orders: can only update the status that too when the order mode is free. No refund system.
+        3. order user can update selected fields of his/her order when the status is pending.
+        The selected fields mentioned above can be taken from get_updatable_fields method from order model.
         :param order:
         :param data:
         :param view_kwargs:
         :return:
         """
-        # Admin can update all the fields while Co-organizer can update only the status
-        if not has_access('is_admin'):
-            for element in data:
-                if element != 'status':
-                    setattr(data, element, getattr(order, element))
+        if (not has_access('is_coorganizer', event_id=order.event_id)) and (not current_user.id == order.user_id):
+            raise ForbiddenException({'pointer': ''}, "Access Forbidden")
 
-        if not has_access('is_coorganizer', event_id=order.event.id):
-            raise ForbiddenException({'pointer': 'data/status'},
-                                     "To update status minimum Co-organizer access required")
+        if has_access('is_coorganizer_but_not_admin', event_id=order.event_id):
+            if current_user.id == order.user_id:
+                # Order created from the tickets tab.
+                for element in data:
+                    if data[element] != getattr(order, element, None) and element not in Order.get_updatable_fields():
+                        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                 "You cannot update {} of an order".format(element))
+
+            else:
+                # Order created from the public pages.
+                for element in data:
+                    if data[element] != getattr(order, element, None):
+                        if element != 'status':
+                            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 current_user.id == order.user_id:
+            if order.status != 'pending':
+                raise ForbiddenException({'pointer': ''},
+                                         "You cannot update a non-pending order")
+            else:
+                for element in data:
+                    if data[element] != getattr(order, element, None) and element not in Order.get_updatable_fields():
+                        raise ForbiddenException({'pointer': 'data/{}'.format(element)},
+                                                 "You cannot update {} of an order".format(element))
 
         if 'order_notes' in data:
             if order.order_notes and data['order_notes'] not in order.order_notes.split(","):
diff --git a/app/api/schema/orders.py b/app/api/schema/orders.py
--- a/app/api/schema/orders.py
+++ b/app/api/schema/orders.py
@@ -1,7 +1,7 @@
 from flask import request
 from marshmallow import post_dump, validates_schema, validate
 from marshmallow_jsonapi import fields
-from marshmallow_jsonapi.flask import Schema, Relationship
+from marshmallow_jsonapi.flask import Relationship
 
 from app import db
 from app.api.helpers.payment import PayPalPaymentsManager
@@ -42,11 +42,11 @@ def initial_values(self, data):
     address = fields.Str()
     city = fields.Str()
     state = fields.Str(db.String)
-    country = fields.Str(required=True)
+    country = fields.Str()
     zipcode = fields.Str()
     completed_at = fields.DateTime(dump_only=True)
     transaction_id = fields.Str(dump_only=True)
-    payment_mode = fields.Str(default="free", required=True,
+    payment_mode = fields.Str(default="free",
                               validate=validate.OneOf(choices=["free", "stripe", "paypal"]))
     paid_via = fields.Str(dump_only=True)
     brand = fields.Str(dump_only=True)
diff --git a/app/models/order.py b/app/models/order.py
--- a/app/models/order.py
+++ b/app/models/order.py
@@ -17,6 +17,13 @@ def get_new_order_identifier():
         return get_new_order_identifier()
 
 
+def get_updatable_fields():
+    """
+    :return: The list of fields which can be modified by the order user using the pre payment form.
+    """
+    return ['country', 'address', 'city', 'state', 'zipcode', 'status', 'paid_via', 'order_notes']
+
+
 class OrderTicket(SoftDeletionModel):
     __tablename__ = 'orders_tickets'
     order_id = db.Column(db.Integer, db.ForeignKey('orders.id', ondelete='CASCADE'), primary_key=True)


Test patch: