Ejemplo n.º 1
0
def initialize_account(
    user: User,
    password: str,
    apps_flyer_user_id: str = None,
    apps_flyer_platform: str = None,
    send_activation_mail: bool = True,
    remote_updates: bool = True,
) -> User:

    user.setPassword(password)
    if apps_flyer_user_id and apps_flyer_platform:
        if user.externalIds is None:
            user.externalIds = {}
        user.externalIds["apps_flyer"] = {"user": apps_flyer_user_id, "platform": apps_flyer_platform.upper()}

    repository.save(user)
    logger.info("Created user account", extra={"user": user.id})
    delete_all_users_tokens(user)

    if remote_updates:
        update_external_user(user)

    if not user.isEmailValidated and send_activation_mail:
        request_email_confirmation(user)

    return user
Ejemplo n.º 2
0
def activate_beneficiary(
    user: users_models.User, deposit_source: str = None, has_activated_account: Optional[bool] = True
) -> users_models.User:
    if not deposit_source:
        beneficiary_import = subscription_repository.get_beneficiary_import_for_beneficiary(user)
        if not beneficiary_import:
            raise exceptions.BeneficiaryImportMissingException()

        eligibility = beneficiary_import.eligibilityType
        deposit_source = beneficiary_import.get_detailed_source()
    else:
        eligibility = users_models.EligibilityType.AGE18

    if eligibility == users_models.EligibilityType.UNDERAGE:
        user.add_underage_beneficiary_role()
    elif eligibility == users_models.EligibilityType.AGE18:
        user.add_beneficiary_role()
    else:
        raise users_exception.InvalidEligibilityTypeException()

    if "apps_flyer" in user.externalIds:
        apps_flyer_job.log_user_becomes_beneficiary_event_job.delay(user.id)

    deposit = payments_api.create_deposit(user, deposit_source=deposit_source, eligibility=eligibility)

    db.session.add_all((user, deposit))
    db.session.commit()
    logger.info("Activated beneficiary and created deposit", extra={"user": user.id, "source": deposit_source})

    db.session.refresh(user)

    update_external_user(user)
    _send_beneficiary_activation_email(user, has_activated_account)

    return user
Ejemplo n.º 3
0
def update_user_profile(
    user: User, body: serializers.UserProfileUpdateRequest
) -> serializers.UserProfileResponse:
    api.update_notification_subscription(user, body.subscriptions)
    update_external_user(user)

    return serializers.UserProfileResponse.from_orm(user)
Ejemplo n.º 4
0
def signup_webapp():
    objects_to_save = []
    check_valid_signup_webapp(request)

    new_user = User(from_dict=request.json)
    new_user.email = sanitize_email(new_user.email)
    new_user.notificationSubscriptions = asdict(
        NotificationSubscriptions(marketing_email=bool(request.json.get("contact_ok")))
    )

    if settings.IS_INTEGRATION:
        objects_to_save.append(payments_api.create_deposit(new_user, "test"))
    else:
        authorized_emails, departement_codes = get_authorized_emails_and_dept_codes(ttl_hash=get_ttl_hash())
        departement_code = _get_departement_code_when_authorized_or_error(authorized_emails, departement_codes)
        new_user.departementCode = departement_code

    new_user.remove_admin_role()
    new_user.remove_beneficiary_role()
    new_user.isEmailValidated = True
    new_user.needsToFillCulturalSurvey = False
    new_user.hasSeenTutorials = True
    objects_to_save.append(new_user)

    repository.save(*objects_to_save)

    update_external_user(new_user)

    return jsonify(as_dict(new_user, includes=BENEFICIARY_INCLUDES)), 201
Ejemplo n.º 5
0
def mark_as_used_with_uncancelling(booking: Booking) -> None:
    """Mark a booking as used from cancelled status.

    This function should be called only if the booking
    has been cancelled by mistake or fraudulently after the offer was
    retrieved (for example, when a beneficiary retrieved a book from a
    library and then cancelled their booking before the library marked
    it as used).
    """
    # I'm not 100% sure the transaction is required here
    # It is not clear to me wether or not Flask-SQLAlchemy will make
    # a rollback if we raise a validation exception.
    # Since I lock the stock, I really want to make sure the lock is
    # removed ASAP.
    with transaction():
        if booking.isCancelled or booking.status == BookingStatus.CANCELLED:
            booking.uncancel_booking_set_used()
            stock = offers_repository.get_and_lock_stock(stock_id=booking.stockId)
            stock.dnBookedQuantity += booking.quantity
            db.session.add(stock)
    db.session.add(booking)
    db.session.commit()
    logger.info("Booking was uncancelled and marked as used", extra={"bookingId": booking.id})

    if booking.individualBookingId is not None:
        update_external_user(booking.individualBooking.user)
