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_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_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(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 _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_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 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 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(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_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_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', 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 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])
def send_sso_link_email(self, actor, provider): from sentry.utils.email import MessageBuilder link_args = {'organization_slug': self.organization.slug} context = { 'organization': self.organization, 'actor': actor, 'provider': provider, 'url': absolute_uri(reverse('sentry-auth-organization', kwargs=link_args)), } 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', type='organization.auth_link', context=context, ) msg.send_async([self.get_email()])
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: # TODO(Leander): Implement logging here return msg = MessageBuilder( subject="Your Download is Ready!", context={ "url": absolute_uri( reverse("sentry-data-export-details", args=[self.organization.slug, self.id])), "expiration": self.date_expired_string, }, template="sentry/emails/data-export-success.txt", html_template="sentry/emails/data-export-success.html", ) msg.send_async([self.user.email])
def send_notification_as_email( notification: ActivityNotification, users: Mapping[User, int], shared_context: Mapping[str, Any], ) -> None: headers = get_headers(notification) subject = get_subject_with_prefix(notification) type = get_email_type(notification) for user, reason in users.items(): msg = MessageBuilder( subject=subject, template=notification.get_template(), html_template=notification.get_html_template(), headers=headers, type=type, context=get_context(notification, user, reason, shared_context), reference=notification.activity, reply_reference=notification.group, ) msg.add_users([user.id], project=notification.project) msg.send_async()
def send_recover_mail(self, request): 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(), 'datetime': timezone.now(), 'ip_address': request.META['REMOTE_ADDR'], } 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_invite_email(self): from sentry.utils.email import MessageBuilder context = { 'email': self.email, 'organization': self.organization, 'url': self.get_invite_link(), } 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_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])
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_invite_email(self): from sentry.utils.email import MessageBuilder context = { "email": self.email, "organization": self.organization, "url": self.get_invite_link(), } 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_email(self, request, mode="recover"): 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(mode), "datetime": timezone.now(), "ip_address": request.META["REMOTE_ADDR"], } template = "set_password" if mode == "set_password" else "recover_account" msg = MessageBuilder( subject="{}Password Recovery".format(options.get("mail.subject-prefix")), template="sentry/emails/{name}.txt".format(name=template), html_template="sentry/emails/{name}.html".format(name=template), type="user.password_recovery", context=context, ) msg.send_async([self.user.email])
def send_email(self, request, mode='recover'): 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(mode), 'datetime': timezone.now(), 'ip_address': request.META['REMOTE_ADDR'], } template = 'set_password' if mode == 'set_password' else 'recover_account' msg = MessageBuilder( subject=u'{}Password Recovery'.format(options.get('mail.subject-prefix')), template=u'sentry/emails/{name}.txt'.format(name=template), html_template=u'sentry/emails/{name}.html'.format(name=template), type='user.password_recovery', context=context, ) msg.send_async([self.user.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_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", type="team.access.approved", context=context, ) msg.send_async([email])
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}), ) # Nothing to send if this member isn't associated to a user if not self.user_id: return 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_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, ) 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 send_sso_unlink_email(self, actor, provider): from sentry.models import LostPasswordHash from sentry.utils.email import MessageBuilder email = self.get_email() recover_uri = "{path}?{query}".format( path=reverse("sentry-account-recover"), query=urlencode({"email": email})) # Nothing to send if this member isn't associated to a user if not self.user_id: return 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=f"Action Required for {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_setup_2fa_emails(self): from sentry import options from sentry.utils.email import MessageBuilder from sentry.models import User for user in User.objects.filter( is_active=True, sentry_orgmember_set__organization=self, ): if not Authenticator.objects.user_has_2fa(user): context = { 'user': user, 'url': absolute_uri(reverse('sentry-account-settings-2fa')), 'organization': self } message = MessageBuilder( subject='%s %s Mandatory: Enable Two-Factor Authentication' % ( options.get('mail.subject-prefix'), self.name), template='sentry/emails/setup_2fa.txt', html_template='sentry/emails/setup_2fa.html', type='user.setup_2fa', context=context, ) message.send_async([user.email])
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="{}Confirm Email".format(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 call_to_action(self, org: Organization, user: User, member: OrganizationMember): import django.contrib.auth.models if isinstance(user, django.contrib.auth.models.User): # TODO(RyanSkonnord): Add test to repro this case (or delete check if unable) logger.warning( "Could not send verified email compliance notification (non-Sentry User model)" ) return elif not isinstance(user, User): raise TypeError(user) email = UserEmail.objects.get_primary_email(user) email_context = { "confirm_url": absolute_uri( reverse("sentry-account-confirm-email", args=[user.id, email.validation_hash])), "invite_url": member.get_invite_link(), "email": email.email, "organization": org, } subject = "{} {} Mandatory: Verify Email Address".format( options.get("mail.subject-prefix"), org.name.capitalize()) message = MessageBuilder( subject=subject, template="sentry/emails/setup_email.txt", html_template="sentry/emails/setup_email.html", type="user.setup_email", context=email_context, ) message.send_async([email])
def send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option("subject_prefix", settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + " " if self.group: subject = "%s%s" % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = "%sRelease %s" % (subject_prefix, self.data["version"]) else: raise NotImplementedError headers = {} context = { "data": self.data, "author": author, "project": self.project, "project_link": absolute_uri( reverse("sentry-stream", kwargs={"organization_slug": org.slug, "project_id": project.slug}) ), } if self.group: headers.update({"X-Sentry-Reply-To": group_id_to_email(self.group.id)}) context.update({"group": self.group, "link": self.group.get_absolute_url()}) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update( { "release": Release.objects.get(version=self.data["version"], project=project), "release_link": absolute_uri( reverse( "sentry-release-details", kwargs={ "organization_slug": org.slug, "project_id": project.slug, "version": self.data["version"], }, ) ), } ) 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 handle_user_report(self, payload, project, **kwargs): from sentry.models import Group, GroupSubscription, GroupSubscriptionReason group = Group.objects.get(id=payload['report']['issue']['id']) participants = GroupSubscription.objects.get_participants(group=group) if not participants: return context = { 'project': project, 'project_link': absolute_uri('/{}/{}/'.format( project.organization.slug, project.slug, )), 'issue_link': absolute_uri('/{}/{}/issues/{}/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), # TODO(dcramer): we dont have permalinks to feedback yet 'link': absolute_uri('/{}/{}/issues/{}/feedback/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), 'group': group, 'report': payload['report'], } subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() subject_prefix = force_text(subject_prefix) subject = force_text(u'{}{} - New Feedback from {}'.format( subject_prefix, group.qualified_short_id, payload['report']['name'], )) headers = { 'X-Sentry-Project': project.slug, } # TODO(dcramer): this is copypasta'd from activity notifications # and while it'd be nice to re-use all of that, they are currently # coupled to <Activity> instances which makes this tough for user, reason in participants.items(): 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}, ), }) msg = MessageBuilder( subject=subject, template='sentry/emails/activity/new-user-feedback.txt', html_template='sentry/emails/activity/new-user-feedback.html', headers=headers, type='notify.user-report', context=context, reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def handle_user_report(self, payload, project, **kwargs): metrics.incr("mail_adapter.handle_user_report") group = Group.objects.get(id=payload["report"]["issue"]["id"]) participants = GroupSubscription.objects.get_participants(group=group) if not participants: return org = group.organization enhanced_privacy = org.flags.enhanced_privacy context = { "project": project, "project_link": absolute_uri(u"/{}/{}/".format(project.organization.slug, project.slug)), "issue_link": absolute_uri(u"/{}/{}/issues/{}/".format( project.organization.slug, project.slug, payload["report"]["issue"]["id"])), # TODO(dcramer): we dont have permalinks to feedback yet "link": absolute_uri(u"/{}/{}/issues/{}/feedback/".format( project.organization.slug, project.slug, payload["report"]["issue"]["id"])), "group": group, "report": payload["report"], "enhanced_privacy": enhanced_privacy, } subject_prefix = self._build_subject_prefix(project) subject = force_text(u"{}{} - New Feedback from {}".format( subject_prefix, group.qualified_short_id, payload["report"]["name"])) headers = {"X-Sentry-Project": project.slug} # TODO(dcramer): this is copypasta'd from activity notifications # and while it'd be nice to re-use all of that, they are currently # coupled to <Activity> instances which makes this tough for user, reason in participants.items(): 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}, ), }) msg = MessageBuilder( subject=subject, template="sentry/emails/activity/new-user-feedback.txt", html_template="sentry/emails/activity/new-user-feedback.html", headers=headers, type="notify.user-report", context=context, 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 RuntimeError as error: return Response({"detail": force_text(error)}, 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 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", 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) analytics.record( "invite_request.sent", organization_id=om.organization.id, user_id=om.inviter.id if om.inviter else None, target_user_id=recipient.id, providers="email", )
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)), "settings_link": absolute_uri( reverse("sentry-organization-settings", args=[self.organization.slug])), } 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 send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization group = self.group if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' if self.group: subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = '%sRelease %s' % (subject_prefix, self.data['version']) else: raise NotImplementedError headers = {} context = { 'data': self.data, 'author': author, 'project': project, 'project_link': absolute_uri(reverse('sentry-stream', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, })), } if group: group_link = absolute_uri('/{}/{}/group/{}/'.format(org.slug, project.slug, group.id)) activity_link = '{}activity/'.format(group_link) headers.update({ 'X-Sentry-Reply-To': group_id_to_email(group.id), }) context.update({ 'group': group, 'link': group_link, 'activity_link': activity_link, }) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update({ 'release': Release.objects.get( version=self.data['version'], project=project, ), 'release_link': absolute_uri(reverse('sentry-release-details', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, 'version': self.data['version'], })), }) 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 handle_user_report(self, payload, project, **kwargs): from sentry.models import Group, GroupSubscription, GroupSubscriptionReason group = Group.objects.get(id=payload['report']['issue']['id']) participants = GroupSubscription.objects.get_participants(group=group) if not participants: return context = { 'project': project, 'project_link': absolute_uri(u'/{}/{}/'.format( project.organization.slug, project.slug, )), 'issue_link': absolute_uri(u'/{}/{}/issues/{}/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), # TODO(dcramer): we dont have permalinks to feedback yet 'link': absolute_uri(u'/{}/{}/issues/{}/feedback/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), 'group': group, 'report': payload['report'], } subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() subject_prefix = force_text(subject_prefix) subject = force_text(u'{}{} - New Feedback from {}'.format( subject_prefix, group.qualified_short_id, payload['report']['name'], )) headers = { 'X-Sentry-Project': project.slug, } # TODO(dcramer): this is copypasta'd from activity notifications # and while it'd be nice to re-use all of that, they are currently # coupled to <Activity> instances which makes this tough for user, reason in participants.items(): 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}, ), }) msg = MessageBuilder( subject=subject, template='sentry/emails/activity/new-user-feedback.txt', html_template='sentry/emails/activity/new-user-feedback.html', headers=headers, type='notify.user-report', context=context, reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' if self.group: subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = '%sRelease %s' % (subject_prefix, self.data['version']) else: raise NotImplementedError headers = {} context = { 'data': self.data, 'author': author, 'project': self.project, 'project_link': absolute_uri(reverse('sentry-stream', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, })), } if self.group: headers.update({ 'X-Sentry-Reply-To': group_id_to_email(self.group.id), }) context.update({ 'group': self.group, 'link': self.group.get_absolute_url(), }) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update({ 'release': Release.objects.get( version=self.data['version'], project=project, ), 'release_link': absolute_uri(reverse('sentry-release-details', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, 'version': self.data['version'], })), }) 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 send_notification(self): from sentry.models import User, UserOption, ProjectOption from sentry.utils.email import MessageBuilder, group_id_to_email 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_id_list = set( User.objects.filter( accessgroup__projects=self.project, is_active=True ).exclude( id=self.user_id, ).values_list('id', flat=True) ) if self.project.team: # fetch team members user_id_list |= set( u_id for u_id in self.project.team.member_set.exclude( user__id=self.user_id, ).values_list('user', flat=True) ) if not user_id_list: return disabled = set(UserOption.objects.filter( user__in=user_id_list, key='subscribe_notes', value=u'0', ).values_list('user', flat=True)) send_to = filter(lambda u_id: u_id not in disabled, user_id_list) if not send_to: return author = self.user.first_name or self.user.username subject_prefix = ProjectOption.objects.get_value( self.project, '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 = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.pk), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def send_notification(self): from sentry.models import User, UserOption, ProjectOption from sentry.utils.email import MessageBuilder, group_id_to_email 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_id_list = set( User.objects.filter(accessgroup__projects=self.project, is_active=True).exclude( id=self.user_id, ).values_list('id', flat=True)) if self.project.team: # fetch team members user_id_list |= set( u_id for u_id in self.project.team.member_set.filter( user__is_active=True, ).exclude(user__id=self.user_id, ). values_list('user', flat=True)) if not user_id_list: return disabled = set( UserOption.objects.filter( user__in=user_id_list, key='subscribe_notes', value=u'0', ).values_list('user', flat=True)) send_to = filter(lambda u_id: u_id not in disabled, user_id_list) if not send_to: return author = self.user.first_name or self.user.username subject_prefix = ProjectOption.objects.get_value( self.project, '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 = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.pk), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()