def email_success(self): from sentry.utils.email import MessageBuilder # The following condition should never be true, but it's a safeguard in case someone manually calls this method if self.date_finished is None or self.date_expired is None or self.file is None: logger.warning( "Notification email attempted on incomplete dataset", extra={ "data_export_id": self.id, "organization_id": self.organization_id }, ) return url = absolute_uri( reverse("sentry-data-export-details", args=[self.organization.slug, self.id])) msg = MessageBuilder( subject="Your Download is Ready!", context={ "url": url, "expiration": self.format_date(self.date_expired) }, type="organization.export-data", template="sentry/emails/data-export-success.txt", html_template="sentry/emails/data-export-success.html", ) msg.send_async([self.user.email]) metrics.incr("dataexport.end", instance="success")
def send_confirm_email_singular(self, email, is_new_user=False): from sentry import options from sentry.utils.email import MessageBuilder if not email.hash_is_valid(): email.set_hash() email.save() context = { 'user': self, 'url': absolute_uri( reverse('sentry-account-confirm-email', args=[self.id, email.validation_hash])), 'confirm_email': email.email, 'is_new_user': is_new_user, } msg = MessageBuilder( subject='%sConfirm Email' % (options.get('mail.subject-prefix'), ), template='sentry/emails/confirm_email.txt', html_template='sentry/emails/confirm_email.html', type='user.confirm_email', context=context, ) msg.send_async([email.email])
def build_message(timestamp, duration, organization, user, reports): start, stop = interval = _to_interval(timestamp, duration) duration_spec = durations[duration] message = MessageBuilder( subject="{} Report for {}: {} - {}".format( duration_spec.adjective.title(), organization.name, date_format(start), date_format(stop), ), template="sentry/emails/reports/body.txt", html_template="sentry/emails/reports/body.html", type="report.organization", context={ "duration": duration_spec, "interval": {"start": date_format(start), "stop": date_format(stop)}, "organization": organization, "personal": fetch_personal_statistics(interval, organization, user), "report": to_context(organization, interval, reports), "user": user, }, headers={"X-SMTPAPI": json.dumps({"category": "organization_report_email"})}, ) message.add_users((user.id,)) return message
def send_invite_email(self): from sentry.utils.email import MessageBuilder context = { 'email': self.email, 'team': self.team, 'url': absolute_uri( reverse('sentry-accept-invite', kwargs={ 'member_id': self.id, 'token': self.token, })), } msg = MessageBuilder( subject='Invite to join team: %s' % (self.team.name, ), template='sentry/emails/member_invite.txt', context=context, ) try: msg.send([self.email]) except Exception, e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def test_add_groupemailthread(self, make_msgid): make_msgid.return_value = "abc123" msg = MessageBuilder(subject="Test", body="hello world", html_body="<b>hello world</b>", reference=self.group) msg.send(["*****@*****.**"]) assert len(mail.outbox) == 1 out = mail.outbox[0] assert out.to == ["*****@*****.**"] assert out.subject == "Test", "First message should not have Re: prefix" assert out.extra_headers["Message-Id"] == "abc123" assert "In-Reply-To" not in out.extra_headers assert "References" not in out.extra_headers assert out.body == "hello world" assert len(out.alternatives) == 1 assert out.alternatives[0] == ( "<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>", "text/html", ) # Our new EmailThread row was added assert GroupEmailThread.objects.count() == 1 thread = GroupEmailThread.objects.all()[0] assert thread.msgid == "abc123" assert thread.email == "*****@*****.**" assert thread.group == self.group
def send_invite_email(self): from sentry.utils.email import MessageBuilder context = { 'email': self.email, 'organization': self.organization, 'url': absolute_uri(reverse('sentry-accept-invite', kwargs={ 'member_id': self.id, 'token': self.token, })), } msg = MessageBuilder( subject='Join %s in using Sentry' % self.organization.name, template='sentry/emails/member-invite.txt', html_template='sentry/emails/member-invite.html', type='organization.invite', context=context, ) try: msg.send_async([self.get_email()]) except Exception as e: logger = get_logger(name='sentry.mail') logger.exception(e)
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, headers=None, context=None, fail_silently=False): subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, ) send_to = self._send_to return msg.send(to=send_to, fail_silently=fail_silently)
def _build_message( self, project, subject, template=None, html_template=None, body=None, reference=None, reply_reference=None, headers=None, context=None, send_to=None, type=None, ): if not send_to: logger.debug("Skipping message rendering, no users to send to.") return subject_prefix = self._build_subject_prefix(project) subject = force_text(subject) msg = MessageBuilder( subject=f"{subject_prefix}{subject}", template=template, html_template=html_template, body=body, headers=headers, type=type, context=context, reference=reference, reply_reference=reply_reference, ) msg.add_users(send_to, project=project) return msg
def _build_message(self, project, subject, template=None, html_template=None, body=None, reference=None, reply_reference=None, headers=None, context=None, send_to=None, type=None): if send_to is None: send_to = self.get_send_to(project) if not send_to: logger.debug('Skipping message rendering, no users to send to.') return subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() subject_prefix = force_text(subject_prefix) subject = force_text(subject) msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, type=type, context=context, reference=reference, reply_reference=reply_reference, ) msg.add_users(send_to, project=project) return msg
def build_message(timestamp, duration, organization, user, reports): start, stop = interval = _to_interval(timestamp, duration) duration_spec = durations[duration] message = MessageBuilder( subject=u'{} Report for {}: {} - {}'.format( duration_spec.adjective.title(), organization.name, date_format(start), date_format(stop), ), template='sentry/emails/reports/body.txt', html_template='sentry/emails/reports/body.html', type='report.organization', context={ 'duration': duration_spec, 'interval': { 'start': date_format(start), 'stop': date_format(stop), }, 'organization': organization, 'personal': fetch_personal_statistics( interval, organization, user, ), 'report': to_context(organization, interval, reports), 'user': user, }, ) message.add_users((user.id, )) return message
def _build_message(self, subject, template=None, html_template=None, body=None, project=None, group=None, headers=None, context=None): """ Identical function to _build_message for sentry_mail, by the Sentry Team, except for the send_to list that is received is assigned to the message's list instead of appended. """ send_to = self.get_option('emails', project) or [] if not send_to: logger.debug('Skipping message rendering, no users to send to.') return subject_prefix = self.get_option('subject_prefix', project) or \ self.subject_prefix subject_prefix = force_text(subject_prefix) subject = force_text(subject) msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, reference=group, ) msg._send_to = set(send_to) return msg
def handle_project(plugin, project, stream): stream.write('# Project: %s\n' % project) from sentry.utils.email import MessageBuilder msg = MessageBuilder('test') msg.add_users(plugin.get_sendable_users(project), project) for email in msg._send_to: stream.write(email + '\n')
def test_fake_dont_send(self): project = self.project user_a = User.objects.create(email=create_fake_email('foo', 'fake')) user_b = User.objects.create(email=create_fake_email('bar', 'fake')) user_c = User.objects.create(email=create_fake_email('baz', 'fake')) UserOption.objects.create( user=user_b, key='alert_email', value=create_fake_email('fizzle', 'fake'), ) UserOption.objects.create( user=user_c, project=project, key='mail:email', value=create_fake_email('bazzer', 'fake'), ) msg = MessageBuilder( subject='Test', body='hello world', html_body='<!DOCTYPE html>\n<b>hello world</b>', ) msg.add_users([user_a.id, user_b.id, user_c.id], project=project) msg.send() assert len(mail.outbox) == 0
def test_add_groupemailthread(self, make_msgid): make_msgid.return_value = 'abc123' msg = MessageBuilder( subject='Test', body='hello world', html_body='<b>hello world</b>', reference=self.group, ) msg.send(['*****@*****.**']) assert len(mail.outbox) == 1 out = mail.outbox[0] assert out.to == ['*****@*****.**'] assert out.subject == 'Test', 'First message should not have Re: prefix' assert out.extra_headers['Message-Id'] == 'abc123' assert 'In-Reply-To' not in out.extra_headers assert 'References' not in out.extra_headers assert out.body == 'hello world' assert len(out.alternatives) == 1 assert out.alternatives[0] == ( '<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>', 'text/html', ) # Our new EmailThread row was added assert GroupEmailThread.objects.count() == 1 thread = GroupEmailThread.objects.all()[0] assert thread.msgid == 'abc123' assert thread.email == '*****@*****.**' assert thread.group == self.group
def _send_mail( self, subject, template=None, html_template=None, body=None, project=None, headers=None, context=None, fail_silently=False, ): subject_prefix = self.get_option("subject_prefix", project) or self.subject_prefix msg = MessageBuilder( subject="%s%s" % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, ) send_to = self._send_to return msg.send(to=send_to, fail_silently=fail_silently)
def build_message(timestamp, duration, organization, user, report): start, stop = interval = _to_interval(timestamp, duration) duration_spec = durations[duration] message = MessageBuilder( subject=u'{} Report for {}: {} - {}'.format( duration_spec.adjective.title(), organization.name, date_format(start), date_format(stop), ), template='sentry/emails/reports/body.txt', html_template='sentry/emails/reports/body.html', type='report.organization', context={ 'duration': duration_spec, 'interval': { 'start': date_format(start), 'stop': date_format(stop), }, 'organization': organization, 'personal': fetch_personal_statistics( interval, organization, user, ), 'report': to_context(report), 'user': user, }, ) message.add_users((user.id,)) return message
def test_with_users(self): project = self.project user_a = User.objects.create(email='*****@*****.**') user_b = User.objects.create(email='*****@*****.**') user_c = User.objects.create(email='*****@*****.**') UserOption.objects.create( user=user_b, key='alert_email', value='*****@*****.**', ) UserOption.objects.create( user=user_c, project=project, key='mail:email', value='*****@*****.**', ) msg = MessageBuilder( subject='Test', body='hello world', html_body='<!DOCTYPE html>\n<b>hello world</b>', ) msg.add_users([user_a.id, user_b.id, user_c.id], project=project) msg.send() assert len(mail.outbox) == 3 assert sorted([out.to[0] for out in mail.outbox]) == [ '*****@*****.**', '*****@*****.**', '*****@*****.**', ]
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, group=None, headers=None, context=None, fail_silently=False): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix try: subject_prefix = subject_prefix.encode("utf-8") subject = subject.encode("utf-8") except UnicodeDecodeError: subject_prefix = unicode(subject_prefix, "utf-8") subject = unicode(subject, "utf-8") msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, reference=group, ) msg.add_users(send_to, project=project) return msg.send(fail_silently=fail_silently)
def send_confirm_email_singular(self, email, is_new_user=False): from sentry import options from sentry.utils.email import MessageBuilder if not email.hash_is_valid(): email.set_hash() email.save() context = { 'user': self, 'url': absolute_uri( reverse('sentry-account-confirm-email', args=[self.id, email.validation_hash]) ), 'confirm_email': email.email, 'is_new_user': is_new_user, } msg = MessageBuilder( subject='%sConfirm Email' % (options.get('mail.subject-prefix'), ), template='sentry/emails/confirm_email.txt', html_template='sentry/emails/confirm_email.html', type='user.confirm_email', context=context, ) msg.send_async([email.email])
def send_approved_email(self): from sentry.utils.email import MessageBuilder user = self.member.user email = user.email organization = self.team.organization context = { 'email': email, 'name': user.get_display_name(), 'organization': organization, 'team': self.team, } msg = MessageBuilder( subject='Sentry Access Request', template='sentry/emails/access-approved.txt', html_template='sentry/emails/access-approved.html', context=context, ) try: msg.send([email]) except Exception as e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_request_email(self): from sentry.utils.email import MessageBuilder user = self.member.user email = user.email organization = self.team.organization context = { 'email': email, 'name': user.get_display_name(), 'organization': organization, 'team': self.team, 'url': absolute_uri(reverse('sentry-organization-members', kwargs={ 'organization_slug': organization.slug, }) + '?ref=access-requests'), } msg = MessageBuilder( subject='Sentry Access Request', template='sentry/emails/request-team-access.txt', html_template='sentry/emails/request-team-access.html', context=context, ) try: msg.send([email]) except Exception as e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_sso_unlink_email(self, actor, provider): from sentry.utils.email import MessageBuilder from sentry.models import LostPasswordHash email = self.get_email() recover_uri = '{path}?{query}'.format( path=reverse('sentry-account-recover'), query=urlencode({'email': email}), ) context = { 'email': email, 'recover_url': absolute_uri(recover_uri), 'has_password': self.user.password, 'organization': self.organization, 'actor': actor, 'provider': provider, } if not self.user.password: password_hash = LostPasswordHash.for_user(self.user) context['set_password_url'] = password_hash.get_absolute_url(mode='set_password') msg = MessageBuilder( subject='Action Required for %s' % (self.organization.name, ), template='sentry/emails/auth-sso-disabled.txt', html_template='sentry/emails/auth-sso-disabled.html', type='organization.auth_sso_disabled', context=context, ) msg.send_async([email])
def send_notification(self): from sentry.utils.email import MessageBuilder if self.type != Activity.NOTE or not self.group: return # TODO(dcramer): some of this logic is duplicated in NotificationPlugin # fetch access group members user_list = set(m.user for m in AccessGroup.objects.filter( projects=self.project, members__is_active=True, ).exclude(members__id=self.user_id, )) if self.project.team: # fetch team members user_list |= set( m.user for m in self.project.team.member_set.filter( user__is_active=True, ).exclude(user__id=self.user_id, )) if not user_list: return disabled = set( UserOption.objects.filter( user__in=user_list, key='subscribe_comments', value='0', ).values_list('user', flat=True)) send_to = [ u.email for u in user_list if u.email and u.id not in disabled ] if not send_to: return author = self.user.first_name or self.user.username subject = '%s: %s' % (author, self.data['text'].splitlines()[0][:64]) context = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', ) try: msg.send(to=send_to) except Exception, e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def test_stripped_newline(self): msg = MessageBuilder(subject="Foo\r\nBar", body="hello world", html_body="<b>hello world</b") msg.send(["*****@*****.**"]) assert len(mail.outbox) == 1 assert mail.outbox[0].subject == "Foo"
def send_invite_request_notification_email(member_id): try: om = OrganizationMember.objects.select_related( "inviter", "organization").get(id=member_id) except OrganizationMember.DoesNotExist: return link_args = {"organization_slug": om.organization.slug} context = { "email": om.email, "organization_name": om.organization.name, "pending_requests_link": absolute_uri( reverse("sentry-organization-members-requests", kwargs=link_args)), } if om.requested_to_join: email_args = { "template": "sentry/emails/organization-join-request.txt", "html_template": "sentry/emails/organization-join-request.html", } context["settings_link"] = absolute_uri( reverse("sentry-organization-settings", args=[om.organization.slug])) elif om.requested_to_be_invited: email_args = { "template": "sentry/emails/organization-invite-request.txt", "html_template": "sentry/emails/organization-invite-request.html", } context["inviter_name"] = om.inviter.get_salutation_name else: raise RuntimeError("This member is not pending invitation") recipients = OrganizationMember.objects.select_related("user").filter( organization_id=om.organization_id, user__isnull=False, invite_status=InviteStatus.APPROVED.value, role__in=(r.id for r in roles.get_all() if r.has_scope("member:write")), ) msg = MessageBuilder( subject=f"Access request to {om.organization.name}", type="organization.invite-request", context=context, **email_args, ) for recipient in recipients: try: msg.send_async([recipient.get_email()]) except Exception as e: logger = get_logger(name="sentry.mail") logger.exception(e)
def send_request_notification_email(self): from sentry.utils.email import MessageBuilder link_args = {"organization_slug": self.organization.slug} context = { "email": self.email, "inviter": self.inviter, "organization": self.organization, "organization_link": absolute_uri( reverse("sentry-organization-index", args=[self.organization.slug])), "pending_requests_link": absolute_uri( reverse("sentry-organization-members-requests", kwargs=link_args)), } if self.requested_to_join: email_args = { "template": "sentry/emails/organization-join-request.txt", "html_template": "sentry/emails/organization-join-request.html", } elif self.requested_to_be_invited: email_args = { "template": "sentry/emails/organization-invite-request.txt", "html_template": "sentry/emails/organization-invite-request.html", } else: raise RuntimeError("This member is not pending invitation") recipients = OrganizationMember.objects.select_related("user").filter( organization_id=self.organization_id, user__isnull=False, invite_status=InviteStatus.APPROVED.value, role__in=(r.id for r in roles.get_all() if r.has_scope("member:write")), ) msg = MessageBuilder(subject="Access request to %s" % (self.organization.name, ), type="organization.invite-request", context=context, **email_args) for recipient in recipients: try: msg.send_async([recipient.get_email()]) except Exception as e: logger = get_logger(name="sentry.mail") logger.exception(e)
def test_bcc_on_send(self): msg = MessageBuilder(subject="Test", body="hello world") msg.send(["*****@*****.**"], bcc=["*****@*****.**"]) assert len(mail.outbox) == 1 out = mail.outbox[0] assert out.to == ["*****@*****.**"] assert out.bcc == ["*****@*****.**"]
def test_reply_reference(self, make_msgid): make_msgid.return_value = 'abc123' msg = MessageBuilder( subject='Test', body='hello world', html_body='<b>hello world</b>', reference=self.activity, reply_reference=self.group, ) msg.send(['*****@*****.**']) assert len(mail.outbox) == 1 out = mail.outbox[0] assert out.to == ['*****@*****.**'] assert out.subject == 'Re: Test' assert out.extra_headers['Message-Id'] == 'abc123' assert 'In-Reply-To' not in out.extra_headers assert 'References' not in out.extra_headers assert out.body == 'hello world' assert len(out.alternatives) == 1 assert out.alternatives[0] == ( '<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>', 'text/html', ) # Our new EmailThread row was added assert GroupEmailThread.objects.count() == 1 thread = GroupEmailThread.objects.all()[0] assert thread.msgid == 'abc123' assert thread.email == '*****@*****.**' assert thread.group == self.group # new msgid for the next message make_msgid.return_value = '321cba' msg.send(['*****@*****.**']) assert len(mail.outbox) == 2 out = mail.outbox[1] assert out.to == ['*****@*****.**'] assert out.subject == 'Re: Test' assert out.extra_headers['Message-Id'] == '321cba' assert out.extra_headers['In-Reply-To'] == 'abc123' assert out.extra_headers['References'] == 'abc123' assert out.body == 'hello world' assert len(out.alternatives) == 1 assert out.alternatives[0] == ( '<!DOCTYPE html>\n<html><body><b>hello world</b></body></html>', 'text/html', ) # Our new GroupEmailThread row was added assert GroupEmailThread.objects.count() == 1, 'Should not have added a new row' assert GroupEmailThread.objects.all()[0].msgid == 'abc123', 'msgid should not have changed'
def test_stripped_newline(self): msg = MessageBuilder( subject='Foo\r\nBar', body='hello world', html_body='<b>hello world</b', ) msg.send(['*****@*****.**']) assert len(mail.outbox) == 1 assert mail.outbox[0].subject == 'Foo'
def test_get_built_messages(self): msg = MessageBuilder( subject="Test", body="hello world", html_body="<b>hello world</b>", reference=self.activity, reply_reference=self.group, ) results = msg.get_built_messages(["*****@*****.**"]) assert len(results) == 1
def test_get_built_messages(self): msg = MessageBuilder( subject='Test', body='hello world', html_body='<b>hello world</b>', reference=self.activity, reply_reference=self.group, ) results = msg.get_built_messages(['*****@*****.**']) assert len(results) == 1
def send(self): if not self.should_email(): return participants = self.get_participants() if not participants: return activity = self.activity project = self.project group = self.group context = self.get_base_context() context.update(self.get_context()) template = self.get_template() html_template = self.get_html_template() email_type = self.get_email_type() headers = self.get_headers() for user, reason in participants.items(): if group: context.update( { 'reason': GroupSubscriptionReason.descriptions.get( reason, "are subscribed to this issue", ), 'unsubscribe_link': generate_signed_link( user.id, 'sentry-account-email-unsubscribe-issue', kwargs={'issue_id': group.id}, ), } ) user_context = self.get_user_context(user) if user_context: user_context.update(context) else: user_context = context msg = MessageBuilder( subject=self.get_subject_with_prefix(), template=template, html_template=html_template, headers=headers, type=email_type, context=user_context, reference=activity, reply_reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def post(self, request, organization): """ Email the organization owners asking them to install an integration. ```````````````````````````````````````````````````````````````````` When a non-owner user views integrations in the integrations directory, they lack the ability to install them themselves. POSTing to this API alerts users with permission that there is demand for this integration. :param string providerSlug: Unique string that identifies the integration. :param string providerType: One of: first_party, plugin, sentry_app. :param string message: Optional message from the requester to the owners. """ provider_type = request.data.get("providerType") provider_slug = request.data.get("providerSlug") message_option = request.data.get("message", "").strip() try: provider_name = get_provider_name(provider_type, provider_slug) except Exception as error: return Response({"detail": error.message}, status=400) requester = request.user owners_list = organization.get_owners() # If for some reason the user had permissions all along, silently fail. if requester.id in [user.id for user in owners_list]: return Response({"detail": "User can install integration"}, status=200) msg = MessageBuilder( subject="Your team member requested the %s integration on Sentry" % provider_name, template="sentry/emails/requests/organization-integration.txt", html_template="sentry/emails/requests/organization-integration.html", type="organization.integration.request", context={ "integration_link": get_url(organization, provider_type, provider_slug), "integration_name": provider_name, "message": message_option, "organization_name": organization.name, "requester_name": requester.name or requester.username, "requester_link": absolute_uri( "/settings/{organization_slug}/members/{user_id}/".format( organization_slug=organization.slug, user_id=requester.id, ) ), "settings_link": absolute_uri( reverse("sentry-organization-settings", args=[organization.slug]) ), }, ) msg.send_async([user.email for user in owners_list]) return Response(status=201)
def test_bcc_on_send(self): msg = MessageBuilder( subject='Test', body='hello world', ) msg.send(['*****@*****.**'], bcc=['*****@*****.**']) assert len(mail.outbox) == 1 out = mail.outbox[0] assert out.to == ['*****@*****.**'] assert out.bcc == ['*****@*****.**']
def call_to_action(self, org: Organization, user: User, member: OrganizationMember): # send invite to setup 2fa email_context = {"url": member.get_invite_link(), "organization": org} subject = "{} {} Mandatory: Enable Two-Factor Authentication".format( options.get("mail.subject-prefix"), org.name.capitalize()) message = MessageBuilder( subject=subject, template="sentry/emails/setup_2fa.txt", html_template="sentry/emails/setup_2fa.html", type="user.setup_2fa", context=email_context, ) message.send_async([member.email])
def _remove_2fa_non_compliant_member(member, org, actor=None, actor_key=None, ip_address=None): user = member.user logging_data = { 'organization_id': org.id, 'user_id': user.id, 'member_id': member.id } try: member.email = member.get_email() member.user = None member.save() except (AssertionError, IntegrityError): logger.warning( 'Could not remove 2FA noncompliant user from org', extra=logging_data ) else: logger.info( '2FA noncompliant user removed from org', extra=logging_data ) AuditLogEntry.objects.create( actor=actor, actor_key=actor_key, ip_address=ip_address, event=AuditLogEntryEvent.MEMBER_PENDING, data=member.get_audit_log_data(), organization=org, target_object=org.id, target_user=user, ) # send invite to setup 2fa email_context = { 'url': member.get_invite_link(), 'organization': org } subject = u'{} {} Mandatory: Enable Two-Factor Authentication'.format( options.get('mail.subject-prefix'), org.name.capitalize(), ) message = MessageBuilder( subject=subject, template='sentry/emails/setup_2fa.txt', html_template='sentry/emails/setup_2fa.html', type='user.setup_2fa', context=email_context, ) message.send_async([member.email])
def send_request_email(self): from sentry.models import OrganizationMember from sentry.utils.email import MessageBuilder user = self.member.user email = user.email organization = self.team.organization context = { "email": email, "name": user.get_display_name(), "organization": organization, "team": self.team, "url": absolute_uri( reverse( "sentry-organization-members-requests", kwargs={"organization_slug": organization.slug}, )), } if self.requester: context.update({"requester": self.requester.get_display_name()}) msg = MessageBuilder( subject="Sentry Access Request", template="sentry/emails/request-team-access.txt", html_template="sentry/emails/request-team-access.html", type="team.access.request", context=context, ) global_roles = [ r.id for r in roles.with_scope("org:write") if r.is_global ] team_roles = [r.id for r in roles.with_scope("team:write")] # find members which are either team scoped or have access to all teams member_list = OrganizationMember.objects.filter( Q(role__in=global_roles) | Q(teams=self.team, role__in=team_roles), organization=self.team.organization, user__isnull=False, ).select_related("user") msg.send_async([m.user.email for m in member_list])
def email_failure(self, message): from sentry.utils.email import MessageBuilder msg = MessageBuilder( subject="Unable to Export Data", context={ "error_message": message, "payload": json.dumps(self.payload, indent=2, sort_keys=True), }, type="organization.export-data", template="sentry/emails/data-export-failure.txt", html_template="sentry/emails/data-export-failure.html", ) msg.send_async([self.user.email]) self.delete()
def _send_mail(self, subject, body, html_body=None, project=None, headers=None, fail_silently=False): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), body=body, html_body=html_body, headers=headers, ) msg.send(send_to, fail_silently=fail_silently)
def send_delete_confirmation(self, audit_log_entry, countdown): from sentry import options from sentry.utils.email import MessageBuilder owners = self.get_owners() context = { 'organization': self, 'audit_log_entry': audit_log_entry, 'eta': timezone.now() + timedelta(seconds=countdown), 'url': absolute_uri( reverse( 'sentry-restore-organization', args=[self.slug], )), } MessageBuilder( subject='%sOrganization Queued for Deletion' % (options.get('mail.subject-prefix'), ), template='sentry/emails/org_delete_confirm.txt', html_template='sentry/emails/org_delete_confirm.html', type='org.confirm_delete', context=context, ).send_async([o.email for o in owners])
def send_delete_confirmation(self, audit_log_entry, countdown): from sentry import options from sentry.utils.email import MessageBuilder owners = self.get_owners() context = { "organization": self, "audit_log_entry": audit_log_entry, "eta": timezone.now() + timedelta(seconds=countdown), "url": absolute_uri( reverse("sentry-restore-organization", args=[self.slug])), } MessageBuilder( subject="{}Organization Queued for Deletion".format( options.get("mail.subject-prefix")), template="sentry/emails/org_delete_confirm.txt", html_template="sentry/emails/org_delete_confirm.html", type="org.confirm_delete", context=context, ).send_async([o.email for o in owners])
def generate_security_email(account, type, actor, ip_address, context=None, current_datetime=None): if current_datetime is None: current_datetime = timezone.now() subject = 'Security settings changed' if type == 'mfa-removed': assert 'authenticator' in context template = 'sentry/emails/mfa-removed.txt' html_template = 'sentry/emails/mfa-removed.html' elif type == 'mfa-added': assert 'authenticator' in context template = 'sentry/emails/mfa-added.txt' html_template = 'sentry/emails/mfa-added.html' elif type == 'password-changed': template = 'sentry/emails/password-changed.txt' html_template = 'sentry/emails/password-changed.html' else: raise ValueError('unknown type: {}'.format(type)) new_context = { 'account': account, 'actor': actor, 'ip_address': ip_address, 'datetime': current_datetime, } if context: new_context.update(context) return MessageBuilder( subject=subject, context=new_context, template=template, html_template=html_template, )
def send_notification(self): from sentry.utils.email import MessageBuilder, group_id_to_email if not self.group_id: return if self.type not in (Activity.NOTE, Activity.ASSIGNED): return send_to = self.get_recipients() if not send_to: return author = self.user.first_name or self.user.username subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) context = { 'data': self.data, 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.id), } template_name = self.get_type_display() msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/activity/{}.txt'.format(template_name), html_template='sentry/emails/activity/{}.html'.format(template_name), headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def _remove_2fa_non_compliant_member(member, org, actor=None, actor_key=None, ip_address=None): user = member.user logging_data = { 'organization_id': org.id, 'user_id': user.id, 'member_id': member.id } try: member.email = member.get_email() member.user = None member.save() except (AssertionError, IntegrityError): logger.warning('Could not remove 2FA noncompliant user from org', extra=logging_data) else: logger.info('2FA noncompliant user removed from org', extra=logging_data) AuditLogEntry.objects.create( actor=actor, actor_key=actor_key, ip_address=ip_address, event=AuditLogEntryEvent.MEMBER_PENDING, data=member.get_audit_log_data(), organization=org, target_object=org.id, target_user=user, ) # send invite to setup 2fa email_context = {'url': member.get_invite_link(), 'organization': org} subject = u'{} {} Mandatory: Enable Two-Factor Authentication'.format( options.get('mail.subject-prefix'), org.name.capitalize(), ) message = MessageBuilder( subject=subject, template='sentry/emails/setup_2fa.txt', html_template='sentry/emails/setup_2fa.html', type='user.setup_2fa', context=email_context, ) message.send_async([member.email])
def send_request_email(self): from sentry.models import OrganizationMember from sentry.utils.email import MessageBuilder user = self.member.user email = user.email organization = self.team.organization context = { 'email': email, 'name': user.get_display_name(), 'organization': organization, 'team': self.team, 'url': absolute_uri( reverse('sentry-organization-members', kwargs={ 'organization_slug': organization.slug, }) + '?ref=access-requests'), } msg = MessageBuilder( subject='Sentry Access Request', template='sentry/emails/request-team-access.txt', html_template='sentry/emails/request-team-access.html', context=context, ) roles_capable = [r.id for r in roles.with_scope('team:write')] non_global_roles = [ r for r in roles_capable if not roles.get(r).is_global or roles.get(r).has_scope('member:write') ] # find members which are either team scoped or have access to all teams member_list = OrganizationMember.objects.filter( Q(role__in=non_global_roles) | Q(teams=self.team, role__in=roles_capable), organization=self.team.organization, user__isnull=False, ).select_related('user') msg.send_async([m.user.email for m in member_list])
def send(self): if not self.should_email(): return users = self.get_participants() if not users: return activity = self.activity project = self.project group = self.group context = self.get_base_context() context.update(self.get_context()) subject_prefix = self._get_subject_prefix() subject = (u'{}{}'.format( subject_prefix, self.get_subject(), )).encode('utf-8') template = self.get_template() html_template = self.get_html_template() email_type = self.get_email_type() headers = self.get_headers() for user in users: if group: context['unsubscribe_link'] = generate_signed_link( user.id, 'sentry-account-email-unsubscribe-issue', kwargs={'issue_id': group.id}, ) msg = MessageBuilder( subject=subject, template=template, html_template=html_template, headers=headers, type=email_type, context=context, reference=activity, reply_reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def send_recover_mail(self): from sentry.utils.email import MessageBuilder context = { 'user': self.user, 'domain': urlparse(settings.SENTRY_URL_PREFIX).hostname, 'url': absolute_uri(reverse( 'sentry-account-recover-confirm', args=[self.user.id, self.hash] )), } msg = MessageBuilder( subject='%sPassword Recovery' % (settings.EMAIL_SUBJECT_PREFIX,), template='sentry/emails/recover_account.txt', context=context, ) msg.send_async([self.user.email])
def email_failure(self, message): from sentry.utils.email import MessageBuilder msg = MessageBuilder( subject="We couldn't export your data.", context={ "creation": self.format_date(self.date_added), "error_message": message, "payload": json.dumps(self.payload, indent=2, sort_keys=True), }, type="organization.export-data", template="sentry/emails/data-export-failure.txt", html_template="sentry/emails/data-export-failure.html", ) msg.send_async([self.user.email]) metrics.incr("dataexport.end", instance="failure") self.delete()
def send_approved_email(self): from sentry.utils.email import MessageBuilder user = self.member.user email = user.email organization = self.team.organization context = {"email": email, "name": user.get_display_name(), "organization": organization, "team": self.team} msg = MessageBuilder( subject="Sentry Access Request", template="sentry/emails/access-approved.txt", html_template="sentry/emails/access-approved.html", context=context, ) msg.send_async([email])
def send_sso_link_email(self): from sentry.utils.email import MessageBuilder context = { 'email': self.email, 'organization_name': self.organization.name, 'url': absolute_uri(reverse('sentry-auth-link-identity', kwargs={ 'organization_slug': self.organization.slug, })), } msg = MessageBuilder( subject='Action Required for %s' % (self.organization.name,), template='sentry/emails/auth-link-identity.txt', html_template='sentry/emails/auth-link-identity.html', context=context, ) msg.send_async([self.get_email()])
def send_recover_mail(self): from sentry import options from sentry.http import get_server_hostname from sentry.utils.email import MessageBuilder context = { 'user': self.user, 'domain': get_server_hostname(), 'url': self.get_absolute_url(), } msg = MessageBuilder( subject='%sPassword Recovery' % (options.get('mail.subject-prefix'),), template='sentry/emails/recover_account.txt', html_template='sentry/emails/recover_account.html', type='user.password_recovery', context=context, ) msg.send_async([self.user.email])
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, headers=None, context=None, fail_silently=False): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, ) msg.add_users(send_to, project=project) return msg.send(fail_silently=fail_silently)
def send_notification(self): from sentry.utils.email import MessageBuilder if self.type != Activity.NOTE or not self.group: return user_list = list(User.objects.filter( groupseen__group=self.group, ).exclude(user=self.user)) disabled = set(UserOption.objects.filter( user__in=user_list, key='subscribe_comments', value='0')) send_to = [ u.email for u in user_list if u.id not in disabled and u.email ] author = self.user.first_name or self.user.username subject = '%s: %s' % ( author, self.data['text'].splitlines()[0][:64]) context = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', ) try: msg.send(to=send_to) except Exception, e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_recover_mail(self): from sentry import options from sentry.http import get_server_hostname from sentry.utils.email import MessageBuilder context = { 'user': self.user, 'domain': get_server_hostname(), 'url': absolute_uri(reverse( 'sentry-account-recover-confirm', args=[self.user.id, self.hash] )), } msg = MessageBuilder( subject='%sPassword Recovery' % (options.get('mail.subject-prefix'),), template='sentry/emails/recover_account.txt', context=context, ) msg.send_async([self.user.email])