def before_update_object(self, session, data, view_kwargs): """ before update method to verify if session is locked before updating session object :param event: :param data: :param view_kwargs: :return: """ is_organizer = has_access('is_admin') or has_access( 'is_organizer', event_id=session.event_id) if session.is_locked and not is_organizer: raise ForbiddenError( {'pointer': '/data/attributes/is-locked'}, "Locked sessions cannot be edited", ) new_state = data.get('state') if (new_state and new_state != session.state and (is_organizer or (session.ends_at and session.ends_at > datetime.now(pytz.utc)))): # State change detected. Verify that state change is allowed g.send_email = new_state in [ 'accepted', 'rejected', 'confirmed', 'rejected', 'canceled', 'withdrawn', ] key = 'speaker' if is_organizer: key = 'organizer' state_dict = SESSION_STATE_DICT[key] try: state_dict[session.state][new_state] except KeyError: raise ForbiddenError( {'pointer': '/data/attributes/state'}, f'You cannot change a session state from "{session.state}" to "{new_state}"', ) # We allow organizers and admins to edit session without validations complex_field_values = data.get('complex_field_values', 'absent') # Set default to 'absent' to differentiate between None and not sent is_absent = complex_field_values == 'absent' # True if values are not sent in data JSON is_same = data.get( 'complex_field_values') == session.complex_field_values # Using original value to ensure None instead of absent # We stop checking validations for organizers only if they may result in data change or absent. See test_session_forms_api.py for more info if not (is_organizer and (is_absent or is_same)): data[ 'complex_field_values'] = validate_custom_form_constraints_request( 'session', self.resource.schema, session, data)
def before_post(self, args, kwargs, data=None): """ method to add user_id to view_kwargs before post :param args: :param kwargs: :param data: :return: """ data['user'] = current_user.id require_relationship(['event', 'user'], data) if not has_access('is_coorganizer', event_id=data['event']): event = db.session.query(Event).filter_by(id=data['event']).one() if event.state == "draft": raise ObjectNotFound( {'parameter': 'event_id'}, "Event: {} not found".format(data['event']), ) if (get_count( db.session.query(Event).filter_by( id=int(data['event']), is_sessions_speakers_enabled=False)) > 0): raise ForbiddenError({'pointer': ''}, "Speakers are disabled for this Event") check_email_override(data, data['event']) if (data.get('email') and get_count( db.session.query(Speaker).filter_by(event_id=int( data['event']), email=data['email'], deleted_at=None)) > 0): raise ForbiddenError({'pointer': ''}, 'Speaker with this Email ID already exists') if 'sessions' in data: session_ids = data['sessions'] for session_id in session_ids: if not has_access('is_session_self_submitted', session_id=session_id): raise ObjectNotFound( {'parameter': 'session_id'}, f"Session: {session_id} not found", ) excluded = [] if not data.get('email'): # Don't check requirement of email if overriden excluded = ['email'] data[ 'complex_field_values'] = validate_custom_form_constraints_request( 'speaker', self.schema, Speaker(event_id=data['event']), data, excluded)
def before_post(self, args, kwargs, data=None): """ method to add user_id to view_kwargs before post :param args: :param kwargs: :param data: :return: """ require_relationship(['event', 'user'], data) if not has_access('is_coorganizer', event_id=data['event']): event = db.session.query(Event).filter_by(id=data['event']).one() if event.state == "draft": raise ObjectNotFound( {'parameter': 'event_id'}, "Event: {} not found".format(data['event_id']), ) if (get_count( db.session.query(Event).filter_by( id=int(data['event']), is_sessions_speakers_enabled=False)) > 0): raise ForbiddenError({'pointer': ''}, "Speakers are disabled for this Event") if (not data.get('is_email_overridden') and get_count( db.session.query(Speaker).filter_by(event_id=int( data['event']), email=data['email'], deleted_at=None)) > 0): raise ForbiddenError({'pointer': ''}, 'Speaker with this Email ID already exists') if data.get('is_email_overridden') and not has_access( 'is_organizer', event_id=data['event']): raise ForbiddenError( {'pointer': 'data/attributes/is_email_overridden'}, 'Organizer access required to override email', ) elif (data.get('is_email_overridden') and has_access('is_organizer', event_id=data['event']) and not data.get('email')): data['email'] = current_user.email if 'sessions' in data: session_ids = data['sessions'] for session_id in session_ids: if not has_access('is_session_self_submitted', session_id=session_id): raise ObjectNotFound( {'parameter': 'session_id'}, "Session: {} not found".format(session_id), )
def validate_event(user, data): if not user.can_create_event(): raise ForbiddenError({'source': ''}, "Please verify your Email") if data.get('state', None) == 'published' and not user.can_publish_event(): raise ForbiddenError({'source': ''}, "Only verified accounts can publish events") if not data.get('name', None) and data.get('state', None) == 'published': raise ConflictError( {'pointer': '/data/attributes/name'}, "Event Name is required to publish the event", )
def validate_fields(self, data, original_data): is_patch_request = 'id' in original_data['data'] if is_patch_request: try: session = Session.query.filter_by( id=original_data['data']['id']).one() except NoResultFound: raise ObjectNotFound({'parameter': '{id}'}, "Session: not found") if 'starts_at' not in data: data['starts_at'] = session.starts_at if 'ends_at' not in data: data['ends_at'] = session.ends_at if 'event' not in data: data['event'] = session.event_id if data['starts_at'] and data['ends_at']: if data['starts_at'] >= data['ends_at']: raise UnprocessableEntityError( {'pointer': '/data/attributes/ends-at'}, "ends-at should be after starts-at", ) if not is_patch_request and datetime.timestamp( data['starts_at']) <= datetime.timestamp(datetime.now()): raise UnprocessableEntityError( {'pointer': '/data/attributes/starts-at'}, "starts-at should be after current date-time", ) if 'state' in data: if data['state'] not in ('draft', 'pending'): if not has_access('is_coorganizer', event_id=data['event']): raise ForbiddenError({'source': ''}, 'Co-organizer access is required.') if 'track' in data: if not has_access('is_coorganizer', event_id=data['event']): raise ForbiddenError({'source': ''}, 'Co-organizer access is required.') if 'microlocation' in data: if not has_access('is_coorganizer', event_id=data['event']): raise ForbiddenError({'source': ''}, 'Co-organizer access is required.') validate_complex_fields_json(self, data, original_data)
def before_post(self, args, kwargs, data): """ before get method to get the resource id for fetching details :param args: :param kwargs: :param data: :return: """ require_relationship(['group', 'role'], data) group = Group.query.get(data['group']) role = Role.query.get(data['role']) if group.user != current_user: raise ForbiddenError({'pointer': 'group'}, 'Owner access is required.') if role.name != 'organizer': raise ForbiddenError({'pointer': 'role'}, 'Only organizer role is allowed.')
def before_create_object(self, data, view_kwargs): """ before create object method for GroupListPost Class :param data: :param view_kwargs: :return: """ data['user_id'] = current_user.id if not current_user.is_verified: raise ForbiddenError({'source': ''}, 'Access Forbidden') for event in data.get('events', []): if not has_access('is_owner', event_id=event): raise ForbiddenError({'source': ''}, "Event owner access required")
def check_same_event(room_ids): rooms = Microlocation.query.filter(Microlocation.id.in_(room_ids)).all() event_ids = set() for room in rooms: event_ids.add(room.event_id) if len(event_ids) > 1: raise ForbiddenError( {'pointer': '/data/relationships/rooms'}, 'Video Stream can only be created/edited with rooms of a single event', ) if not has_access('is_coorganizer', event_id=event_ids.pop()): raise ForbiddenError( {'pointer': '/data/relationships/rooms'}, "You don't have access to the event of provided rooms", )
def validate_event(user, modules, data): if not user.can_create_event(): raise ForbiddenError({'source': ''}, "Please verify your Email") elif not modules.ticket_include: raise ForbiddenError({'source': ''}, "Ticketing is not enabled in the system") if (data.get('can_pay_by_paypal', False) or data.get('can_pay_by_cheque', False) or data.get('can_pay_by_bank', False) or data.get('can_pay_by_stripe', False)): if not modules.payment_include: raise ForbiddenError({'source': ''}, "Payment is not enabled in the system") if data.get('is_donation_enabled', False) and not modules.donation_include: raise ForbiddenError( {'source': '/data/attributes/is-donation-enabled'}, "Donation is not enabled in the system", ) if data.get('state', None) == 'published' and not user.can_publish_event(): raise ForbiddenError({'source': ''}, "Only verified accounts can publish events") if (not data.get('is_event_online') and data.get('state', None) == 'published' and not data.get('location_name', None)): raise ConflictError( {'pointer': '/data/attributes/location-name'}, "Location is required to publish the event", ) if data.get('location_name', None) and data.get('is_event_online'): raise ConflictError( {'pointer': '/data/attributes/location-name'}, "Online Event does not have any locaton", ) if not data.get('name', None) and data.get('state', None) == 'published': raise ConflictError( {'pointer': '/data/attributes/location-name'}, "Event Name is required to publish the event", ) if data.get('searchable_location_name') and data.get('is_event_online'): raise ConflictError( {'pointer': '/data/attributes/searchable-location-name'}, "Online Event does not have any locaton", )
def before_update_object(self, ticket, data, view_kwargs): """ method to check if paid ticket has payment method before updating ticket object :param ticket: :param data: :param view_kwargs: :return: """ if ticket.type == 'paid' or ticket.type == 'donation': try: event = ( db.session.query(Event) .filter_by(id=ticket.event.id, deleted_at=None) .one() ) except NoResultFound: raise UnprocessableEntityError( {'event_id': ticket.event.id}, "Event does not exist" ) if not event.is_payment_enabled(): raise UnprocessableEntityError( {'event_id': ticket.event.id}, "Event having paid ticket must have a payment method", ) if (data.get('deleted_at') and ticket.has_current_orders): raise ForbiddenError( {'param': 'ticket_id'}, "Can't delete a ticket that has sales", )
def test_errors(self): """Method to test the status code of all errors""" with app.test_request_context(): # Forbidden Error forbidden_error = ForbiddenError({'source': ''}, 'Super admin access is required') self.assertEqual(forbidden_error.status, 403) # Not Found Error not_found_error = NotFoundError({'source': ''}, 'Object not found.') self.assertEqual(not_found_error.status, 404) # Server Error server_error = ServerError({'source': ''}, 'Internal Server Error') self.assertEqual(server_error.status, 500) # UnprocessableEntity Error unprocessable_entity_error = UnprocessableEntityError( {'source': ''}, 'Entity cannot be processed') self.assertEqual(unprocessable_entity_error.status, 422) # Bad Request Error bad_request_error = BadRequestError({'source': ''}, 'Request cannot be served') self.assertEqual(bad_request_error.status, 400)
def test_exceptions(self): """Method to test all exceptions.""" # Unprocessable Entity Exception with self.assertRaises(UnprocessableEntityError): raise UnprocessableEntityError( {'pointer': '/data/attributes/min-quantity'}, "min-quantity should be less than max-quantity", ) # Conflict Exception with self.assertRaises(ConflictError): raise ConflictError({'pointer': '/data/attributes/email'}, "Email already exists") # Forbidden Exception with self.assertRaises(ForbiddenError): raise ForbiddenError({'source': ''}, "Access Forbidden") # Not Found Error with self.assertRaises(NotFoundError): raise NotFoundError({'source': ''}, "Not Found") # Server Error with self.assertRaises(ServerError): raise ServerError({'source': ''}, "Internal Server Error") # Bad Request Error with self.assertRaises(BadRequestError): raise BadRequestError({'source': ''}, "Bad Request") # Method Not Allowed Exception with self.assertRaises(MethodNotAllowed): raise MethodNotAllowed({'source': ''}, "Method Not Allowed")
def is_coorganizer_endpoint_related_to_event(view, view_args, view_kwargs, *args, **kwargs): """ If the authorization header is present (but expired) and the eventbeing accessed is not published - And the user is related to the event (organizer, co-organizer etc) show a 401 - Else show a 404 :param view: :param view_args: :param view_kwargs: :param args: :param kwargs: :return: """ user = get_identity() if user.is_staff: verify_jwt_in_request() return view(*view_args, **view_kwargs) if user.has_event_access(kwargs['event_id']): verify_jwt_in_request() return view(*view_args, **view_kwargs) raise ForbiddenError({'source': ''}, 'Co-organizer access is required.')
def is_coorganizer_but_not_admin(view, view_args, view_kwargs, *args, **kwargs): user = current_user if user.has_event_access(kwargs['event_id']): return view(*view_args, **view_kwargs) return ForbiddenError({'source': ''}, 'Co-organizer access is required.').respond()
def decorated_function(*args, **kwargs): user = current_identity if not user.is_admin and not user.is_super_admin: return ForbiddenError({ 'source': '' }, 'Admin access is required').respond() return f(*args, **kwargs)
def reject_invite(speaker_invite_id): try: speaker_invite = SpeakerInvite.query.filter_by( id=speaker_invite_id).one() except NoResultFound: raise NotFoundError({'source': ''}, 'Speaker Invite Not Found') else: if not current_user.email == speaker_invite.email: raise ForbiddenError({'source': ''}, 'Invitee access is required.') elif speaker_invite.status == 'accepted': raise ConflictError( {'pointer': '/data/status'}, 'Accepted speaker invite can not be rejected.', ) elif speaker_invite.status == 'rejected': raise ConflictError( {'pointer': '/data/status'}, 'Speaker invite is already rejected.', ) try: speaker_invite.status = 'rejected' save_to_db(speaker_invite, {'speaker invite rejected'}) except Exception: raise UnprocessableEntityError( {'source': ''}, 'error while rejecting speaker invite.') return jsonify( success=True, message="Speaker invite rejected successfully", )
def resend_emails(): """ Sends confirmation email for pending and completed orders on organizer request :param order_identifier: :return: JSON response if the email was succesfully sent """ order_identifier = request.json['data']['order'] order = safe_query(db, Order, 'identifier', order_identifier, 'identifier') if (has_access('is_coorganizer', event_id=order.event_id)): if order.status == 'completed' or order.status == 'placed': # fetch tickets attachment order_identifier = order.identifier key = UPLOAD_PATHS['pdf']['tickets_all'].format(identifier=order_identifier) ticket_path = 'generated/tickets/{}/{}/'.format(key, generate_hash(key)) + order_identifier + '.pdf' key = UPLOAD_PATHS['pdf']['order'].format(identifier=order_identifier) invoice_path = 'generated/invoices/{}/{}/'.format(key, generate_hash(key)) + order_identifier + '.pdf' # send email. send_email_to_attendees(order=order, purchaser_id=current_user.id, attachments=[ticket_path, invoice_path]) return jsonify(status=True, message="Verification emails for order : {} has been sent succesfully". format(order_identifier)) else: return UnprocessableEntityError({'source': 'data/order'}, "Only placed and completed orders have confirmation").respond() else: return ForbiddenError({'source': ''}, "Co-Organizer Access Required").respond()
def before_post(self, args, kwargs, data): require_relationship(['event'], data) if not has_access('is_coorganizer', event_id=data['event']): raise ForbiddenError( {'pointer': '/data/relationships/event'}, 'Co-organizer access is required.', )
class VideoStreamModeratorList(ResourceList): def before_post(self, args, kwargs, data): require_relationship(['video_stream'], data) stream = safe_query_kwargs(VideoStream, data, 'video_stream') if not has_access('is_coorganizer', event_id=stream.event_id): raise ForbiddenError({'pointer': 'user_id'}, 'Co-Organizer access required') def after_create_object(self, video_stream_moderator, data, view_kwargs): send_email_to_moderator(video_stream_moderator) def query(self, view_kwargs): query_ = self.session.query(VideoStreamModerator) if user_id := view_kwargs.get('user_id'): if current_user.id != int(user_id): raise ForbiddenError({'pointer': 'user_id'}, "Cannot access other user's data") user = safe_query_kwargs(User, view_kwargs, 'user_id') query_ = query_.filter_by(email=user.email) elif view_kwargs.get('video_stream_id'): stream = safe_query_kwargs(VideoStream, view_kwargs, 'video_stream_id') if not has_access('is_coorganizer', event_id=stream.event_id): raise ForbiddenError({'pointer': 'user_id'}, 'Co-Organizer access required') query_ = query_.filter_by( video_stream_id=view_kwargs['video_stream_id'])
def is_speaker_for_session(view, view_args, view_kwargs, *args, **kwargs): """ Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ user = current_user if user.is_admin or user.is_super_admin: return view(*view_args, **view_kwargs) if user.is_staff: return view(*view_args, **view_kwargs) try: session = Session.query.filter(Session.id == view_kwargs['id']).one() except NoResultFound: return NotFoundError({ 'parameter': 'id' }, 'Session not found.').respond() if user.has_event_access(session.event_id): return view(*view_args, **view_kwargs) if session.speakers: for speaker in session.speakers: if speaker.user_id == user.id: return view(*view_args, **view_kwargs) if session.creator_id == user.id: return view(*view_args, **view_kwargs) return ForbiddenError({'source': ''}, 'Access denied.').respond()
def is_coorganizer_endpoint_related_to_event(view, view_args, view_kwargs, *args, **kwargs): """ If the authorization header is present (but expired) and the event being accessed is not published - And the user is related to the event (organizer, co-organizer etc) show a 401 - Else show a 404 :param view: :param view_args: :param view_kwargs: :param args: :param kwargs: :return: """ user = get_identity() if user.is_staff: _jwt_required(app.config['JWT_DEFAULT_REALM']) return view(*view_args, **view_kwargs) if user.is_organizer(kwargs['event_id']) or user.is_coorganizer( kwargs['event_id']): _jwt_required(app.config['JWT_DEFAULT_REALM']) return view(*view_args, **view_kwargs) return ForbiddenError({ 'source': '' }, 'Co-organizer access is required.').respond()
def before_update_object(self, event, data, view_kwargs): """ method to save image urls before updating event object :param event: :param data: :param view_kwargs: :return: """ is_date_updated = (data.get('starts_at') != event.starts_at or data.get('ends_at') != event.ends_at) is_draft_published = event.state == "draft" and data.get( 'state') == "published" is_event_restored = event.deleted_at and not data.get('deleted_at') if is_date_updated or is_draft_published or is_event_restored: validate_date(event, data) if has_access( 'is_admin') and data.get('deleted_at') != event.deleted_at: if len(event.orders) != 0 and not has_access('is_super_admin'): raise ForbiddenError( {'source': ''}, "Event associated with orders cannot be deleted") event.deleted_at = data.get('deleted_at') if (data.get('original_image_url') and data['original_image_url'] != event.original_image_url): start_image_resizing_tasks(event, data['original_image_url'])
def can_edit_after_cfs_ends(event_id): """ Method to check that user has permission to edit the speaker or session after the CFS ends """ speakers_call = SpeakersCall.query.filter_by( event_id=event_id, deleted_at=None ).one_or_none() not_allowed = not ( has_access('is_admin') or has_access('is_organizer', event_id=event_id) or has_access('is_coorganizer', event_id=event_id) ) if speakers_call: speakers_call_tz = speakers_call.ends_at.tzinfo return not ( speakers_call.ends_at <= datetime.now().replace(tzinfo=speakers_call_tz) and not_allowed ) elif not_allowed: raise ForbiddenError( {'source': '/data/event-id'}, f'Speaker Calls for event {event_id} not found', ) return True
def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs): """ Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ user = current_identity if user.is_admin or user.is_super_admin: return view(*view_args, **view_kwargs) if user.is_staff: return view(*view_args, **view_kwargs) try: session = Session.query.filter( Session.id == kwargs['session_id']).one() except NoResultFound: return NotFoundError({ 'parameter': 'session_id' }, 'Session not found.').respond() if user.is_organizer(session.event_id) or user.is_coorganizer( session.event_id): return view(*view_args, **view_kwargs) if session.creator_id == user.id: return view(*view_args, **view_kwargs) return ForbiddenError({'source': ''}, 'Access denied.').respond()
def before_update_object(self, speaker, data, view_kwargs): """ method to save image urls before updating speaker object :param speaker: :param data: :param view_kwargs: :return: """ if not can_edit_after_cfs_ends(speaker.event_id): raise ForbiddenError( {'source': ''}, "Cannot edit speaker after the call for speaker is ended") if data.get('photo_url') and data['photo_url'] != speaker.photo_url: start_image_resizing_tasks(speaker, data['photo_url']) check_email_override(data, speaker.event_id) excluded = [] if not data.get('email'): # Don't check requirement of email if overriden excluded = ['email'] data[ 'complex_field_values'] = validate_custom_form_constraints_request( 'speaker', self.resource.schema, speaker, data, excluded)
def before_post(self, args, kwargs, data): """ before get method to get the resource id for fetching details :param args: :param kwargs: :param data: :return: """ require_relationship(['session', 'event'], data) if not has_access('is_speaker_for_session', id=data['session']): raise ForbiddenError({'source': ''}, 'Speaker access is required.') if data.get('status'): if not data['status'] == 'pending': raise ForbiddenError( {'source': ''}, 'Speaker Invite can not created with accepted status.')
def after_get_object(self, video_recording, view_kwargs): if not has_access('is_organizer', event_id=video_recording.video_stream.event_id): raise ForbiddenError( {'pointer': 'event_id'}, 'You need to be the event organizer to access video recordings.', )
def before_get_object(self, view_kwargs): """ before get method to get the resource id for fetching details :param view_kwargs: :return: """ if view_kwargs.get('attendee_id'): attendee = safe_query_kwargs(TicketHolder, view_kwargs, 'attendee_id') view_kwargs['id'] = attendee.order.id if view_kwargs.get('order_identifier'): order = safe_query_kwargs(Order, view_kwargs, 'order_identifier', 'identifier') view_kwargs['id'] = order.id elif view_kwargs.get('id'): order = safe_query_by_id(Order, view_kwargs['id']) if not has_access( 'is_coorganizer_or_user_itself', event_id=order.event_id, user_id=order.user_id, ): raise ForbiddenError( {'source': ''}, 'You can only access your orders or your event\'s orders') # expire the initializing order if time limit is over. set_expiry_for_order(order)
def send_receipt(): """ Send receipts to attendees related to the provided order. :return: """ order_identifier = request.json.get('order-identifier') if order_identifier: try: order = db.session.query(Order).filter_by( identifier=order_identifier).one() except NoResultFound: return NotFoundError({ 'parameter': '{order_identifier}' }, "Order not found").respond() if (order.user_id != current_user.id) and (not has_access( 'is_registrar', event_id=order.event_id)): return ForbiddenError({ 'source': '' }, 'You need to be the event organizer or order buyer to send receipts.' ).respond() elif order.status != 'completed': abort( make_response( jsonify( error="Cannot send receipt for an incomplete order"), 409)) else: send_email_to_attendees(order, current_user.id) return jsonify(message="receipt sent to attendees") else: return UnprocessableEntityError({ 'source': '' }, 'Order identifier missing').respond()
def query(self, view_kwargs): """ query method for Attendees List :param view_kwargs: :return: """ query_ = self.session.query(TicketHolder) if view_kwargs.get('order_identifier'): order = safe_query_kwargs( Order, view_kwargs, 'order_identifier', 'identifier', ) is_coorganizer = has_access( 'is_coorganizer', event_id=order.event_id, ) if not ( is_coorganizer or current_user.id == order.user_id or order.is_attendee(current_user) ): raise ForbiddenError({'source': ''}, 'Access Forbidden') query_ = query_.join(Order).filter(Order.id == order.id) if not is_coorganizer and current_user.id != order.user_id: query_ = query_.filter(TicketHolder.user == current_user) if view_kwargs.get('ticket_id'): ticket = safe_query_kwargs(Ticket, view_kwargs, 'ticket_id') # if not has_access('is_registrar', event_id=ticket.event_id): # raise ForbiddenError({'source': ''}, 'Access Forbidden') query_ = query_.join(Ticket).filter(Ticket.id == ticket.id) if view_kwargs.get('user_id'): user = safe_query_kwargs(User, view_kwargs, 'user_id') if not has_access('is_user_itself', user_id=user.id): raise ForbiddenError({'source': ''}, 'Access Forbidden') query_ = query_.join(User, User.email == TicketHolder.email).filter( User.id == user.id ) query_ = event_query(query_, view_kwargs, restrict=True) return query_