Ejemplo n.º 6
0
def _cancel_booking(booking: Booking, reason: BookingCancellationReasons) -> None:
    """Cancel booking and update a user's credit information on Batch"""
    with transaction():
        stock = offers_repository.get_and_lock_stock(stock_id=booking.stockId)
        db.session.refresh(booking)
        try:
            booking.cancel_booking()
        except (BookingIsAlreadyUsed, BookingIsAlreadyCancelled) as e:
            logger.info(
                str(e),
                extra={
                    "booking": booking.id,
                    "reason": str(reason),
                },
            )
            return
        booking.cancellationReason = reason
        stock.dnBookedQuantity -= booking.quantity
        repository.save(booking, stock)
    logger.info(
        "Booking has been cancelled",
        extra={
            "booking": booking.id,
            "reason": str(reason),
        },
    )

    if booking.individualBooking is not None:
        update_external_user(booking.individualBooking.user)

    search.async_index_offer_ids([booking.stock.offerId])
Ejemplo n.º 7
0
def mark_as_unused(booking: Booking) -> None:
    validation.check_can_be_mark_as_unused(booking)
    booking.mark_as_unused_set_confirmed()
    repository.save(booking)
    logger.info("Booking was marked as unused", extra={"booking": booking.id})

    if booking.individualBookingId is not None:
        update_external_user(booking.individualBooking.user)
 def after_model_change(self, form: Form, model: User,
                        is_created: bool) -> None:
     update_external_user(model)
     if is_created and not send_activation_email(model):
         token = create_reset_password_token(model)
         flash(
             f"L'envoi d'email a échoué. Le mot de passe peut être réinitialisé depuis le lien suivant : {build_pc_webapp_reset_password_link(token.value)}",
             "error",
         )
     super().after_model_change(form, model, is_created)
def test_update_external_pro_user():
    user = ProFactory()

    n_query_get_user = 1
    n_query_is_pro = 1

    with assert_num_queries(n_query_get_user + n_query_is_pro):
        update_external_user(user)

    assert len(batch_testing.requests) == 0
    assert len(sendinblue_testing.sendinblue_requests) == 1
def test_update_external_user():
    user = BeneficiaryGrant18Factory()
    IndividualBookingFactory(individualBooking__user=user)

    n_query_get_user = 1
    n_query_get_bookings = 1
    n_query_get_deposit = 1
    n_query_is_pro = 1
    n_query_get_last_favorite = 1

    with assert_num_queries(n_query_get_user + n_query_get_bookings +
                            n_query_get_deposit + n_query_is_pro +
                            n_query_get_last_favorite):
        update_external_user(user)

    assert len(batch_testing.requests) == 1
    assert len(sendinblue_testing.sendinblue_requests) == 1
    def save(cls,
             beneficiary_pre_subscription: BeneficiaryPreSubscription,
             user: Optional[User] = None) -> User:
        user_sql_entity = beneficiary_pre_subscription_sql_converter.to_model(
            beneficiary_pre_subscription, user=user)
        users_api.attach_beneficiary_import_details(
            user_sql_entity, beneficiary_pre_subscription)
        repository.save(user_sql_entity)

        if not users_api.steps_to_become_beneficiary(user_sql_entity):
            user_sql_entity = subscription_api.check_and_activate_beneficiary(
                user_sql_entity.id,
                beneficiary_pre_subscription.deposit_source,
                has_activated_account=user is not None)
        else:
            update_external_user(user_sql_entity)

        return user_sql_entity
Ejemplo n.º 12
0
def validate_email(body: ValidateEmailRequest) -> ValidateEmailResponse:
    user = users_repo.get_user_with_valid_token(
        body.email_validation_token, [TokenType.EMAIL_VALIDATION], use_token=False
    )

    if not user:
        raise ApiErrors({"token": ["Le token de validation d'email est invalide."]})

    user.isEmailValidated = True
    repository.save(user)
    update_external_user(user)

    response = ValidateEmailResponse(
        access_token=users_api.create_user_access_token(user),
        refresh_token=create_refresh_token(identity=user.email),
    )

    return response
Ejemplo n.º 13
0
def update_last_connection_date(user):
    previous_connection_date = user.lastConnectionDate
    last_connection_date = datetime.utcnow()

    should_save_last_connection_date = (
        not previous_connection_date or last_connection_date - previous_connection_date > timedelta(minutes=15)
    )
    should_update_sendinblue_last_connection_date = should_save_last_connection_date and (
        not previous_connection_date
        or last_connection_date.date() - previous_connection_date.date() >= timedelta(days=1)
    )

    if should_save_last_connection_date:
        user.lastConnectionDate = last_connection_date
        repository.save(user)

    if should_update_sendinblue_last_connection_date:
        update_external_user(user, skip_batch=True)
Ejemplo n.º 14
0
def add_to_favorite():
    mediation = None
    offer_id = request.json.get("offerId")
    mediation_id = request.json.get("mediationId")
    check_offer_id_is_present_in_request(offer_id)

    offer = load_or_404(Offer, offer_id)
    if mediation_id is not None:
        mediation = load_or_404(Mediation, mediation_id)

    favorite_sql_entity = Favorite()
    favorite_sql_entity.mediation = mediation
    favorite_sql_entity.offer = offer
    favorite_sql_entity.user = current_user
    repository.save(favorite_sql_entity)

    favorite = favorite_domain_converter.to_domain(favorite_sql_entity)
    update_external_user(current_user)

    return jsonify(serialize_favorite(favorite)), 201
Ejemplo n.º 15
0
def process_beneficiary_application(
    information: fraud_models.DMSContent,
    procedure_id: int,
    preexisting_account: Optional[users_models.User] = None,
) -> None:
    """
    Create/update a user account and complete the import process.
    Note that a 'user' is not always a beneficiary.
    """
    user = create_beneficiary_from_application(information, user=preexisting_account)
    user.hasCompletedIdCheck = True
    try:
        repository.save(user)
    except ApiErrors as api_errors:
        logger.warning(
            "[BATCH][REMOTE IMPORT BENEFICIARIES] Could not save application %s, because of error: %s - Procedure %s",
            information.application_id,
            api_errors,
            procedure_id,
        )
        return

    logger.info(
        "[BATCH][REMOTE IMPORT BENEFICIARIES] Successfully created user for application %s - Procedure %s",
        information.application_id,
        procedure_id,
    )

    beneficiary_import = save_beneficiary_import_with_status(
        ImportStatus.CREATED,
        information.application_id,
        source=BeneficiaryImportSources.demarches_simplifiees,
        source_id=procedure_id,
        user=user,
    )

    if not users_api.steps_to_become_beneficiary(user):
        deposit_source = beneficiary_import.get_detailed_source()
        subscription_api.activate_beneficiary(user, deposit_source)
    else:
        users_external.update_external_user(user)
Ejemplo n.º 16
0
def update_beneficiary_mandatory_information(
    user: User, address: str, city: str, postal_code: str, activity: str, phone_number: Optional[str] = None
) -> None:
    user_initial_roles = user.roles

    update_payload = {
        "address": address,
        "city": city,
        "postalCode": postal_code,
        "departementCode": PostalCode(postal_code).get_departement_code(),
        "activity": activity,
        "hasCompletedIdCheck": True,
    }
    if not FeatureToggle.ENABLE_PHONE_VALIDATION.is_active() and not user.phoneNumber and phone_number:
        update_payload["phoneNumber"] = phone_number

    with transaction():
        User.query.filter(User.id == user.id).update(update_payload)
    db.session.refresh(user)

    if (
        not steps_to_become_beneficiary(user)
        and fraud_api.has_user_passed_fraud_checks(user)
        and not fraud_api.is_user_fraudster(user)
    ):
        subscription_api.check_and_activate_beneficiary(user.id)
    else:
        update_external_user(user)

    new_user_roles = user.roles
    underage_user_has_been_activated = (
        UserRole.UNDERAGE_BENEFICIARY in new_user_roles and UserRole.UNDERAGE_BENEFICIARY not in user_initial_roles
    )

    logger.info(
        "User id check profile updated",
        extra={"user": user.id, "has_been_activated": user.has_beneficiary_role or underage_user_has_been_activated},
    )
Ejemplo n.º 17
0
def _cancel_bookings_from_stock(stock: Stock, reason: BookingCancellationReasons) -> list[Booking]:
    """
    Cancel multiple bookings and update the users' credit information on Batch.
    Note that this will not reindex the stock.offer in Algolia
    """
    deleted_bookings: list[Booking] = []
    with transaction():
        stock = offers_repository.get_and_lock_stock(stock_id=stock.id)
        for booking in stock.bookings:
            try:
                booking.cancel_booking()
            except (BookingIsAlreadyUsed, BookingIsAlreadyCancelled) as e:
                logger.info(str(e), extra={"booking": booking.id, "reason": str(reason)})
            else:
                booking.cancellationReason = reason
                stock.dnBookedQuantity -= booking.quantity
                deleted_bookings.append(booking)
        repository.save(*deleted_bookings)

    for booking in deleted_bookings:
        if booking.individualBooking is not None:
            update_external_user(booking.individualBooking.user)

    return deleted_bookings
