Repository: fossasia/open-event-server

Issue:
Access code should only be linked to hidden tickets
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
Right now we are allowing access code to be linked to any ticket.

**To Reproduce**
Steps to reproduce the behavior:
1. Create an access code linking it to a public ticket

**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
We should only allow creating access codes for hidden tickets.


Discussion:

Resolving patch:
diff --git a/app/api/access_codes.py b/app/api/access_codes.py
--- a/app/api/access_codes.py
+++ b/app/api/access_codes.py
@@ -1,9 +1,11 @@
 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
 from flask_rest_jsonapi.exceptions import ObjectNotFound
+from sqlalchemy.orm.exc import NoResultFound
 
 from app.api.bootstrap import api
 from app.api.helpers.db import safe_query
-from app.api.helpers.exceptions import ForbiddenException, UnprocessableEntity
+from app.api.helpers.exceptions import ForbiddenException, ConflictException
+from app.api.helpers.exceptions import UnprocessableEntity
 from app.api.helpers.permission_manager import has_access
 from app.api.helpers.permissions import jwt_required
 from app.api.helpers.query import event_query
@@ -31,11 +33,33 @@ def before_post(self, args, kwargs, data):
         if not has_access('is_coorganizer', event_id=data['event']):
             raise ForbiddenException({'source': ''}, "Minimum Organizer access required")
 
+    def before_create_object(self, data, view_kwargs):
+        """
+        before create object method for AccessCodeListPost Class
+        :param data:
+        :param view_kwargs:
+        :return:
+        """
+        if data.get('tickets', None):
+            for ticket in data['tickets']:
+                # Ensuring that the ticket exists and is hidden.
+                try:
+                    ticket_object = self.session.query(Ticket).filter_by(id=int(ticket),
+                                                                         deleted_at=None).one()
+                    if not ticket_object.is_hidden:
+                        raise ConflictException({'pointer': '/data/relationships/tickets'},
+                                                "Ticket with id {} is public.".format(ticket) +
+                                                " Access code cannot be applied to public tickets")
+                except NoResultFound:
+                    raise ConflictException({'pointer': '/data/relationships/tickets'},
+                                            "Ticket with id {} does not exists".format(str(ticket)))
+
     schema = AccessCodeSchema
     methods = ['POST', ]
     data_layer = {'session': db.session,
-                  'model': AccessCode
-                  }
+                  'model': AccessCode,
+                  'methods': {'before_create_object': before_create_object
+                              }}
 
 
 class AccessCodeList(ResourceList):
diff --git a/app/api/schema/access_codes.py b/app/api/schema/access_codes.py
--- a/app/api/schema/access_codes.py
+++ b/app/api/schema/access_codes.py
@@ -55,17 +55,16 @@ def validate_order_quantity(self, data, original_data):
 
         min_quantity = data.get('min_quantity', None)
         max_quantity = data.get('max_quantity', None)
-        if min_quantity is not None and max_quantity is not None:
-            if min_quantity > max_quantity:
-                raise UnprocessableEntity(
+        tickets_number = data.get('tickets_number', None)
+        if min_quantity and max_quantity and (min_quantity > max_quantity):
+            raise UnprocessableEntity(
                     {'pointer': '/data/attributes/min-quantity'},
                     "min-quantity should be less than max-quantity"
-                )
+            )
 
-        if 'tickets_number' in data and 'max_quantity' in data:
-            if data['tickets_number'] < data['max_quantity']:
-                raise UnprocessableEntity({'pointer': '/data/attributes/tickets-number'},
-                                          "tickets-number should be greater than max-quantity")
+        if tickets_number and max_quantity and (tickets_number < max_quantity):
+            raise UnprocessableEntity({'pointer': '/data/attributes/tickets-number'},
+                                      "tickets-number should be greater than max-quantity")
 
     id = fields.Integer(dump_ony=True)
     code = fields.Str(required=True)


Test patch: