def send_reminder_email_task(custom_template_text, email_recipient_list,
                             subscription_uuid):
    """
    Sends license activation reminder email(s) asynchronously.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_slug = enterprise_api_client.get_enterprise_slug(
        subscription_plan.enterprise_customer_uuid)
    enterprise_name = enterprise_api_client.get_enterprise_name(
        subscription_plan.enterprise_customer_uuid)

    try:
        send_activation_emails(custom_template_text,
                               pending_licenses,
                               enterprise_slug,
                               enterprise_name,
                               is_reminder=True)
    except Exception:  # pylint: disable=broad-except
        msg = 'License manager reminder email sending received an exception for enterprise: {}.'.format(
            enterprise_name)
        logger.error(msg, exc_info=True)
        # Return without updating the last_remind_date for licenses
        return

    License.set_date_fields_to_now(pending_licenses, ['last_remind_date'])
Beispiel #2
0
def activation_task(custom_template_text, email_recipient_list,
                    subscription_uuid):
    """
    Sends license activation email(s) asynchronously, and creates pending enterprise users to link the email recipients
    to the subscription's enterprise.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_slug = enterprise_api_client.get_enterprise_slug(
        subscription_plan.enterprise_customer_uuid)
    send_activation_emails(custom_template_text, pending_licenses,
                           enterprise_slug)
    License.set_date_fields_to_now(pending_licenses,
                                   ['last_remind_date', 'assigned_date'])

    for email_recipient in email_recipient_list:
        enterprise_api_client.create_pending_enterprise_user(
            subscription_plan.enterprise_customer_uuid,
            email_recipient,
        )
Beispiel #3
0
def send_reminder_email_task(custom_template_text, email_recipient_list,
                             subscription_uuid):
    """
    Sends license activation reminder email(s) asynchronously.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_customer = enterprise_api_client.get_enterprise_customer_data(
        subscription_plan.enterprise_customer_uuid)
    enterprise_slug = enterprise_customer.get('slug')
    enterprise_name = enterprise_customer.get('name')
    enterprise_sender_alias = get_enterprise_sender_alias(enterprise_customer)
    enterprise_contact_email = enterprise_customer.get('contact_email')

    # We need to send these emails individually, because each email's text must be
    # generated for every single user/activation_key
    for pending_license in pending_licenses:
        user_email = pending_license.user_email
        license_activation_key = str(pending_license.activation_key)
        braze_campaign_id = settings.BRAZE_REMIND_EMAIL_CAMPAIGN
        braze_trigger_properties = {
            'TEMPLATE_GREETING': custom_template_text['greeting'],
            'TEMPLATE_CLOSING': custom_template_text['closing'],
            'license_activation_key': license_activation_key,
            'enterprise_customer_slug': enterprise_slug,
            'enterprise_customer_name': enterprise_name,
            'enterprise_sender_alias': enterprise_sender_alias,
            'enterprise_contact_email': enterprise_contact_email,
        }
        recipient = _aliased_recipient_object_from_email(user_email)

        try:
            braze_client_instance = BrazeApiClient()
            braze_client_instance.create_braze_alias(
                [user_email],
                ENTERPRISE_BRAZE_ALIAS_LABEL,
            )
            braze_client_instance.send_campaign_message(
                braze_campaign_id,
                recipients=[recipient],
                trigger_properties=braze_trigger_properties,
            )

        except BrazeClientError as exc:
            message = ('Error hitting Braze API. '
                       f'reminder email to {user_email} for license failed.')
            logger.exception(message)
            raise exc

    License.set_date_fields_to_now(pending_licenses, ['last_remind_date'])
Beispiel #4
0
def update_user_email_for_licenses_task(lms_user_id, new_email):
    """
    Updates the user_email field on all licenses associated with the given lms_user_id.

    Arguments:
        lms_user_id (str): The lms_user_id associated with licenses that should be updated
        new_email (str): The email that will overwrite curent user_email fields

    """

    user_licenses = License.objects.filter(lms_user_id=lms_user_id, )
    for lcs in user_licenses:
        lcs.user_email = new_email

    License.bulk_update(user_licenses, ['user_email'])
    def test_bulk_create(self):
        """
        Test that bulk_create creates and saves objects, and creates an associated
        historical record for the creation.
        """
        licenses = [License(subscription_plan=self.subscription_plan) for _ in range(3)]

        License.bulk_create(licenses)

        for user_license in licenses:
            user_license.refresh_from_db()
            assert UNASSIGNED == user_license.status
            license_history = user_license.history.all()
            assert 1 == len(license_history)
            assert self.CREATE_HISTORY_TYPE == user_license.history.earliest().history_type
Beispiel #6
0
    def test_bulk_create(self, mock_track_license_changes):
        """
        Test that bulk_create creates and saves objects, and creates an associated
        historical record for the creation, and calls the create track_event.
        """
        licenses = [License(subscription_plan=self.subscription_plan) for _ in range(3)]

        License.bulk_create(licenses)

        for user_license in licenses:
            user_license.refresh_from_db()
            assert UNASSIGNED == user_license.status
            license_history = user_license.history.all()
            assert 1 == len(license_history)
            assert self.CREATE_HISTORY_TYPE == user_license.history.earliest().history_type

        mock_track_license_changes.assert_called_with(
            licenses,
            SegmentEvents.LICENSE_CREATED
        )
    def test_bulk_update(self):
        """
        Test that bulk_update saves objects, and creates an associated
        historical record for the update action
        """
        licenses = [License(subscription_plan=self.subscription_plan) for _ in range(3)]

        License.bulk_create(licenses)

        for user_license in licenses:
            user_license.status = REVOKED

        License.bulk_update(licenses, ['status'])

        for user_license in licenses:
            user_license.refresh_from_db()
            assert REVOKED == user_license.status
            license_history = user_license.history.all()
            assert 2 == len(license_history)
            assert self.CREATE_HISTORY_TYPE == user_license.history.earliest().history_type
            assert self.UPDATE_HISTORY_TYPE == user_license.history.first().history_type
Beispiel #8
0
    def test_for_user_and_customer_active_and_current_only(self):
        expected_licenses = [
            self.active_current_license,
        ]

        actual_licenses = License.for_user_and_customer(
            user_email=self.user_email,
            lms_user_id=None,
            enterprise_customer_uuid=self.enterprise_customer_uuid,
            active_plans_only=True,
            current_plans_only=True,
        )

        self.assertCountEqual(actual_licenses, expected_licenses)
Beispiel #9
0
    def test_for_user_and_customer_no_kwargs(self):
        expected_licenses = [
            self.active_current_license,
            self.inactive_current_license,
            self.non_current_active_license,
            self.non_current_inactive_license,
        ]

        actual_licenses = License.for_user_and_customer(
            user_email=self.user_email,
            lms_user_id=None,
            enterprise_customer_uuid=self.enterprise_customer_uuid,
        )

        self.assertCountEqual(actual_licenses, expected_licenses)
    def post(self, request):
        """
        For a given email address provided in POST data,
        returns all licenses and associated subscription data
        associated with that email address.
        """
        user_email = request.data.get('user_email')
        if not user_email:
            return Response(
                'A ``user_email`` is required in the request data',
                status=status.HTTP_400_BAD_REQUEST,
            )

        user_licenses = License.by_user_email(user_email)
        if not user_licenses:
            return Response(status=status.HTTP_404_NOT_FOUND, )

        serialized_licenses = serializers.StaffLicenseSerializer(user_licenses,
                                                                 many=True)

        return Response(
            serialized_licenses.data,
            status=status.HTTP_200_OK,
        )
    def assign(self, request, subscription_uuid=None):
        """
        Given a list of emails, assigns a license to those user emails and sends an activation email.

        This endpoint allows assigning licenses to users who have previously had a license revoked, by removing their
        association to the revoked licenses and then assigning them to unassigned licenses.
        """
        # Validate the user_emails and text sent in the data
        self._validate_data(request.data)
        # Dedupe all lowercase emails before turning back into a list for indexing
        user_emails = list(
            {email.lower()
             for email in request.data.get('user_emails', [])})

        subscription_plan = self._get_subscription_plan()

        # Find any emails that have already been associated with a non-revoked license in the subscription
        # and remove from user_emails list
        already_associated_licenses = subscription_plan.licenses.filter(
            user_email__in=user_emails,
            status__in=[constants.ASSIGNED, constants.ACTIVATED],
        )
        if already_associated_licenses:
            already_associated_emails = list(
                already_associated_licenses.values_list('user_email',
                                                        flat=True))
            for email in already_associated_emails:
                user_emails.remove(email.lower())

        # Get the revoked licenses that are attempting to be assigned to
        revoked_licenses_for_assignment = subscription_plan.licenses.filter(
            status=constants.REVOKED,
            user_email__in=user_emails,
        )

        # Make sure there are enough licenses that we can assign to
        num_user_emails = len(user_emails)
        num_unassigned_licenses = subscription_plan.unassigned_licenses.count()
        # Since we flip the status of revoked licenses when admins attempt to re-assign that learner to a new
        # license, we check that there are enough unassigned licenses when combined with the revoked licenses that
        # will have their status change
        num_potential_unassigned_licenses = num_unassigned_licenses + revoked_licenses_for_assignment.count(
        )
        if num_user_emails > num_potential_unassigned_licenses:
            msg = (
                'There are not enough licenses that can be assigned to complete your request.'
                'You attempted to assign {} licenses, but there are only {} potentially available.'
            ).format(num_user_emails, num_potential_unassigned_licenses)
            return Response(msg, status=status.HTTP_400_BAD_REQUEST)

        # Flip all revoked licenses that were associated with emails that we are assigning to unassigned, and clear
        # all the old data on the license.
        for revoked_license in revoked_licenses_for_assignment:
            revoked_license.reset_to_unassigned()

        License.bulk_update(
            revoked_licenses_for_assignment,
            [
                'status',
                'user_email',
                'lms_user_id',
                'last_remind_date',
                'activation_date',
                'activation_key',
                'assigned_date',
                'revoked_date',
            ],
        )

        # Get a queryset of only the number of licenses we need to assign
        unassigned_licenses = subscription_plan.unassigned_licenses[:
                                                                    num_user_emails]
        for unassigned_license, email in zip(unassigned_licenses, user_emails):
            # Assign each email to a license and mark the license as assigned
            unassigned_license.user_email = email
            unassigned_license.status = constants.ASSIGNED
            activation_key = str(uuid4())
            unassigned_license.activation_key = activation_key
            unassigned_license.assigned_date = localized_utcnow()
            unassigned_license.last_remind_date = localized_utcnow()

        License.bulk_update(
            unassigned_licenses,
            [
                'user_email', 'status', 'activation_key', 'assigned_date',
                'last_remind_date'
            ],
        )

        # Create async chains of the pending learners and activation emails tasks with each batch of users
        # The task signatures are immutable, hence the `si()` - we don't want the result of the
        # link_learners_to_enterprise_task passed to the "child" activation_email_task.
        for pending_learner_batch in chunks(
                user_emails, constants.PENDING_ACCOUNT_CREATION_BATCH_SIZE):
            chain(
                link_learners_to_enterprise_task.si(
                    pending_learner_batch,
                    subscription_plan.enterprise_customer_uuid,
                ),
                activation_email_task.si(
                    self._get_custom_text(request.data),
                    pending_learner_batch,
                    subscription_uuid,
                )).apply_async()

        # Pass email assignment data back to frontend for display
        response_data = {
            'num_successful_assignments': len(user_emails),
            'num_already_associated': len(already_associated_licenses)
        }
        return Response(data=response_data, status=status.HTTP_200_OK)
    def handle(self, *args, **options):
        ready_for_retirement_date = localized_utcnow() - timedelta(
            DAYS_TO_RETIRE)

        expired_licenses_for_retirement = License.get_licenses_exceeding_purge_duration(
            'subscription_plan__expiration_date', )
        # Scrub all piii on licenses whose subscription expired over 90 days ago, and mark the licenses as revoked
        for expired_license in expired_licenses_for_retirement:
            original_lms_user_id = expired_license.lms_user_id
            # record event data BEFORE we clear the license data:
            event_properties = get_license_tracking_properties(expired_license)

            expired_license.clear_pii()
            expired_license.status = REVOKED
            expired_license.revoked_date = localized_utcnow()
            expired_license.save()

            event_properties = get_license_tracking_properties(expired_license)
            track_event(original_lms_user_id, SegmentEvents.LICENSE_REVOKED,
                        event_properties)

            # Clear historical pii after removing pii from the license itself
            expired_license.clear_historical_pii()
        expired_license_uuids = sorted([
            expired_license.uuid
            for expired_license in expired_licenses_for_retirement
        ])
        message = 'Retired {} expired licenses with uuids: {}'.format(
            len(expired_license_uuids), expired_license_uuids)
        logger.info(message)

        revoked_licenses_for_retirement = License.get_licenses_exceeding_purge_duration(
            'revoked_date',
            status=REVOKED,
        )
        # Scrub all pii on the revoked licenses, but they should stay revoked and keep their other info as we currently
        # add an unassigned license to the subscription's license pool whenever one is revoked.
        for revoked_license in revoked_licenses_for_retirement:
            revoked_license.clear_pii()
            revoked_license.save()
            # Clear historical pii after removing pii from the license itself
            revoked_license.clear_historical_pii()
        revoked_license_uuids = sorted([
            revoked_license.uuid
            for revoked_license in revoked_licenses_for_retirement
        ])
        message = 'Retired {} revoked licenses with uuids: {}'.format(
            len(revoked_license_uuids), revoked_license_uuids)
        logger.info(message)

        # Any license that was assigned but not activated before the associated agreement's
        # ``unactivated_license_duration`` elapsed should have its data scrubbed.
        assigned_licenses_for_retirement = License.get_licenses_exceeding_purge_duration(
            'assigned_date',
            status=ASSIGNED,
        )
        # We place previously assigned licenses that are now retired back into the unassigned license pool, so we scrub
        # all data on them.
        for assigned_license in assigned_licenses_for_retirement:
            assigned_license.reset_to_unassigned()
            assigned_license.save()
            # Clear historical pii after removing pii from the license itself
            assigned_license.clear_historical_pii()
        assigned_license_uuids = sorted([
            assigned_license.uuid
            for assigned_license in assigned_licenses_for_retirement
        ], )
        message = 'Retired {} assigned licenses that exceeded their inactivation duration with uuids: {}'.format(
            len(assigned_license_uuids),
            assigned_license_uuids,
        )
        logger.info(message)