Ejemplo n.º 18
0
def post_new_password():
    validate_new_password_request(request)
    token = request.get_json()["token"]
    new_password = request.get_json()["newPassword"]

    check_password_strength("newPassword", new_password)

    user = users_repo.get_user_with_valid_token(token,
                                                [TokenType.RESET_PASSWORD])

    if not user:
        errors = ApiErrors()
        errors.add_error(
            "token", "Votre lien de changement de mot de passe est invalide.")
        raise errors

    user.setPassword(new_password)
    if not user.isEmailValidated:
        user.isEmailValidated = True
        update_external_user(user)

    repository.save(user)

    return "", 204
Ejemplo n.º 19
0
def has_completed_id_check(user: User) -> None:
    user.hasCompletedIdCheck = True
    repository.save(user)
    update_external_user(user)
Ejemplo n.º 20
0
def book_offer(
    beneficiary: User,
    stock_id: int,
    quantity: int,
) -> Booking:
    """
    Return a booking or raise an exception if it's not possible.
    Update a user's credit information on Batch.
    """
    # The call to transaction here ensures we free the FOR UPDATE lock
    # on the stock if validation issues an exception
    with transaction():
        stock = offers_repository.get_and_lock_stock(stock_id=stock_id)
        validation.check_offer_is_not_educational(stock)
        validation.check_can_book_free_offer(beneficiary, stock)
        validation.check_offer_already_booked(beneficiary, stock.offer)
        validation.check_quantity(stock.offer, quantity)
        validation.check_stock_is_bookable(stock, quantity)
        total_amount = quantity * stock.price
        validation.check_expenses_limits(beneficiary, total_amount, stock.offer)

        from pcapi.core.offers.api import is_activation_code_applicable  # To avoid import loops

        if is_activation_code_applicable(stock):
            validation.check_activation_code_available(stock)

        # FIXME (dbaty, 2020-10-20): if we directly set relations (for
        # example with `booking.user = beneficiary`) instead of foreign keys,
        # the session tries to add the object when `get_user_expenses()`
        # is called because autoflush is enabled. As such, the PostgreSQL
        # exceptions (tooManyBookings and insufficientFunds) may raise at
        # this point and will bubble up. If we want them to be caught, we
        # have to set foreign keys, so that the session is NOT autoflushed
        # in `get_user_expenses` and is only committed in `repository.save()`
        # where exceptions are caught. Since we are using flask-sqlalchemy,
        # I don't think that we should use autoflush, nor should we use
        # the `pcapi.repository.repository` module.
        booking = Booking(
            userId=beneficiary.id,
            stockId=stock.id,
            amount=stock.price,
            quantity=quantity,
            token=generate_booking_token(),
            venueId=stock.offer.venueId,
            offererId=stock.offer.venue.managingOffererId,
        )

        booking.dateCreated = datetime.datetime.utcnow()
        booking.cancellationLimitDate = compute_cancellation_limit_date(stock.beginningDatetime, booking.dateCreated)

        if is_activation_code_applicable(stock):
            booking.activationCode = offers_repository.get_available_activation_code(stock)

            if FeatureToggle.AUTO_ACTIVATE_DIGITAL_BOOKINGS.is_active():
                booking.mark_as_used()

        individual_booking = IndividualBooking(
            booking=booking,
            depositId=beneficiary.deposit.id if beneficiary.has_active_deposit else None,
            userId=beneficiary.id,
        )
        stock.dnBookedQuantity += booking.quantity

        repository.save(individual_booking, stock)

    logger.info(
        "Beneficiary booked an offer",
        extra={
            "actor": beneficiary.id,
            "offer": stock.offerId,
            "stock": stock.id,
            "booking": booking.id,
            "used": booking.isUsed,
        },
    )

    try:
        user_emails.send_individual_booking_confirmation_email_to_offerer(individual_booking)
    except MailServiceException as error:
        logger.exception("Could not send booking=%s confirmation email to offerer: %s", booking.id, error)
    try:
        user_emails.send_individual_booking_confirmation_email_to_beneficiary(individual_booking)
    except MailServiceException as error:
        logger.exception("Could not send booking=%s confirmation email to beneficiary: %s", booking.id, error)

    search.async_index_offer_ids([stock.offerId])

    update_external_user(individual_booking.user)

    return individual_booking.booking