def _send_notification_email(email, payment, template_name, reference_prefix): personalisation = { 'site_url': settings.START_PAGE_URL, 'help_url': site_url(reverse('help_area:help')), 'compliance_contact': settings.COMPLIANCE_CONTACT_EMAIL or site_url(reverse('help_area:help')), 'today': timezone.localdate().strftime('%d/%m/%Y'), 'short_payment_ref': payment['uuid'][:8].upper(), 'prisoner_name': payment['recipient_name'], 'prisoner_number': payment['prisoner_number'], 'amount': currency_format(Decimal(payment['amount']) / 100), } personalisation = { field: personalisation[field] for field in SendMoneyNotifyTemplates.templates[template_name] ['personalisation'] } send_email( template_name=template_name, to=email, personalisation=personalisation, reference='%s-%s' % (reference_prefix, payment['uuid']), staff_email=False, )
def add_failed_attempt(self, user, client): self.get_queryset().create( user=user, application=client, ) failed_attempts = self.get_queryset().filter(user=user, application=client) if failed_attempts.count() == settings.MTP_AUTH_LOCKOUT_COUNT: roles = Role.objects.get_roles_for_user(user) roles = list(filter(lambda role: role.application == client, roles)) if roles: service_name = client.name.lower() login_url = roles[0].login_url else: service_name = gettext('Prisoner Money').lower() login_url = None email_context = { 'service_name': service_name, 'lockout_period': settings.MTP_AUTH_LOCKOUT_LOCKOUT_PERIOD // 60, 'login_url': login_url, } send_email( user.email, 'mtp_auth/account_locked.txt', capfirst( gettext( 'Your %(service_name)s account is temporarily locked') % email_context), context=email_context, html_template='mtp_auth/account_locked.html', anymail_tags=['account-locked'], )
def perform_destroy(self, instance): context = { 'service_name': instance.role.application.name.lower(), } send_email( instance.email, 'mtp_auth/account_request_denied.txt', capfirst( gettext('Account access for %(service_name)s was denied') % context), context=context, html_template='mtp_auth/account_request_denied.html', anymail_tags=['account-request-denied'], ) LogEntry.objects.log_action( user_id=self.request.user.pk, content_type_id=get_content_type_for_model(instance).pk, object_id=instance.pk, object_repr=gettext('Declined account request from %(username)s') % { 'username': instance.username, }, action_flag=DELETION_LOG_ENTRY, ) super().perform_destroy(instance)
def add_failed_attempt(self, user, client): self.get_queryset().create( user=user, application=client, ) failed_attempts = self.get_queryset().filter(user=user, application=client) if failed_attempts.count() == settings.MTP_AUTH_LOCKOUT_COUNT: roles = Role.objects.get_roles_for_user(user) roles = list(filter(lambda role: role.application == client, roles)) if roles: service_name = client.name.lower() login_url = roles[0].login_url else: service_name = gettext('Prisoner Money').lower() login_url = None email_context = { 'service_name': service_name, 'lockout_period': settings.MTP_AUTH_LOCKOUT_LOCKOUT_PERIOD // 60, 'login_url': login_url, } send_email( user.email, 'mtp_auth/account_locked.txt', capfirst(gettext('Your %(service_name)s account is temporarily locked') % email_context), context=email_context, html_template='mtp_auth/account_locked.html', anymail_tags=['account-locked'], )
def send_email_with_events(email_context): send_email( email_context['user'].email, 'notification/notifications.txt', _('Your new intelligence tool notifications'), context=email_context, html_template='notification/notifications.html', anymail_tags=['intel-notification', 'intel-notification-daily'], )
def send_first_email_with_events(email_context): send_email( email_context['user'].email, 'notification/notifications-first.txt', _('New notification feature added to intelligence tool'), context=email_context, html_template='notification/notifications-first.html', anymail_tags=['intel-notification', 'intel-notification-first'], )
def send_first_email_not_monitoring(email_context): send_email( email_context['user'].email, 'notification/not-monitoring.txt', _('New helpful ways to get the best from the intelligence tool'), context=email_context, html_template='notification/not-monitoring.html', anymail_tags=['intel-notification', 'intel-notification-not-monitoring'], )
def credit_individual_credit_to_nomis(user, user_session, credit_id, credit): api_session = get_api_session_with_session(user, user_session) if not hasattr(thread_local, 'nomis_session'): thread_local.nomis_session = requests.Session() nomis_response = None try: nomis_response = nomis.create_transaction( prison_id=credit['prison'], prisoner_number=credit['prisoner_number'], amount=credit['amount'], record_id=str(credit_id), description='Sent by {sender}'.format(sender=credit['sender_name']), transaction_type='MTDS', retries=1, session=thread_local.nomis_session ) except HTTPError as e: if e.response.status_code == 409: logger.warning('Credit %s was already present in NOMIS' % credit_id) elif e.response.status_code >= 500: logger.error('Credit %s could not credited as NOMIS is unavailable' % credit_id) return else: logger.warning('Credit %s cannot be automatically credited to NOMIS' % credit_id) api_session.post( 'credits/actions/setmanual/', json={'credit_ids': [int(credit_id)]} ) return except RequestException: logger.exception('Credit %s could not credited as NOMIS is unavailable' % credit_id) return credit_update = {'id': credit_id, 'credited': True} if nomis_response and 'id' in nomis_response: credit_update['nomis_transaction_id'] = nomis_response['id'] api_session.post('credits/actions/credit/', json=[credit_update]) if credit.get('sender_email'): send_email( credit['sender_email'], 'cashbook/email/credited-confirmation.txt', _('Send money to someone in prison: the prisoner’s account has been credited'), context={ 'amount': credit['amount'], 'ref_number': credit.get('short_ref_number'), 'received_at': credit['received_at'], 'prisoner_name': credit.get('intended_recipient'), 'help_url': settings.CITIZEN_HELP_PAGE_URL, 'feedback_url': settings.CITIZEN_CONTACT_PAGE_URL, 'site_url': settings.START_PAGE_URL, }, html_template='cashbook/email/credited-confirmation.html', anymail_tags=['credited'], )
def handle(self, *args, **options): expiring = Token.objects.filter(expires__date=(now() + datetime.timedelta(days=7)).date()) token_names = ', '.join('"%s"' % token for token in expiring) if token_names: send_email( settings.TEAM_EMAIL, 'core/expiring-tokens-email.txt', 'Tokens are expiring in 7 days', context={'token_names': token_names}, anymail_tags=['expiring-tokens'], ) raise CommandError('These tokens are expiring in a week ' + token_names)
def send_task_failure_notification(email, context): activate(settings.LANGUAGE_CODE) context['feedback_url'] = urljoin(settings.SITE_URL, reverse('submit_ticket')) send_email( email, 'prisoner_location_admin/email/failure-notification.txt', gettext('Prisoner money: prisoner location update failed'), context=context, html_template='prisoner_location_admin/email/failure-notification.html', anymail_tags=['locations-failed'], )
def test_send_plain_email(self, backend): send_email('*****@*****.**', 'dummy-email.txt', 'email subject 1') self.assertFalse(backend().write_message.called) send_email('*****@*****.**', 'dummy-email.txt', 'email subject 2') self.assertFalse(backend().write_message.called) self.assertEqual(len(mail.outbox), 2) for index, email in enumerate(mail.outbox): self.assertEqual(email.body.strip(), 'EMAIL-') self.assertEqual(email.subject, 'email subject %d' % (index + 1)) self.assertSequenceEqual(email.recipients(), ['*****@*****.**' % (index + 1)])
def test_synchronous_send_email_does_not_retry(self, mocked_email, logger): from mtp_common.tasks import AnymailRequestsAPIError, send_email state = {'calls': 0} def mock_send_email(): state['calls'] += 1 raise AnymailRequestsAPIError mocked_email().send = mock_send_email with self.assertRaises(AnymailRequestsAPIError): send_email('*****@*****.**', 'dummy-email.txt', 'email subject', retry_attempts=10) self.assertEqual(state['calls'], 1) self.assertTrue(logger.exception.called, True)
def test_asynchronous_send_email_retries(self, uwsgi, mocked_email, logger): from mtp_common.tasks import AnymailRequestsAPIError, send_email state = {'calls': 0} def mock_send_email(): state['calls'] += 1 raise AnymailRequestsAPIError mocked_email().send = mock_send_email uwsgi.spool = spooler.__call__ send_email('*****@*****.**', 'dummy-email.txt', 'email subject', retry_attempts=3) self.assertEqual(state['calls'], 4) self.assertTrue(logger.exception.called, True)
def _send_notification_email(email, template_name, subject, tags, context): context.update({ 'site_url': settings.START_PAGE_URL, 'help_url': site_url(reverse('help_area:help')), }) send_email( email, f'send_money/email/{template_name}.txt', gettext('Send money to someone in prison: %(subject)s') % {'subject': subject}, context=context, html_template=f'send_money/email/{template_name}.html', anymail_tags=tags, )
def email_admins(self, admins, role, names): service_name = role.application.name.lower() send_email( [admin.email for admin in admins], 'mtp_auth/new_account_requests.txt', capfirst( gettext('You have new %(service_name)s users to approve') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'names': names, 'login_url': role.login_url, }, html_template='mtp_auth/new_account_requests.html', anymail_tags=['new-account-requests'], )
def email_admins(self, admins, role, prison, names): service_name = role.application.name.lower() prison_name = prison.short_name send_email( [admin.email for admin in admins], 'mtp_auth/new_account_requests.txt', capfirst(gettext('You have new %(service_name)s users to approve') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'prison_name': prison_name, 'names': names, 'login_url': role.login_url, }, html_template='mtp_auth/new_account_requests.html', anymail_tags=['new-account-requests'], )
def handle(self, **options): frequency = options['frequency'] preferences = EmailNotificationPreferences.objects.filter(frequency=frequency) period_start, period_end = get_notification_period(frequency) memo_table = {} if frequency == EMAIL_FREQUENCY.DAILY: period = _('day') elif frequency == EMAIL_FREQUENCY.WEEKLY: period = _('week') else: period = _('month') notifications_period_url = urljoin( settings.NOMS_OPS_NOTIFICATIONS_URL, '%s/' % period_start.date().isoformat() ) for preference in preferences: user = preference.user total_notifications = get_notification_count( user, period_start, period_end, memo_table ) if total_notifications > 0: email_context = { 'user': user, 'period': period, 'period_start': period_start, 'total_notifications': total_notifications, 'notifications_url': notifications_period_url, 'settings_url': settings.NOMS_OPS_SETTINGS_URL, 'feedback_url': settings.NOMS_OPS_FEEDBACK_URL, 'staff_email': True } send_email( user.email, 'notification/periodic_email.txt', _('You have %s prisoner money intelligence tool notifications') % total_notifications, context=email_context, html_template='notification/periodic_email.html', anymail_tags=['intelligence-notifications'], )
def create(self, validated_data): creating_user = self.context['request'].user role = validated_data.pop('role', None) make_user_admin = validated_data.pop('user_admin', False) validated_data.pop('is_locked_out', None) if not role: raise serializers.ValidationError( {'role': 'Role must be specified'}) new_user = super().create(validated_data) password = generate_new_password() new_user.set_password(password) new_user.save() if make_user_admin: new_user.groups.add(Group.objects.get(name='UserAdmin')) PrisonUserMapping.objects.assign_prisons_from_user( creating_user, new_user) role.assign_to_user(new_user) context = { 'username': new_user.username, 'password': password, 'service_name': role.application.name.lower(), 'login_url': role.login_url, } send_email( new_user.email, 'mtp_auth/new_user.txt', capfirst( gettext('Your new %(service_name)s account is ready to use') % context), context=context, html_template='mtp_auth/new_user.html', anymail_tags=['new-user'], ) return new_user
def perform_destroy(self, instance): context = { 'service_name': instance.role.application.name.lower(), } send_email( instance.email, 'mtp_auth/account_request_denied.txt', capfirst(gettext('Account access for %(service_name)s was denied') % context), context=context, html_template='mtp_auth/account_request_denied.html', anymail_tags=['account-request-denied'], ) LogEntry.objects.log_action( user_id=self.request.user.pk, content_type_id=get_content_type_for_model(instance).pk, object_id=instance.pk, object_repr=gettext('Declined account request from %(username)s') % { 'username': instance.username, }, action_flag=DELETION_LOG_ENTRY, ) super().perform_destroy(instance)
def clean(self): if self.is_valid(): email = self.cleaned_data.get('email') send_email( self.request.user.email, 'mtp_common/auth/email-change-email.txt', _('Your prisoner money email address has been changed'), context={ 'user': self.request.user, 'new_email': email, 'site_url': settings.SITE_URL, 'feedback_url': urljoin(settings.SITE_URL, reverse('submit_ticket')), 'staff_email': True, }, html_template='mtp_common/auth/email-change-email.html', anymail_tags=['change-email'], ) try: session = api_client.get_api_session(self.request) session.patch('/users/%s/' % self.request.user.username, json={'email': email}) refresh_user_data(self.request, session) except HttpClientError as e: try: response_body = json.loads(e.content.decode('utf-8')) for field in response_body: for error in response_body[field]: self.add_error(field, error) except Exception: logger.exception('Could not email change error') raise forms.ValidationError(self.error_messages['generic']) return self.cleaned_data
def create(self, validated_data): creating_user = self.context['request'].user role = validated_data.pop('role', None) make_user_admin = validated_data.pop('user_admin', False) validated_data.pop('is_locked_out', None) if not role: raise serializers.ValidationError({'role': 'Role must be specified'}) new_user = super().create(validated_data) password = generate_new_password() new_user.set_password(password) new_user.save() if make_user_admin: new_user.groups.add(Group.objects.get(name='UserAdmin')) PrisonUserMapping.objects.assign_prisons_from_user(creating_user, new_user) role.assign_to_user(new_user) context = { 'username': new_user.username, 'password': password, 'service_name': role.application.name.lower(), 'login_url': role.login_url, } send_email( new_user.email, 'mtp_auth/new_user.txt', capfirst(gettext('Your new %(service_name)s account is ready to use') % context), context=context, html_template='mtp_auth/new_user.html', anymail_tags=['new-user'], ) return new_user
def test_special_addresses_ignored_on_production(self, backend): for address in ('*****@*****.**', '*****@*****.**', '*****@*****.**'): send_email(address, 'dummy-email.txt', 'email subject') self.assertFalse(backend().write_message.called)
def partial_update(self, request, *args, **kwargs): instance = self.get_object() user_admin = request.data.get('user_admin', '').lower() == 'true' try: user = User.objects.get_by_natural_key(instance.username) if request.user.pk == user.pk: raise RestValidationError({ api_settings.NON_FIELD_ERRORS_KEY: _('You cannot confirm changes to yourself') }) if user.is_superuser: raise RestValidationError({ api_settings.NON_FIELD_ERRORS_KEY: _('Super users cannot be edited') }) serializer_kwargs = {'instance': user} user.is_active = True user.save() user_existed = True except User.DoesNotExist: serializer_kwargs = {} user_existed = False if instance.prison: prisons = [instance.prison] else: prisons = None user_serializer = UserSerializer(data=dict( first_name=instance.first_name, last_name=instance.last_name, email=instance.email, username=instance.username, role=instance.role.name, prisons=prisons, user_admin=user_admin, ), context={ 'request': request, 'from_account_request': True }, **serializer_kwargs) user_serializer.is_valid() user = user_serializer.save() context = { 'username': user.username, 'service_name': instance.role.application.name.lower(), 'login_url': instance.role.login_url, } if user_existed: send_email( user.email, 'mtp_auth/user_moved.txt', capfirst( gettext( 'Your new %(service_name)s account is ready to use') % context), context=context, html_template='mtp_auth/user_moved.html', anymail_tags=['user-moved'], ) LogEntry.objects.log_action( user_id=request.user.pk, content_type_id=get_content_type_for_model(user).pk, object_id=user.pk, object_repr=gettext('Accepted account request for %(username)s') % { 'username': user.username, }, action_flag=CHANGE_LOG_ENTRY, ) instance.delete() return Response({})
def post(self, request): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): user_identifier = serializer.validated_data['username'] try: user = User.objects.get_by_natural_key(user_identifier) except User.DoesNotExist: users = User.objects.filter(email__iexact=user_identifier) user_count = users.count() if user_count == 0: return self.failure_response('not_found', field='username') elif user_count > 1: return self.failure_response('multiple_found', field='username') user = users[0] if user.username in self.immutable_users: return self.failure_response('not_found', field='username') if user.is_locked_out: return self.failure_response('locked_out', field='username') if not user.email: return self.failure_response('no_email', field='username') service_name = gettext('Prisoner Money').lower() if serializer.validated_data.get('create_password'): change_request, _ = PasswordChangeRequest.objects.get_or_create( user=user) change_password_url = urlsplit( serializer.validated_data['create_password'] ['password_change_url']) query = parse_qs(change_password_url.query) query.update({ serializer.validated_data['create_password']['reset_code_param']: str(change_request.code) }) change_password_url = list(change_password_url) change_password_url[3] = urlencode(query) change_password_url = urlunsplit(change_password_url) send_email( user.email, 'mtp_auth/create_new_password.txt', capfirst( gettext('Create a new %(service_name)s password') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'change_password_url': change_password_url, }, html_template='mtp_auth/create_new_password.html', anymail_tags=['new-password'], ) return Response(status=status.HTTP_204_NO_CONTENT) else: password = generate_new_password() if not password: logger.error( 'Password could not be generated; have validators changed?' ) return self.failure_response('generic') user.set_password(password) user.save() send_email( user.email, 'mtp_auth/reset_password.txt', capfirst( gettext('Your new %(service_name)s password') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'username': user.username, 'password': password, }, html_template='mtp_auth/reset_password.html', anymail_tags=['reset-password'], ) return Response(status=status.HTTP_204_NO_CONTENT) else: return self.failure_response(serializer.errors)
def credit_individual_credit_to_nomis(user, user_session, credit_id, credit): api_session = get_api_session_with_session(user, user_session) if not hasattr(thread_local, 'nomis_session'): thread_local.nomis_session = requests.Session() nomis_response = None try: nomis_response = nomis.create_transaction( prison_id=credit['prison'], prisoner_number=credit['prisoner_number'], amount=credit['amount'], record_id=str(credit_id), description='Sent by {sender}'.format( sender=credit['sender_name']), transaction_type='MTDS', retries=1, session=thread_local.nomis_session) except HTTPError as e: if e.response.status_code == 409: logger.warning('Credit %s was already present in NOMIS' % credit_id) elif e.response.status_code >= 500: logger.error( 'Credit %s could not credited as NOMIS is unavailable' % credit_id) return else: logger.warning( 'Credit %s cannot be automatically credited to NOMIS' % credit_id) api_session.post('credits/actions/setmanual/', json={'credit_ids': [int(credit_id)]}) return except RequestException: logger.exception( 'Credit %s could not credited as NOMIS is unavailable' % credit_id) return credit_update = {'id': credit_id, 'credited': True} if nomis_response and 'id' in nomis_response: credit_update['nomis_transaction_id'] = nomis_response['id'] api_session.post('credits/actions/credit/', json=[credit_update]) if credit.get('sender_email'): send_email( credit['sender_email'], 'cashbook/email/credited-confirmation.txt', _('Send money to someone in prison: the prisoner’s account has been credited' ), context={ 'amount': credit['amount'], 'ref_number': credit.get('short_payment_ref'), 'received_at': credit['received_at'], 'prisoner_name': credit.get('intended_recipient'), 'help_url': urljoin(settings.SEND_MONEY_URL, '/help/'), 'feedback_url': urljoin(settings.SEND_MONEY_URL, '/contact-us/'), 'site_url': settings.START_PAGE_URL, }, html_template='cashbook/email/credited-confirmation.html', anymail_tags=['credited'], )
def post(self, request): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): user_identifier = serializer.validated_data['username'] try: user = User.objects.get_by_natural_key(user_identifier) except User.DoesNotExist: users = User.objects.filter(email__iexact=user_identifier) user_count = users.count() if user_count == 0: return self.failure_response('not_found', field='username') elif user_count > 1: return self.failure_response('multiple_found', field='username') user = users[0] if user.username in self.immutable_users: return self.failure_response('not_found', field='username') if user.is_locked_out: return self.failure_response('locked_out', field='username') if not user.email: return self.failure_response('no_email', field='username') service_name = gettext('Prisoner Money').lower() if serializer.validated_data.get('create_password'): change_request, _ = PasswordChangeRequest.objects.get_or_create(user=user) change_password_url = urlsplit( serializer.validated_data['create_password']['password_change_url'] ) query = parse_qs(change_password_url.query) query.update({ serializer.validated_data['create_password']['reset_code_param']: str(change_request.code) }) change_password_url = list(change_password_url) change_password_url[3] = urlencode(query) change_password_url = urlunsplit(change_password_url) send_email( user.email, 'mtp_auth/create_new_password.txt', capfirst(gettext('Create a new %(service_name)s password') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'change_password_url': change_password_url, }, html_template='mtp_auth/create_new_password.html', anymail_tags=['new-password'], ) return Response(status=status.HTTP_204_NO_CONTENT) else: password = generate_new_password() if not password: logger.error('Password could not be generated; have validators changed?') return self.failure_response('generic') user.set_password(password) user.save() send_email( user.email, 'mtp_auth/reset_password.txt', capfirst(gettext('Your new %(service_name)s password') % { 'service_name': service_name, }), context={ 'service_name': service_name, 'username': user.username, 'password': password, }, html_template='mtp_auth/reset_password.html', anymail_tags=['reset-password'], ) return Response(status=status.HTTP_204_NO_CONTENT) else: return self.failure_response(serializer.errors)
def partial_update(self, request, *args, **kwargs): instance = self.get_object() user_admin = request.data.get('user_admin', '').lower() == 'true' try: user = User.objects.get_by_natural_key(instance.username) if request.user.pk == user.pk: raise RestValidationError({'username': _('You cannot confirm changes to yourself')}) if user.is_superuser: raise RestValidationError({'username': _('Super users cannot be edited')}) # inactive users get re-activated user.is_active = True user.save() # existing non-superadmins have their prisons, applications and groups replaced user.groups.clear() PrisonUserMapping.objects.filter(user=user).delete() ApplicationUserMapping.objects.filter(user=user).delete() user_existed = True password = None except User.DoesNotExist: user = User.objects.create( first_name=instance.first_name, last_name=instance.last_name, email=instance.email, username=instance.username, ) password = generate_new_password() user.set_password(password) user.save() user_existed = False role = instance.role role.assign_to_user(user) if user_admin: user.groups.add(Group.objects.get(name='UserAdmin')) PrisonUserMapping.objects.assign_prisons_from_user(request.user, user) context = { 'username': user.username, 'password': password, 'service_name': role.application.name.lower(), 'login_url': role.login_url, } if user_existed: context.pop('password') send_email( user.email, 'mtp_auth/user_moved.txt', capfirst(gettext('Your new %(service_name)s account is ready to use') % context), context=context, html_template='mtp_auth/user_moved.html', anymail_tags=['user-moved'], ) else: send_email( user.email, 'mtp_auth/new_user.txt', capfirst(gettext('Your new %(service_name)s account is ready to use') % context), context=context, html_template='mtp_auth/new_user.html', anymail_tags=['new-user'], ) LogEntry.objects.log_action( user_id=request.user.pk, content_type_id=get_content_type_for_model(user).pk, object_id=user.pk, object_repr=gettext('Accepted account request for %(username)s') % { 'username': user.username, }, action_flag=CHANGE_LOG_ENTRY, ) instance.delete() return Response({})