def test_does_not_generates_list_ids_for_unregistered_types(self):
        message = (
            MessageBuilder(
                subject="Test",
                body="hello world",
                html_body="<b>hello world</b>",
                reference=object(),
            )
            .get_built_messages(["*****@*****.**"])[0]
            .message()
        )

        assert "List-Id" not in message
Esempio n. 2
0
def generate_fetch_commits_error_email(release, repo, error_message):
    new_context = {
        "release": release,
        "error_message": error_message,
        "repo": repo
    }

    return MessageBuilder(
        subject="Unable to Fetch Commits",
        context=new_context,
        template="sentry/emails/unable-to-fetch-commits.txt",
        html_template="sentry/emails/unable-to-fetch-commits.html",
    )
Esempio n. 3
0
def generate_invalid_identity_email(identity, commit_failure=False):
    new_context = {
        "identity": identity,
        "auth_url": absolute_uri(reverse("socialauth_associate", args=[identity.provider])),
        "commit_failure": commit_failure,
    }

    return MessageBuilder(
        subject="Unable to Fetch Commits" if commit_failure else "Action Required",
        context=new_context,
        template="sentry/emails/identity-invalid.txt",
        html_template="sentry/emails/identity-invalid.html",
    )
    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(
                    f"/settings/{organization.slug}/members/{requester.id}/"
                ),
                "settings_link": absolute_uri(
                    reverse("sentry-organization-settings", args=[organization.slug])
                ),
            },
        )
        msg.send([user.email for user in owners_list])

        return Response(status=201)
Esempio n. 5
0
    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()
Esempio n. 6
0
    def generate_delete_fail_email(self, error_message):
        from sentry.utils.email import MessageBuilder

        new_context = {
            'repo': self,
            'error_message': error_message,
        }

        return MessageBuilder(
            subject='Unable to Delete Repository',
            context=new_context,
            template='sentry/emails/unable-to-delete-repo.txt',
            html_template='sentry/emails/unable-to-delete-repo.html',
        )
Esempio n. 7
0
 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])
Esempio n. 8
0
 def build_message(self, context, status, user_id):
     display = self.status_display[status]
     return MessageBuilder(
         subject="[{}] {} - {}".format(context["status"],
                                       context["incident_name"],
                                       self.project.slug),
         template="sentry/emails/incidents/trigger.txt",
         html_template="sentry/emails/incidents/trigger.html",
         type=f"incident.alert_rule_{display.lower()}",
         context=context,
         headers={
             "X-SMTPAPI": json.dumps({"category": "metric_alert_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])
Esempio n. 10
0
    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()
Esempio n. 11
0
def send_notification_as_email(
    notification: ActivityNotification, user: User, context: Mapping[str, Any]
) -> None:
    msg = MessageBuilder(
        subject=notification.get_subject_with_prefix(),
        template=notification.get_template(),
        html_template=notification.get_html_template(),
        headers=notification.get_headers(),
        type=notification.get_email_type(),
        context=context,
        reference=notification.activity,
        reply_reference=notification.group,
    )
    msg.add_users([user.id], project=notification.project)
    msg.send_async()
Esempio n. 12
0
    def generate_delete_fail_email(self, error_message):
        from sentry.utils.email import MessageBuilder

        new_context = {
            "repo": self,
            "error_message": error_message,
            "provider_name": self.get_provider().name,
        }

        return MessageBuilder(
            subject="Unable to Delete Repository Webhooks",
            context=new_context,
            template="sentry/emails/unable-to-delete-repo.txt",
            html_template="sentry/emails/unable-to-delete-repo.html",
        )
Esempio n. 13
0
    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)
Esempio n. 14
0
def generate_security_email(
    account: "User",
    type: str,
    actor: "User",
    ip_address: str,
    context: Optional[Mapping[str, Any]] = None,
    current_datetime: Optional[datetime] = None,
) -> MessageBuilder:
    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"
    elif type == "recovery-codes-regenerated":
        template = "sentry/emails/recovery-codes-regenerated.txt"
        html_template = "sentry/emails/recovery-codes-regenerated.html"
    elif type == "api-token-generated":
        template = "sentry/emails/api-token-generated.txt"
        html_template = "sentry/emails/api-token-generated.html"
    else:
        raise ValueError(f"unknown type: {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,
        type=type,
    )
    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_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])
Esempio n. 17
0
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])
Esempio n. 18
0
    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()
Esempio n. 19
0
def generate_invalid_identity_email(identity, commit_failure=False):
    new_context = {
        'identity':
        identity,
        'auth_url':
        absolute_uri(reverse('socialauth_associate',
                             args=[identity.provider])),
        'commit_failure':
        commit_failure,
    }

    return MessageBuilder(
        subject='Action Required',
        context=new_context,
        template='sentry/emails/identity-invalid.txt',
        html_template='sentry/emails/identity-invalid.html',
    )
Esempio n. 20
0
    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])
Esempio n. 21
0
    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()
Esempio n. 22
0
    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-organization', 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()])
Esempio n. 23
0
    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)
Esempio n. 24
0
    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])
Esempio n. 25
0
    def test_explicit_reply_to(self):
        msg = MessageBuilder(
            subject='Test',
            body='hello world',
            html_body='<b>hello world</b>',
            headers={'X-Sentry-Reply-To': '*****@*****.**'},
        )
        msg.send(['*****@*****.**'])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert out.to == ['*****@*****.**']
        assert out.subject == 'Test'
        assert out.extra_headers['Reply-To'] == '*****@*****.**'
        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',
        )
Esempio n. 26
0
    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()])
Esempio n. 27
0
    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])
Esempio n. 28
0
    def test_raw_content(self):
        msg = MessageBuilder(
            subject='Test',
            body='hello world',
            html_body='<b>hello world</b>',
            headers={'X-Test': 'foo'},
        )
        msg.send(['*****@*****.**'])

        assert len(mail.outbox) == 1

        out = mail.outbox[0]
        assert out.to == ['*****@*****.**']
        assert out.subject == 'Test'
        assert out.extra_headers['X-Test'] == 'foo'
        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',
        )
    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()])
Esempio n. 30
0
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'
    elif type == 'recovery-codes-regenerated':
        template = 'sentry/emails/recovery-codes-regenerated.txt'
        html_template = 'sentry/emails/recovery-codes-regenerated.html'
    elif type == 'api-token-generated':
        template = 'sentry/emails/api-token-generated.txt'
        html_template = 'sentry/emails/api-token-generated.html'
    else:
        raise ValueError(u'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,
        type=type
    )