def method_not_allowed(error: MethodNotAllowed) -> Tuple[Dict, int]: api_errors = ApiErrors() api_errors.add_error( "global", "La méthode que vous utilisez n'existe pas sur notre serveur") app.logger.error("405 %s" % str(error)) return jsonify(api_errors.errors), 405
def change_beneficiary_email(body: ChangeBeneficiaryEmailBody) -> None: try: users_api.change_user_email(body.token) except InvalidTokenError as error: errors = ApiErrors() errors.status_code = 400 raise errors from error
def check_stock_can_be_created_for_offer(offer: Offer) -> None: check_validation_status(offer) if offer.isFromProvider: api_errors = ApiErrors() api_errors.add_error("global", "Les offres importées ne sont pas modifiables") raise api_errors
def unsubscribe_user(): source_ip = ipaddress.IPv4Address( request.headers.get("X-Forwarded-For", "0.0.0.0")) if source_ip not in SENDINBLUE_IP_RANGE and settings.IS_DEV is False: raise ApiErrors( {"IP": "Source IP address is not whitelisted"}, status_code=401, ) user_email = request.json.get("email") if not user_email: raise ApiErrors( {"email": "Email missing in request payload"}, status_code=400, ) user_to_unsubscribe = find_user_by_email(user_email) if user_to_unsubscribe is None: raise ApiErrors({"User": "******" % user_email}, status_code=400) user_to_unsubscribe.notificationSubscriptions = ({ **user_to_unsubscribe.notificationSubscriptions, "marketing_email": False } if user_to_unsubscribe.notificationSubscriptions is not None else { "marketing_email": False }) repository.save(user_to_unsubscribe)
def check_stock_has_no_custom_reimbursement_rule(stock: Stock): if stock.offer.custom_reimbursement_rules: api_errors = ApiErrors() api_errors.status_code = 400 api_errors.add_error( "price", "Vous ne pouvez pas modifier le prix de cette offre") raise api_errors
def ratelimit_handler(error: Exception) -> tuple[dict, int]: # `pcapi.utis.login_manager` cannot be imported at module-scope, # because the application context may not be available and that # module needs it. from pcapi.utils.login_manager import get_request_authorization identifier = None try: if request.is_json and "identifier" in request.json: identifier = request.json["identifier"] except json.JSONDecodeError as e: logger.info("Could not extract user identifier from request: %s", e) auth = get_request_authorization() if auth and auth.username: identifier = auth.username extra = { "method": request.method, "identifier": identifier, "route": str(request.url_rule), "path": request.path, "queryParams": request.query_string.decode("UTF-8"), } logger.warning("Requests ratelimit exceeded on routes url=%s", request.url, extra=extra) api_errors = ApiErrors() api_errors.add_error( "global", "Nombre de tentatives de connexion dépassé, veuillez réessayer dans une minute" ) return jsonify(api_errors.errors), 429
def validate_email_not_empty(cls, email: str) -> Optional[str]: # typing: ignore # pylint: disable=no-self-argument if not email or email.isspace(): errors = ApiErrors() errors.add_error("email", "L'email renseigné est vide") raise errors return email
def check_offer_name_length_is_valid(offer_name: str): max_offer_name_length = 90 if len(offer_name) > max_offer_name_length: api_error = ApiErrors() api_error.add_error( "name", "Le titre de l’offre doit faire au maximum 90 caractères.") raise api_error
def check_offer_is_editable(offer: Offer): if not offer.isEditable: error = ApiErrors() error.status_code = 400 error.add_error("global", "Les offres importées ne sont pas modifiables") raise error
def check_demarches_simplifiees_webhook_payload(payload: Dict): try: request.form["dossier_id"] except: errors = ApiErrors() errors.add_error("application_id", "Invalid application id") raise errors
def check_stock_is_updatable(stock: Stock) -> None: check_offer_existing_stocks_are_editable(stock.offer) if stock.isEventExpired: api_errors = ApiErrors() api_errors.add_error("global", "Les événements passés ne sont pas modifiables") raise api_errors
def check_offer_is_digital(offer: Offer) -> None: if not offer.isDigital: errors = ApiErrors() errors.add_error( "global", "Impossible de créer des codes d'activation sur une offre non-numérique", ) raise errors
def check_offer_existing_stocks_are_editable(offer: Offer) -> None: check_validation_status(offer) if not offer.isEditable: error = ApiErrors() error.status_code = 400 error.add_error("global", "Les offres importées ne sont pas modifiables") raise error
def check_offer_isbn_is_valid(isbn: str): isbn_length = 13 if not (isbn and isbn.isnumeric() and len(isbn) == isbn_length): api_errors = ApiErrors() api_errors.add_error( "isbn", "Format d’ISBN incorrect. Exemple de format correct : 9782020427852" ) raise api_errors
def ensure_current_user_has_rights(rights, offerer_id, user=current_user): if not user.hasRights(rights, offerer_id): errors = ApiErrors() errors.add_error( "global", "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ) errors.status_code = 403 raise errors
def check_update_only_allowed_offer_fields_for_allocine_offer( updated_fields: set) -> None: rejected_fields = updated_fields - EDITABLE_FIELDS_FOR_ALLOCINE_OFFER if rejected_fields: api_error = ApiErrors() for field in rejected_fields: api_error.add_error(field, "Vous ne pouvez pas modifier ce champ") raise api_error
def check_user_has_access_to_offerer(user: User, offerer_id: int): if not user.has_access(offerer_id): errors = ApiErrors() errors.add_error( "global", "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ) errors.status_code = 403 raise errors
def check_validation_status(offer: Offer) -> None: if offer.validation in (OfferValidationStatus.REJECTED, OfferValidationStatus.PENDING): error = ApiErrors() error.add_error( "global", "Les offres refusées ou en attente de validation ne sont pas modifiables" ) raise error
def check_update_only_allowed_stock_fields_for_allocine_offer( updated_fields: set) -> None: if not updated_fields.issubset(EDITABLE_FIELDS_FOR_ALLOCINE_STOCK): api_errors = ApiErrors() api_errors.status_code = 400 api_errors.add_error( "global", "Pour les offres importées, certains champs ne sont pas modifiables" ) raise api_errors
def internal_error(error: Exception) -> Union[Tuple[Dict, int], HTTPException]: # pass through HTTP errors if isinstance(error, HTTPException): return error app.logger.exception("Unexpected error on method=%s url=%s: %s", request.method, request.url, error) errors = ApiErrors() errors.add_error( "global", "Il semble que nous ayons des problèmes techniques :(" + " On répare ça au plus vite.") return jsonify(errors.errors), 500
def database_error_handler(error: DatabaseError) -> Response: logger.error( "Database error %s with the following query.\n\n🚨🚨🚨🚨🚨 BEFORE COPYING THE QUERY MAKE SURE THERE IS NO SQL INJECTION 🚨🚨🚨🚨🚨🚨\n\n%s;", error.__class__.__name__, error.statement % format_sql_statement_params(error.params), ) errors = ApiErrors() errors.add_error( "global", "Il semble que nous ayons des problèmes techniques :(" + " On répare ça au plus vite.") return jsonify(errors.errors), 500
def check_single_order_by_string(order_by_string): order_by_string = order_by_string.strip(" ") optional_table_prefix = '("?\\w+"?\\.|)' column_identifier = '"?\\w+"?' optional_sorting_order = "(|\\s+desc|\\s+asc)" if not re.match( f"^{optional_table_prefix}{column_identifier}{optional_sorting_order}$", order_by_string, re.IGNORECASE): api_errors = ApiErrors() api_errors.add_error("order_by", 'Invalid order_by field : "%s"' % order_by_string) raise api_errors
def upsert_stocks( offer_id: int, stock_data_list: List[Union[StockCreationBodyModel, StockEditionBodyModel]] ) -> List[Stock]: stocks = [] edited_stocks = [] edited_stocks_previous_beginnings = {} offer = offer_queries.get_offer_by_id(offer_id) for stock_data in stock_data_list: if isinstance(stock_data, StockEditionBodyModel): stock = Stock.queryNotSoftDeleted().filter_by( id=stock_data.id).first_or_404() if stock.offerId != offer_id: errors = ApiErrors() errors.add_error( "global", "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ) errors.status_code = 403 raise errors edited_stocks_previous_beginnings[ stock.id] = stock.beginningDatetime edited_stock = _edit_stock( stock, price=stock_data.price, quantity=stock_data.quantity, beginning=stock_data.beginning_datetime, booking_limit_datetime=stock_data.booking_limit_datetime, ) edited_stocks.append(edited_stock) stocks.append(edited_stock) else: created_stock = _create_stock( offer=offer, price=stock_data.price, quantity=stock_data.quantity, beginning=stock_data.beginning_datetime, booking_limit_datetime=stock_data.booking_limit_datetime, ) stocks.append(created_stock) repository.save(*stocks) for stock in edited_stocks: previous_beginning = edited_stocks_previous_beginnings[stock.id] if stock.beginningDatetime != previous_beginning: _notify_beneficiaries_upon_stock_edit(stock) if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA): redis.add_offer_id(client=app.redis_client, offer_id=offer.id) return stocks
def load_or_raise_error(obj_class, human_id): data = obj_class.query.filter_by(id=dehumanize(human_id)).first() if data is None: errors = ApiErrors() errors.add_error( "global", "Aucun objet ne correspond à cet identifiant dans notre base de données" ) errors.status_code = 400 raise errors return data
def check_update_only_allowed_fields_for_offer_from_provider( updated_fields: set, provider: Provider) -> None: if provider.isAllocine: rejected_fields = updated_fields - EDITABLE_FIELDS_FOR_ALLOCINE_OFFER else: rejected_fields = updated_fields - EDITABLE_FIELDS_FOR_OFFER_FROM_PROVIDER if rejected_fields: api_error = ApiErrors() for field in rejected_fields: api_error.add_error(field, "Vous ne pouvez pas modifier ce champ") raise api_error
def mark_booking_as_used(educational_booking_id: int) -> prebooking_serialization.EducationalBookingResponse: """Mark a booking used by the educational institute Can only work if booking is in CONFIRMED status""" try: educational_booking = api.mark_educational_booking_as_used_by_institute(educational_booking_id) except exceptions.EducationalBookingNotFound: raise ApiErrors({"code": constants.EDUCATIONAL_BOOKING_NOT_FOUND}, status_code=404) except exceptions.EducationalBookingNotConfirmedYet: raise ApiErrors({"code": "EDUCATIONAL_BOOKING_NOT_CONFIRMED_YET"}, status_code=422) return prebooking_serialization.serialize_educational_booking(educational_booking)
def _load_product_by_isbn_and_check_is_gcu_compatible_or_raise_error( isbn: str) -> Product: product = Product.query.filter( Product.extraData["isbn"].astext == isbn).first() if product is None or not product.isGcuCompatible: errors = ApiErrors() errors.add_error( "isbn", "Ce produit n’est pas éligible au pass Culture.", ) errors.status_code = 400 raise errors return product
def signin(body: authentication.SigninRequest) -> authentication.SigninResponse: try: user = users_repo.get_user_with_credentials(body.identifier, body.password) except users_exceptions.UnvalidatedAccount as exc: raise ApiErrors({"code": "EMAIL_NOT_VALIDATED", "general": ["L'email n'a pas été validé."]}) from exc except users_exceptions.CredentialsException as exc: raise ApiErrors({"general": ["Identifiant ou Mot de passe incorrect"]}) from exc users_api.update_last_connection_date(user) return authentication.SigninResponse( access_token=users_api.create_user_access_token(user), refresh_token=create_refresh_token(identity=user.email), )
def list_venue_providers(): venue_id = request.args.get("venueId") if venue_id is None: e = ApiErrors() e.add_error("venueId", "Vous devez obligatoirement fournir le paramètre venueId") return jsonify(e.errors), 400 vp_query = VenueProvider.query.filter_by(venueId=dehumanize(venue_id)) return jsonify([ as_dict(venue_provider, includes=VENUE_PROVIDER_INCLUDES) for venue_provider in vp_query.all() ])
def check_activation_codes_expiration_datetime( activation_codes_expiration_datetime: Optional[datetime], booking_limit_datetime: Optional[datetime], ) -> None: if activation_codes_expiration_datetime is None: return if booking_limit_datetime is None and activation_codes_expiration_datetime is not None: errors = ApiErrors() errors.add_error( "bookingLimitDatetime", ("Une date limite de validité a été renseignée. Dans ce cas, il faut également" " renseigner une date limite de réservation qui doit être antérieure d'au moins 7 jours." ), ) raise errors if (booking_limit_datetime is not None and activation_codes_expiration_datetime < booking_limit_datetime + timedelta(days=7) # type: ignore[operator] ): errors = ApiErrors() errors.add_error( "activationCodesExpirationDatetime", ("La date limite de validité des codes d'activation doit être ultérieure" " d'au moins 7 jours à la date limite de réservation"), ) raise errors