Ejemplo n.º 1
0
def edit_user(request, user_id):
    if not request.is_superuser():
        return HttpResponseRedirect(auth.get_login_url())

    try:
        user = User.objects.get(pk=user_id)
    except User.DoesNotExist:
        return HttpResponseRedirect(absolute_uri('/manage/users/'))

    form = ChangeUserForm(request.POST or None, instance=user)
    if form.is_valid():
        user = form.save()
        return HttpResponseRedirect(absolute_uri('/manage/users/'))

    project_list = Project.objects.filter(
        status=0,
        organization__member_set__user=user,
    ).order_by('-date_added')

    context = {
        'form': form,
        'the_user': user,
        'project_list': project_list,
    }
    context.update(csrf(request))

    return render_to_response('sentry/admin/users/edit.html', context, request)
Ejemplo n.º 2
0
def remove_user(request, user_id):
    if six.text_type(user_id) == six.text_type(request.user.id):
        return HttpResponseRedirect(absolute_uri('/manage/users/'))

    try:
        user = User.objects.get(pk=user_id)
    except User.DoesNotExist:
        return HttpResponseRedirect(absolute_uri('/manage/users/'))

    form = RemoveUserForm(request.POST or None)
    if form.is_valid():
        if form.cleaned_data['removal_type'] == '2':
            user.delete()
        else:
            User.objects.filter(pk=user.pk).update(is_active=False)

        return HttpResponseRedirect(absolute_uri('/manage/users/'))

    context = csrf(request)
    context.update({
        'form': form,
        'the_user': user,
    })

    return render_to_response('sentry/admin/users/remove.html', context, request)
Ejemplo n.º 3
0
def build_saml_config(provider_config, org):
    """
    Construct the SAML configuration dict to be passed into the OneLogin SAML
    library.

    For more details about the structure of this object see the
    SAML2Provider.build_config method.
    """
    avd = provider_config.get('advanced', {})

    security_config = {
        'authnRequestsSigned': avd.get('authn_request_signed', False),
        'logoutRequestSigned': avd.get('logout_request_signed', False),
        'logoutResponseSigned': avd.get('logout_response_signed', False),
        'signMetadata': avd.get('metadata_signed', False),
        'wantMessagesSigned': avd.get('want_message_signed', False),
        'wantAssertionsSigned': avd.get('want_assertion_signed', False),
        'wantAssertionsEncrypted': avd.get('want_assertion_encrypted', False),
        'signatureAlgorithm': avd.get('signature_algorithm', OneLogin_Saml2_Constants.RSA_SHA256),
        'digestAlgorithm': avd.get('digest_algorithm', OneLogin_Saml2_Constants.SHA256),
        'wantNameId': False,
    }

    idp = provider_config['idp']

    # TODO(epurkhiser): This is also available in the helper and should probably come from there.
    acs_url = absolute_uri(reverse('sentry-auth-organization-saml-acs', args=[org]))
    sls_url = absolute_uri(reverse('sentry-auth-organization-saml-sls', args=[org]))
    metadata_url = absolute_uri(reverse('sentry-auth-organization-saml-metadata', args=[org]))

    saml_config = {
        'strict': True,
        'idp': {
            'entityId': idp['entity_id'],
            'x509cert': idp['x509cert'],
            'singleSignOnService': {'url': idp['sso_url']},
            'singleLogoutService': {'url': idp['slo_url']},
        },
        'sp': {
            'entityId': metadata_url,
            'assertionConsumerService': {
                'url': acs_url,
                'binding': OneLogin_Saml2_Constants.BINDING_HTTP_POST,
            },
            'singleLogoutService': {
                'url': sls_url,
                'binding': OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            },
        },
        'security': security_config,
    }

    if avd.get('x509cert') is not None:
        saml_config['sp']['x509cert'] = avd['x509cert']

    if avd.get('private_key') is not None:
        saml_config['sp']['privateKey'] = avd['private_key']

    return saml_config
    def handle(self, request, organization, team, project):
        token = None

        if request.method == "POST":
            op = request.POST.get("op")
            if op == "regenerate-token":
                token = self._regenerate_token(project)
                messages.add_message(request, messages.SUCCESS, OK_TOKEN_REGENERATED)
            elif op == "enable":
                self._handle_enable_plugin(request, project)
            elif op == "disable":
                self._handle_disable_plugin(request, project)
            return HttpResponseRedirect(request.path)

        if token is None:
            token = ProjectOption.objects.get_value(project, "sentry:release-token")
        if token is None:
            token = self._regenerate_token(project)

        enabled_plugins = []
        other_plugins = []
        for plugin in self._iter_plugins():
            if plugin.is_enabled(project):
                hook_url = absolute_uri(
                    reverse(
                        "sentry-release-hook",
                        kwargs={
                            "plugin_id": plugin.slug,
                            "project_id": project.id,
                            "signature": self._get_signature(project.id, plugin.slug, token),
                        },
                    )
                )
                content = plugin.get_release_doc_html(hook_url=hook_url)
                enabled_plugins.append((plugin, mark_safe(content)))
            elif plugin.can_configure_for_project(project):
                other_plugins.append(plugin)

        context = {
            "page": "release-tracking",
            "token": token,
            "enabled_plugins": enabled_plugins,
            "other_plugins": other_plugins,
            "webhook_url": absolute_uri(
                reverse(
                    "sentry-release-hook",
                    kwargs={
                        "plugin_id": "builtin",
                        "project_id": project.id,
                        "signature": self._get_signature(project.id, "builtin", token),
                    },
                )
            ),
        }

        return self.respond("sentry/project-release-tracking.html", context)
Ejemplo n.º 5
0
 def dispatch(self, request, pipeline):
     client_key = request.GET.get('clientKey')
     if client_key is None:
         return self.redirect(
             'https://bitbucket.org/site/addons/authorize?descriptor_uri=%s&redirect_uri=%s' % (
                 absolute_uri('/extensions/bitbucket/descriptor/'),
                 absolute_uri('/extensions/bitbucket/setup/'),
             ))
     pipeline.bind_state('bitbucket_client_key', client_key)
     return pipeline.next_step()
Ejemplo n.º 6
0
    def get_absolute_url(self):
        # HACK(dcramer): quick and dirty way to support code/users
        try:
            url_name = self.URL_NAMES[self.key]
        except KeyError:
            url_name = self.DEFAULT_URL_NAME
            return absolute_uri(reverse(url_name, args=[
                self.project.organization.slug, self.project.slug, self.key]))

        return absolute_uri(reverse(url_name, args=[
            self.project.organization.slug, self.project.slug]))
Ejemplo n.º 7
0
    def test_send_alert_event(self, safe_urlopen):
        group = self.create_group(project=self.project)
        event = self.create_event(group=group)
        rule_future = RuleFuture(
            rule=self.rule,
            kwargs={'sentry_app': self.sentry_app},
        )

        with self.feature('organizations:sentry10'):
            with self.tasks():
                notify_sentry_app(event, [rule_future])

        data = json.loads(faux(safe_urlopen).kwargs['data'])

        assert data == {
            'action': 'triggered',
            'installation': {
                'uuid': self.install.uuid,
            },
            'data': {
                'event': DictContaining(
                    event_id=event.event_id,
                    url=absolute_uri(reverse('sentry-api-0-project-event-details', args=[
                        self.organization.slug,
                        self.project.slug,
                        event.id,
                    ])),
                    web_url=absolute_uri(reverse('sentry-organization-event-detail', args=[
                        self.organization.slug,
                        group.id,
                        event.id,
                    ])),
                    issue_url=absolute_uri(
                        '/api/0/issues/{}/'.format(group.id),
                    ),
                ),
                'triggered_rule': self.rule.label,
            },
            'actor': {
                'type': 'application',
                'id': 'sentry',
                'name': 'Sentry',
            }
        }

        assert faux(safe_urlopen).kwarg_equals('headers', DictContaining(
            'Content-Type',
            'Request-ID',
            'Sentry-Hook-Resource',
            'Sentry-Hook-Timestamp',
            'Sentry-Hook-Signature',
        ))
Ejemplo n.º 8
0
    def handle(self, request, organization, team, project):
        token = None

        if request.method == 'POST':
            op = request.POST.get('op')
            if op == 'regenerate-token':
                token = self._regenerate_token(project)
                messages.add_message(
                    request, messages.SUCCESS,
                    OK_TOKEN_REGENERATED,
                )
            elif op == 'enable':
                self._handle_enable_plugin(request, project)
            elif op == 'disable':
                self._handle_disable_plugin(request, project)
            return HttpResponseRedirect(request.path)

        if token is None:
            token = ProjectOption.objects.get_value(project, 'sentry:release-token')
        if token is None:
            token = self._regenerate_token(project)

        enabled_plugins = []
        other_plugins = []
        for plugin in self._iter_plugins():
            if plugin.is_enabled(project):
                hook_url = absolute_uri(reverse('sentry-release-hook', kwargs={
                    'plugin_id': plugin.slug,
                    'project_id': project.id,
                    'signature': self._get_signature(project.id, plugin.slug, token),
                }))
                content = plugin.get_release_doc_html(hook_url=hook_url)
                enabled_plugins.append((plugin, mark_safe(content)))
            elif plugin.can_configure_for_project(project):
                other_plugins.append(plugin)

        context = {
            'page': 'release-tracking',
            'token': token,
            'enabled_plugins': enabled_plugins,
            'other_plugins': other_plugins,
            'webhook_url': absolute_uri(reverse('sentry-release-hook', kwargs={
                'plugin_id': 'builtin',
                'project_id': project.id,
                'signature': self._get_signature(project.id, 'builtin', token),
            }))
        }

        return self.respond('sentry/project-release-tracking.html', context)
    def get(self, request):
        org = Organization(
            id=1,
            slug='organization',
            name='My Company',
        )
        team = Team(
            id=1,
            slug='team',
            name='My Team',
            organization=org,
        )
        project = Project(
            id=1,
            organization=org,
            team=team,
            slug='project',
            name='My Project',
        )
        release = Release(
            project=project,
            version=sha1(uuid4().hex).hexdigest(),
        )

        release_link = absolute_uri(reverse('sentry-release-details', kwargs={
            'organization_slug': org.slug,
            'project_id': project.slug,
            'version': release.version,
        }))

        project_link = absolute_uri(reverse('sentry-stream', kwargs={
            'organization_slug': org.slug,
            'project_id': project.slug,
        }))

        preview = MailPreview(
            html_template='sentry/emails/activity/release.html',
            text_template='sentry/emails/activity/release.txt',
            context={
                'release': release,
                'project': project,
                'release_link': release_link,
                'project_link': project_link,
            },
        )

        return render_to_response('sentry/debug/mail/preview.html', {
            'preview': preview,
        })
Ejemplo n.º 10
0
    def get_absolute_url(self):
        # HACK(dcramer): quick and dirty way to support code/users
        if self.key == 'sentry:user':
            url_name = 'sentry-user-details'
        elif self.key == 'sentry:filename':
            url_name = 'sentry-explore-code-details'
        elif self.key == 'sentry:function':
            url_name = 'sentry-explore-code-details-by-function'
        else:
            url_name = 'sentry-explore-tag-value'
            return absolute_uri(reverse(url_name, args=[
                self.project.organization.slug, self.project.slug, self.key, self.id]))

        return absolute_uri(reverse(url_name, args=[
            self.project.organization.slug, self.project.slug, self.id]))
Ejemplo n.º 11
0
 def dispatch(self, request, pipeline):
     if 'completed_installation_guide' in request.GET:
         return pipeline.next_step()
     return render_to_response(
         template='sentry/integrations/gitlab-config.html',
         context={
             'next_url': '%s%s' % (absolute_uri('extensions/gitlab/setup/'), '?completed_installation_guide'),
             'setup_values': [
                 {'label': 'Name', 'value': 'Sentry'},
                 {'label': 'Redirect URI', 'value': absolute_uri('/extensions/gitlab/setup/')},
                 {'label': 'Scopes', 'value': 'api'}
             ]
         },
         request=request,
     )
Ejemplo n.º 12
0
 def get_invite_link(self):
     if not self.is_pending:
         return None
     return absolute_uri(reverse('sentry-accept-invite', kwargs={
         'member_id': self.id,
         'token': self.token or self.legacy_token,
     }))
Ejemplo n.º 13
0
    def check(self):
        # There is no queue, and celery is not running, so never show error
        if settings.CELERY_ALWAYS_EAGER:
            return []
        last_ping = options.get('sentry:last_worker_ping') or 0
        if last_ping >= time() - 300:
            return []

        backlogged, size = None, 0
        from sentry.monitoring.queues import backend
        if backend is not None:
            size = backend.get_size('default')
            backlogged = size > 0

        message = "Background workers haven't checked in recently. "
        if backlogged:
            message += "It seems that you have a backlog of %d tasks. Either your workers aren't running or you need more capacity." % size
        else:
            message += "This is likely an issue with your configuration or the workers aren't running."

        return [
            Problem(
                message,
                url=absolute_uri('/manage/queue/'),
            ),
        ]
Ejemplo n.º 14
0
    def test_inbound_status_sync_unresolve(self):
        responses.add(
            responses.GET,
            'https://instance.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workitemtypes/Bug/states',
            json=WORK_ITEM_STATES,
        )
        work_item_id = 33
        num_groups = 5
        external_issue = ExternalIssue.objects.create(
            organization_id=self.organization.id,
            integration_id=self.model.id,
            key=work_item_id,
        )
        groups = [
            self.create_linked_group(
                external_issue,
                self.project,
                GroupStatus.RESOLVED) for _ in range(num_groups)]

        # Change so that state is changing from resolved to unresolved
        work_item = self.set_workitem_state('Resolved', 'Active')

        with self.feature('organizations:integrations-issue-sync'):
            resp = self.client.post(
                absolute_uri('/extensions/vsts/issue-updated/'),
                data=work_item,
                HTTP_SHARED_SECRET=self.shared_secret,
            )
            assert resp.status_code == 200
            group_ids = [g.id for g in groups]
            assert len(
                Group.objects.filter(
                    id__in=group_ids,
                    status=GroupStatus.UNRESOLVED)) == num_groups
            assert len(Activity.objects.filter(group_id__in=group_ids)) == num_groups
Ejemplo n.º 15
0
def get_payload_v0(event):
    from sentry.api.serializers import serialize

    group = event.group
    project = group.project

    project_url_base = absolute_uri(u'/{}/{}'.format(
        project.organization.slug,
        project.slug,
    ))

    group_context = serialize(group)
    group_context['url'] = u'{}/issues/{}/'.format(
        project_url_base,
        group.id,
    )

    event_context = serialize(event)
    event_context['url'] = u'{}/issues/{}/events/{}/'.format(
        project_url_base,
        group.id,
        event.id,
    )
    data = {
        'project': {
            'slug': project.slug,
            'name': project.name,
        },
        'group': group_context,
        'event': event_context,
    }
    return data
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
 def get(self, request):
     return self.respond(
         {
             'key': BITBUCKET_KEY,
             'name': 'Sentry for Bitbucket',
             'description': 'A Sentry integration',
             'vendor': {
                 'name': 'Sentry.io',
                 'url': 'https://sentry.io/'
             },
             'baseUrl': absolute_uri(),
             'authentication': {
                 'type': 'JWT',
             },
             'lifecycle': {
                 'installed': '/extensions/bitbucket/installed/',
                 'uninstalled': '/extensions/bitbucket/uninstalled/'
             },
             'scopes': scopes,
             'contexts': ['account'],
             # When the user is redirected the URL will become:
             # https://sentry.io/extensions/bitbucket/setup/?jwt=1212121212
             'modules': {
                 'postInstallRedirect': {
                     'url': '/extensions/bitbucket/setup/',
                     'key': 'redirect'
                 }
             }
         }
     )
Ejemplo n.º 18
0
    def send_invite_email(self):
        from django.core.mail import send_mail
        from sentry.web.helpers import render_to_string

        context = {
            'email':
            self.email,
            'team':
            self.team,
            'url':
            absolute_uri(
                reverse(
                    'sentry-accept-invite',
                    kwargs={
                        'member_id': self.id,
                        'token': self.token,
                    })),
        }
        body = render_to_string('sentry/emails/member_invite.txt', context)

        try:
            send_mail(
                '%sInvite to join team: %s' % (settings.EMAIL_SUBJECT_PREFIX,
                                               self.team.name),
                body,
                settings.SERVER_EMAIL, [self.email],
                fail_silently=False)
        except Exception, e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)
Ejemplo n.º 19
0
    def serialize(self, obj, attrs, user):
        from sentry.api.endpoints.project_releases_token import _get_webhook_url
        doc = ''

        if self.project is not None:
            release_token = ProjectOption.objects.get_value(self.project, 'sentry:release-token')
            if release_token is not None:
                webhook_url = _get_webhook_url(self.project, obj.slug, release_token)

                if hasattr(obj, 'get_release_doc_html'):
                    try:
                        doc = obj.get_release_doc_html(webhook_url)
                    except NotImplementedError:
                        pass

        contexts = []
        if hasattr(obj, 'get_custom_contexts'):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            'id': obj.slug,
            'name': six.text_type(obj.get_title()),
            'slug': obj.slug or slugify(six.text_type(obj.get_title())),
            'shortName': six.text_type(obj.get_short_title()),
            'type': obj.get_plugin_type(),
            'canDisable': obj.can_disable,
            'isTestable': hasattr(obj, 'is_testable') and obj.is_testable(),
            'hasConfiguration': obj.has_project_conf(),
            'metadata': obj.get_metadata(),
            'contexts': contexts,
            'status': obj.get_status(),
            'assets': [
                {
                    'url': absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
                } for asset in obj.get_assets()
            ],
            'doc': doc,
        }
        if self.project:
            d['enabled'] = obj.is_enabled(self.project)

        if obj.version:
            d['version'] = six.text_type(obj.version)

        if obj.author:
            d['author'] = {
                'name': six.text_type(obj.author),
                'url': six.text_type(obj.author_url)
            }

        d['isHidden'] = d.get('enabled', False) is False and obj.is_hidden()

        if obj.description:
            d['description'] = six.text_type(obj.description)

        if obj.resource_links:
            d['resourceLinks'] = [
                {'title': title, 'url': url} for [title, url] in obj.resource_links
            ]

        return d
Ejemplo n.º 20
0
    def get(self, request, *args, **kwargs):
        try:
            # make sure this exists and is valid
            jira_auth = self.get_jira_auth()
        except (ApiError, JiraTenant.DoesNotExist, ExpiredSignatureError):
            return self.get_response('error.html')

        if request.user.is_anonymous():
            return self.get_response('signin.html')

        org = jira_auth.organization
        context = self.get_context()
        if org is None:
            context.update(
                {
                    'error_message': (
                        'You still need to configure this plugin, which '
                        'can be done from the Manage Add-ons page.'
                    )
                }
            )
            return self.get_response('error.html', context)

        context.update(
            {
                'sentry_api_url':
                absolute_uri('/api/0/organizations/%s/users/issues/' % (org.slug, )),
                'issue_key': self.request.GET.get('issueKey')
            }
        )

        return self.get_response('widget.html', context)
Ejemplo n.º 21
0
def request_access(request):
    org = Organization(
        id=1,
        slug='example',
        name='Example',
    )
    team = Team(
        id=1,
        slug='example',
        name='Example',
        organization=org,
    )

    return MailPreview(
        html_template='sentry/emails/request-team-access.html',
        text_template='sentry/emails/request-team-access.txt',
        context={
            'email': '*****@*****.**',
            'name': 'George Bush',
            'organization': org,
            'team': team,
            'url': absolute_uri(reverse('sentry-organization-members', kwargs={
                'organization_slug': org.slug,
            }) + '?ref=access-requests'),
        },
    ).render()
Ejemplo n.º 22
0
    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])
Ejemplo n.º 23
0
 def exchange_token(self, request, pipeline, code):
     # TODO: this needs the auth yet
     data = self.get_token_params(
         code=code,
         redirect_uri=absolute_uri(pipeline.redirect_url()),
     )
     verify_ssl = pipeline.config.get('verify_ssl', True)
     try:
         req = safe_urlopen(self.access_token_url, data=data, verify_ssl=verify_ssl)
         body = safe_urlread(req)
         if req.headers.get('Content-Type', '').startswith('application/x-www-form-urlencoded'):
             return dict(parse_qsl(body))
         return json.loads(body)
     except SSLError:
         logger.info('identity.oauth2.ssl-error', extra={
             'url': self.access_token_url,
             'verify_ssl': verify_ssl,
         })
         url = self.access_token_url
         return {
             'error': 'Could not verify SSL certificate',
             'error_description': u'Ensure that {} has a valid SSL certificate'.format(url)
         }
     except JSONDecodeError:
         logger.info('identity.oauth2.json-error', extra={
             'url': self.access_token_url,
         })
         return {
             'error': 'Could not decode a JSON Response',
             'error_description': u'We were not able to parse a JSON response, please try again.'
         }
Ejemplo n.º 24
0
    def send_recover_mail(self):
        from django.core.mail import send_mail
        from sentry.web.helpers import render_to_string

        context = {
            'user':
            self.user,
            'domain':
            urlparse.urlparse(settings.SENTRY_URL_PREFIX).hostname,
            'url':
            absolute_uri(
                reverse(
                    'sentry-account-recover-confirm',
                    args=[self.user.id, self.hash])),
        }
        body = render_to_string('sentry/emails/recover_account.txt', context)

        try:
            send_mail(
                '%sPassword Recovery' % (settings.EMAIL_SUBJECT_PREFIX, ),
                body,
                settings.SERVER_EMAIL, [self.user.email],
                fail_silently=False)
        except Exception, e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)
Ejemplo n.º 25
0
    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])
Ejemplo n.º 26
0
    def render(self, context):
        from sentry.utils.http import absolute_uri

        # Attempt to resolve all arguments into actual variables.
        # This converts a value such as `"foo"` into the string `foo`
        # and will look up a value such as `foo` from the context as
        # the variable `foo`. If the variable does not exist, silently
        # resolve as an empty string, which matches the behavior
        # `SimpleTagNode`
        args = []
        for arg in self.args:
            try:
                arg = template.Variable(arg).resolve(context)
            except template.VariableDoesNotExist:
                arg = ''
            args.append(arg)

        # No args is just fine
        if not args:
            rv = ''
        # If there's only 1 argument, there's nothing to format
        elif len(args) == 1:
            rv = args[0]
        else:
            rv = args[0].format(*args[1:])

        rv = absolute_uri(rv)

        # We're doing an `as foo` and we want to assign the result
        # to a variable instead of actually returning.
        if self.target_var is not None:
            context[self.target_var] = rv
            rv = ''

        return rv
Ejemplo n.º 27
0
    def get_description(self):
        data = self.activity.data

        if features.has('organizations:sentry10', self.organization):
            url = u'/organizations/{}/releases/{}/?project={}'.format(
                self.organization.slug,
                data['version'],
                self.project.id,
            )
        else:
            url = u'/{}/{}/releases/{}/'.format(
                self.organization.slug,
                self.project.slug,
                data['version'],
            )

        if data.get('version'):
            return u'{author} marked {an issue} as resolved in {version}', {
                'version': data['version'],
            }, {
                'version':
                u'<a href="{}">{}</a>'.format(
                    absolute_uri(url),
                    escape(data['version']),
                )
            }
        return u'{author} marked {an issue} as resolved in an upcoming release'
Ejemplo n.º 28
0
    def test_inbound_status_sync_new_workitem(self):
        responses.add(
            responses.GET,
            'https://instance.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workitemtypes/Bug/states',
            json=WORK_ITEM_STATES,
        )
        work_item_id = 33
        external_issue = ExternalIssue.objects.create(
            organization_id=self.organization.id,
            integration_id=self.model.id,
            key=work_item_id,
        )

        group = self.create_linked_group(
            external_issue,
            self.project,
            GroupStatus.UNRESOLVED)

        # Change so that it is a new workitem
        work_item = self.set_workitem_state(None, 'New')
        assert 'oldValue' not in work_item['resource']['fields']['System.State']

        with self.feature('organizations:integrations-issue-sync'):
            resp = self.client.post(
                absolute_uri('/extensions/vsts/issue-updated/'),
                data=work_item,
                HTTP_SHARED_SECRET=self.shared_secret,
            )
            assert resp.status_code == 200
            assert Group.objects.get(id=group.id).status == GroupStatus.UNRESOLVED
            # no change happened. no activity should be created here
            assert len(Activity.objects.filter(group_id=group.id)) == 0
Ejemplo n.º 29
0
    def handle(self, request, organization):
        team_list = [
            t for t in Team.objects.get_for_user(
                organization=organization,
                user=request.user,
            )
            if request.access.has_team_scope(t, self.required_scope)
        ]
        if not team_list:
            messages.error(request, ERR_NO_TEAMS)
            return self.redirect(reverse('sentry-organization-home', args=[organization.slug]))

        form = self.get_form(request, organization, team_list)
        if form.is_valid():
            project = form.save(request.user, request.META['REMOTE_ADDR'])

            install_uri = absolute_uri('/{}/{}/settings/install/'.format(
                organization.slug,
                project.slug,
            ))

            if 'signup' in request.GET:
                install_uri += '?signup'

            return self.redirect(install_uri)

        context = {
            'form': form,
        }

        return self.respond('sentry/create-project.html', context)
Ejemplo n.º 30
0
    def get_context(self):
        file_count = CommitFileChange.objects.filter(
            commit__in=self.commit_list,
            organization_id=self.organization.id,
        ).values('filename').distinct().count()

        return {
            'commit_count':
            len(self.commit_list),
            'author_count':
            len(self.email_list),
            'file_count':
            file_count,
            'repos':
            self.repos,
            'release':
            self.release,
            'deploy':
            self.deploy,
            'environment':
            self.environment,
            'setup_repo_link':
            absolute_uri('/organizations/{}/repos/'.format(
                self.organization.slug,
            )),
        }
Ejemplo n.º 31
0
 def get_absolute_url(self):
     return absolute_uri(reverse('sentry', args=[self.slug]))
Ejemplo n.º 32
0
def build_group_footer(group, rules, project, event):
    # TODO: implement with event as well
    image_column = {
        "type":
        "Column",
        "items": [{
            "type":
            "Image",
            "url":
            absolute_uri(
                get_asset_url("sentry", "images/sentry-glyph-black.png")),
            "height":
            "20px",
        }],
        "width":
        "auto",
    }

    text = f"{group.qualified_short_id}"
    if rules:
        rule_url = build_rule_url(rules[0], group, project)
        text += f" via [{rules[0].label}]({rule_url})"
        if len(rules) > 1:
            text += f" (+{len(rules) - 1} other)"

    text_column = {
        "type":
        "Column",
        "items": [{
            "type": "TextBlock",
            "size": "Small",
            "weight": "Lighter",
            "text": text
        }],
        "isSubtle":
        True,
        "width":
        "auto",
        "spacing":
        "none",
    }

    date_ts = group.last_seen
    if event:
        event_ts = event.datetime
        date_ts = max(date_ts, event_ts)

    date = date_ts.replace(microsecond=0).isoformat()
    date_text = f"{{{{DATE({date}, SHORT)}}}} at {{{{TIME({date})}}}}"
    date_column = {
        "type":
        "Column",
        "items": [{
            "type": "TextBlock",
            "size": "Small",
            "weight": "Lighter",
            "horizontalAlignment": "Center",
            "text": date_text,
        }],
        "width":
        "auto",
    }

    return {
        "type": "ColumnSet",
        "columns": [image_column, text_column, date_column]
    }
Ejemplo n.º 33
0
 def send_welcome_message(self, conversation_id, signed_params):
     url = u"%s?signed_params=%s" % (
         absolute_uri("/extensions/msteams/configure/"),
         signed_params,
     )
     # TODO: Refactor message creation
     logo = {
         "type": "Image",
         "url": "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png",
         "size": "Medium",
     }
     welcome = {
         "type": "TextBlock",
         "weight": "Bolder",
         "size": "Large",
         "text": "Welcome to Sentry for Microsoft Teams",
         "wrap": True,
     }
     description = {
         "type": "TextBlock",
         "text": "You can use the Sentry app for Microsoft Teams to get notifications that allow you to assign, ignore, or resolve directly in your chat.",
         "wrap": True,
     }
     instruction = {
         "type": "TextBlock",
         "text": "If that sounds good to you, finish the setup process.",
         "wrap": True,
     }
     button = {
         "type": "Action.OpenUrl",
         "title": "Complete Setup",
         "url": url,
     }
     card = {
         "type": "AdaptiveCard",
         "body": [
             {
                 "type": "ColumnSet",
                 "columns": [
                     {"type": "Column", "items": [logo], "width": "auto"},
                     {
                         "type": "Column",
                         "items": [welcome],
                         "width": "stretch",
                         "verticalContentAlignment": "Center",
                     },
                 ],
             },
             description,
             instruction,
         ],
         "actions": [button],
         "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
         "version": "1.2",
     }
     payload = {
         "type": "message",
         "attachments": [
             {"contentType": "application/vnd.microsoft.card.adaptive", "content": card}
         ],
     }
     self.send_message(conversation_id, payload)
Ejemplo n.º 34
0
def build_attachment(group,
                     event=None,
                     tags=None,
                     identity=None,
                     actions=None,
                     rules=None):
    # XXX(dcramer): options are limited to 100 choices, even when nested
    status = group.get_status()

    members = get_member_assignees(group)
    teams = get_team_assignees(group)

    logo_url = absolute_uri(
        get_asset_url('sentry', 'images/sentry-email-avatar.png'))
    color = LEVEL_TO_COLOR.get(event.get_tag('level'),
                               'error') if event else LEVEL_TO_COLOR['error']

    text = build_attachment_text(group, event) or ''

    if actions is None:
        actions = []

    assignee = get_assignee(group)

    resolve_button = {
        'name': 'resolve_dialog',
        'value': 'resolve_dialog',
        'type': 'button',
        'text': 'Resolve...',
    }

    ignore_button = {
        'name': 'status',
        'value': 'ignored',
        'type': 'button',
        'text': 'Ignore',
    }

    has_releases = Release.objects.filter(
        projects=group.project,
        organization_id=group.project.organization_id).exists()

    if not has_releases:
        resolve_button.update({
            'name': 'status',
            'text': 'Resolve',
            'value': 'resolved',
        })

    if status == GroupStatus.RESOLVED:
        resolve_button.update({
            'name': 'status',
            'text': 'Unresolve',
            'value': 'unresolved',
        })

    if status == GroupStatus.IGNORED:
        ignore_button.update({
            'text': 'Stop Ignoring',
            'value': 'unresolved',
        })

    option_groups = []

    if teams:
        option_groups.append({
            'text': 'Teams',
            'options': teams,
        })

    if members:
        option_groups.append({
            'text': 'People',
            'options': members,
        })

    payload_actions = [
        resolve_button,
        ignore_button,
        {
            'name': 'assign',
            'text': 'Select Assignee...',
            'type': 'select',
            'selected_options': [assignee],
            'option_groups': option_groups,
        },
    ]

    fields = []

    if tags:
        event_tags = event.tags if event else group.get_latest_event().tags

        for key, value in event_tags:
            std_key = tagstore.get_standardized_key(key)
            if std_key not in tags:
                continue

            labeled_value = tagstore.get_tag_value_label(key, value)
            fields.append({
                'title': std_key.encode('utf-8'),
                'value': labeled_value.encode('utf-8'),
                'short': True,
            })

    if actions:
        action_texts = filter(
            None, [build_action_text(group, identity, a) for a in actions])
        text += '\n' + '\n'.join(action_texts)

        color = ACTIONED_ISSUE_COLOR
        payload_actions = []

    ts = group.last_seen

    if event:
        event_ts = event.datetime
        ts = max(ts, event_ts)

    footer = u'{}'.format(group.qualified_short_id)

    if rules:
        footer += u' via {}'.format(rules[0].label)

        if len(rules) > 1:
            footer += u' (+{} other)'.format(len(rules) - 1)

    return {
        'fallback': u'[{}] {}'.format(group.project.slug, group.title),
        'title': build_attachment_title(group, event),
        'title_link': group.get_absolute_url(params={'referrer': 'slack'}),
        'text': text,
        'fields': fields,
        'mrkdwn_in': ['text'],
        'callback_id': json.dumps({'issue': group.id}),
        'footer_icon': logo_url,
        'footer': footer,
        'ts': to_timestamp(ts),
        'color': color,
        'actions': payload_actions,
    }
Ejemplo n.º 35
0
 def get_absolute_url(self):
     return absolute_uri(
         reverse('sentry-group',
                 args=[self.team.slug, self.project.slug, self.id]))
Ejemplo n.º 36
0
def build_attachment(group,
                     event=None,
                     tags=None,
                     identity=None,
                     actions=None,
                     rules=None):
    # XXX(dcramer): options are limited to 100 choices, even when nested
    status = group.get_status()
    assignees = get_assignees(group)

    logo_url = absolute_uri(
        get_asset_url('sentry', 'images/sentry-email-avatar.png'))
    color = NEW_ISSUE_COLOR

    text = build_attachment_text(group, event) or ''

    if actions is None:
        actions = []

    try:
        assignee = GroupAssignee.objects.get(group=group).user
        assignee = {
            'text': assignee.get_display_name(),
            'value': assignee.username,
        }

        # Add unassign option to the top of the list
        assignees.insert(0, UNASSIGN_OPTION)
    except GroupAssignee.DoesNotExist:
        assignee = None

    resolve_button = {
        'name': 'resolve_dialog',
        'value': 'resolve_dialog',
        'type': 'button',
        'text': 'Resolve...',
    }

    ignore_button = {
        'name': 'status',
        'value': 'ignored',
        'type': 'button',
        'text': 'Ignore',
    }

    if status == GroupStatus.RESOLVED:
        resolve_button.update({
            'name': 'status',
            'text': 'Unresolve',
            'value': 'unresolved',
        })

    if status == GroupStatus.IGNORED:
        ignore_button.update({
            'text': 'Stop Ignoring',
            'value': 'unresolved',
        })

    payload_actions = [
        resolve_button,
        ignore_button,
        {
            'name': 'assign',
            'text': 'Select Assignee...',
            'type': 'select',
            'options': assignees,
            'selected_options': [assignee],
        },
    ]

    fields = []

    if tags:
        event_tags = event.tags if event else group.get_latest_event().tags

        for tag_key, tag_value in event_tags:
            if tag_key in tags:
                fields.append({
                    'title': tag_key.encode('utf-8'),
                    'value': tag_value.encode('utf-8'),
                    'short': True,
                })

    if actions:
        action_texts = filter(
            None, [build_action_text(identity, a) for a in actions])
        text += '\n' + '\n'.join(action_texts)

        color = ACTIONED_ISSUE_COLOR
        payload_actions = []

    ts = group.last_seen

    if event:
        event_ts = event.datetime
        ts = max(ts, event_ts)

    footer = u'{}'.format(group.qualified_short_id)

    if rules:
        footer += u' via {}'.format(rules[0].label)

        if len(rules) > 1:
            footer += u' (+{} other)'.format(len(rules) - 1)

    return {
        'fallback':
        u'[{}] {}'.format(group.project.slug, group.title),
        'title':
        build_attachment_title(group, event),
        'title_link':
        add_notification_referrer_param(group.get_absolute_url(), 'slack'),
        'text':
        text,
        'fields':
        fields,
        'mrkdwn_in': ['text'],
        'callback_id':
        json.dumps({'issue': group.id}),
        'footer_icon':
        logo_url,
        'footer':
        footer,
        'ts':
        to_timestamp(ts),
        'color':
        color,
        'actions':
        payload_actions,
    }
Ejemplo n.º 37
0
    def post(self, request):
        if not request.META.get("HTTP_X_ZEIT_SIGNATURE"):
            logger.error("vercel.webhook.missing-signature")
            self.respond(status=401)

        is_valid = verify_signature(request)

        if not is_valid:
            logger.error("vercel.webhook.invalid-signature")
            return self.respond(status=401)

        data = request.data
        payload = data["payload"]
        external_id = data.get("teamId") or data["userId"]
        vercel_project_id = payload["projectId"]

        logging_params = {
            "external_id": external_id,
            "vercel_project_id": vercel_project_id
        }

        if payload["target"] != "production":
            logger.info("Ignoring deployment for environment: %s" %
                        payload["target"],
                        extra=logging_params)
            return self.respond(status=204)

        # Steps:
        # 1. Find all org integrations that match the external id
        # 2. Search the configs to find one that matches the vercel project of the webhook
        # 3. Look up the Sentry project that matches
        # 4. Look up the connected internal integration
        # 5. Find the token associated with that installation
        # 6. Determine the commit sha and repo based on what provider is used
        # 7. Create the release using the token WITHOUT refs
        # 8. Update the release with refs

        # find all org integrations that match the external id
        org_integrations = OrganizationIntegration.objects.select_related(
            "organization").filter(integration__external_id=external_id,
                                   integration__provider=self.provider)
        if not org_integrations:
            logger.info("Integration not found", extra=logging_params)
            return self.respond({"detail": "Integration not found"},
                                status=404)

        # for each org integration, search the configs to find one that matches the vercel project of the webhook
        for org_integration in org_integrations:
            project_mappings = org_integration.config.get(
                "project_mappings") or []
            matched_mappings = filter(lambda x: x[1] == vercel_project_id,
                                      project_mappings)
            if matched_mappings:
                organization = org_integration.organization
                sentry_project_id = matched_mappings[0][0]

                logging_params["organization_id"] = organization.id
                logging_params["project_id"] = sentry_project_id

                try:
                    [release_payload, token
                     ] = self.get_payload_and_token(payload, organization.id,
                                                    sentry_project_id)
                except Project.DoesNotExist:
                    logger.info("Project not found", extra=logging_params)
                    return self.respond({"detail": "Project not found"},
                                        status=404)
                except SentryAppInstallationForProvider.DoesNotExist:
                    logger.info("Installation not found", extra=logging_params)
                    return self.respond({"detail": "Installation not found"},
                                        status=404)
                except SentryAppInstallationToken.DoesNotExist:
                    logger.info("Token not found", extra=logging_params)
                    return self.respond({"detail": "Token not found"},
                                        status=404)
                except NoCommitFoundError:
                    logger.info("No commit found", extra=logging_params)
                    return self.respond({"detail": "No commit found"},
                                        status=404)

                session = http.build_session()
                url = absolute_uri("/api/0/organizations/%s/releases/" %
                                   organization.slug)
                headers = {
                    "Accept": "application/json",
                    "Authorization": "Bearer %s" % token,
                    "User-Agent": "sentry_vercel/{}".format(VERSION),
                }
                json_error = None

                # create the basic release payload without refs
                no_ref_payload = release_payload.copy()
                del no_ref_payload["refs"]
                try:
                    resp = session.post(url,
                                        json=no_ref_payload,
                                        headers=headers)
                    json_error = safe_json_parse(resp)
                    resp.raise_for_status()
                except RequestException as e:
                    # errors here should be uncommon but we should be aware of them
                    logger.error(
                        "Error creating release: %s - %s" % (e, json_error),
                        extra=logging_params,
                        exc_info=True,
                    )
                    # 400 probably isn't the right status code but oh well
                    return self.respond(
                        {"detail": "Error creating release: %s" % e},
                        status=400)

                # set the refs
                try:
                    resp = session.post(
                        url,
                        json=release_payload,
                        headers=headers,
                    )
                    json_error = safe_json_parse(resp)
                    resp.raise_for_status()
                except RequestException as e:
                    # errors will probably be common if the user doesn't have repos set up
                    logger.info(
                        "Error setting refs: %s - %s" % (e, json_error),
                        extra=logging_params,
                        exc_info=True,
                    )
                    # 400 probably isn't the right status code but oh well
                    return self.respond(
                        {"detail": "Error setting refs: %s" % e}, status=400)

                # we are going to quit after the first project match as there shouldn't be multiple matches
                return self.respond(status=201)
        return self.respond(status=204)
Ejemplo n.º 38
0
    def handle(self, request, organization, team, project):
        token = None

        if request.method == 'POST':
            op = request.POST.get('op')
            if op == 'regenerate-token':
                token = self._regenerate_token(project)
                messages.add_message(
                    request,
                    messages.SUCCESS,
                    OK_TOKEN_REGENERATED,
                )
            elif op == 'enable':
                self._handle_enable_plugin(request, project)
            elif op == 'disable':
                self._handle_disable_plugin(request, project)
            return HttpResponseRedirect(request.path)

        if token is None:
            token = ProjectOption.objects.get_value(project,
                                                    'sentry:release-token')
        if token is None:
            token = self._regenerate_token(project)

        enabled_plugins = []
        other_plugins = []
        for plugin in self._iter_plugins():
            if plugin.is_enabled(project):
                hook_url = absolute_uri(
                    reverse('sentry-release-hook',
                            kwargs={
                                'plugin_id':
                                plugin.slug,
                                'project_id':
                                project.id,
                                'signature':
                                self._get_signature(project.id, plugin.slug,
                                                    token),
                            }))
                content = plugin.get_release_doc_html(hook_url=hook_url)
                enabled_plugins.append((plugin, mark_safe(content)))
            elif plugin.can_configure_for_project(project):
                other_plugins.append(plugin)

        context = {
            'page':
            'release-tracking',
            'token':
            token,
            'enabled_plugins':
            enabled_plugins,
            'other_plugins':
            other_plugins,
            'webhook_url':
            absolute_uri(
                reverse('sentry-release-hook',
                        kwargs={
                            'plugin_id':
                            'builtin',
                            'project_id':
                            project.id,
                            'signature':
                            self._get_signature(project.id, 'builtin', token),
                        }))
        }

        return self.respond('sentry/project-release-tracking.html', context)
Ejemplo n.º 39
0
    def notify(self, notification, raise_exception=False):
        event = notification.event
        group = event.group
        project = group.project

        if not self.is_configured(project):
            return

        title = event.title.encode("utf-8")
        # TODO(dcramer): we'd like this to be the event culprit, but Sentry
        # does not currently retain it
        if group.culprit:
            culprit = group.culprit.encode("utf-8")
        else:
            culprit = None
        project_name = project.get_full_name().encode("utf-8")

        fields = []

        # They can be the same if there is no culprit
        # So we set culprit to an empty string instead of duplicating the text
        if not self.get_option("exclude_culprit",
                               project) and culprit and title != culprit:
            fields.append({
                "title": "Culprit",
                "value": culprit,
                "short": False
            })
        if not self.get_option("exclude_project", project):
            fields.append({
                "title": "Project",
                "value": project_name,
                "short": True
            })

        if self.get_option("custom_message", project):
            fields.append({
                "title": "Custom message",
                "value": self.get_option("custom_message", project),
                "short": False,
            })

        if self.get_option("include_rules", project):
            rules = []
            for rule in notification.rules:
                rule_link = "/%s/%s/settings/alerts/rules/%s/" % (
                    group.organization.slug,
                    project.slug,
                    rule.id,
                )

                # Make sure it's an absolute uri since we're sending this
                # outside of Sentry into Slack
                rule_link = absolute_uri(rule_link)
                rules.append((rule_link, rule.label))

            if rules:
                value = ", ".join("<{} | {}>".format(*r) for r in rules)

                fields.append({
                    "title": "Triggered By",
                    "value": value.encode("utf-8"),
                    "short": False
                })

        if self.get_option("include_tags", project):
            included_tags = set(
                self.get_tag_list("included_tag_keys", project) or [])
            excluded_tags = set(
                self.get_tag_list("excluded_tag_keys", project) or [])
            for tag_key, tag_value in self._get_tags(event):
                key = tag_key.lower()
                std_key = tagstore.get_standardized_key(key)
                if included_tags and key not in included_tags and std_key not in included_tags:
                    continue
                if excluded_tags and (key in excluded_tags
                                      or std_key in excluded_tags):
                    continue
                fields.append({
                    "title": tag_key.encode("utf-8"),
                    "value": tag_value.encode("utf-8"),
                    "short": True,
                })
        payload = {
            "attachments": [{
                "fallback":
                b"[%s] %s" % (project_name, title),
                "title":
                title,
                "title_link":
                group.get_absolute_url(params={"referrer": "slack"}),
                "color":
                self.color_for_event(event),
                "fields":
                fields,
            }]
        }
        client = self.get_client(project)

        if client.username:
            payload["username"] = client.username.encode("utf-8")

        if client.channel:
            payload["channel"] = client.channel

        if client.icon_url:
            payload["icon_url"] = client.icon_url

        values = {"payload": json.dumps(payload)}
        client.request(values)
Ejemplo n.º 40
0
 def get_absolute_url(self):
     return absolute_uri(reverse('sentry-alert-details', args=[
         self.team.slug, self.project.slug, self.id]))
Ejemplo n.º 41
0
 def get(self, request):
     sentry_logo = absolute_uri(
         get_asset_url("sentry", "images/logos/logo-sentry.svg"))
     return self.respond({
         "name": "Sentry",
         "description": "Sentry",
         "key": JIRA_KEY,
         "baseUrl": absolute_uri(),
         "vendor": {
             "name": "Sentry",
             "url": "https://sentry.io"
         },
         "authentication": {
             "type": "jwt"
         },
         "lifecycle": {
             "installed": "/extensions/jira/installed/",
             "uninstalled": "/extensions/jira/uninstalled/",
         },
         "apiVersion": 1,
         "modules": {
             "postInstallPage": {
                 "url": "/extensions/jira/ui-hook",
                 "name": {
                     "value": "Configure Sentry Add-on"
                 },
                 "key": "post-install-sentry",
             },
             "configurePage": {
                 "url": "/extensions/jira/ui-hook",
                 "name": {
                     "value": "Configure Sentry Add-on"
                 },
                 "key": "configure-sentry",
             },
             "jiraIssueGlances": [{
                 "icon": {
                     "width": 24,
                     "height": 24,
                     "url": sentry_logo
                 },
                 "content": {
                     "type": "label",
                     "label": {
                         "value": "Linked Issues"
                     }
                 },
                 "target": {
                     "type": "web_panel",
                     "url": "/extensions/jira/issue/{issue.key}/",
                 },
                 "name": {
                     "value": "Sentry "
                 },
                 "key": "sentry-issues-glance",
             }],
             "webhooks": [{
                 "event":
                 "jira:issue_updated",
                 "url":
                 reverse("sentry-extensions-jira-issue-updated"),
                 "excludeBody":
                 False,
             }],
         },
         "apiMigrations": {
             "gdpr": True
         },
         "scopes": scopes,
     })
Ejemplo n.º 42
0
def get_settings_url(notification: BaseNotification) -> str:
    return urljoin(absolute_uri("/settings/account/notifications/"),
                   get_referrer_qstring(notification))
Ejemplo n.º 43
0
def build_rule_url(rule, group, project):
    org_slug = group.organization.slug
    project_slug = project.slug
    rule_url = f"/organizations/{org_slug}/alerts/rules/{project_slug}/{rule.id}/"
    return absolute_uri(rule_url)
Ejemplo n.º 44
0
 def get_absolute_url(self):
     return absolute_uri(
         reverse('sentry-stream', args=[self.team.slug, self.slug]))
Ejemplo n.º 45
0
 def u2f_app_id(cls):
     rv = options.get('u2f.app-id')
     return rv or absolute_uri(reverse('sentry-u2f-app-id'))
Ejemplo n.º 46
0
 def get_absolute_url(self):
     return absolute_uri('/{}/{}/'.format(self.organization.slug,
                                          self.slug))
Ejemplo n.º 47
0
def build_welcome_card(signed_params):
    url = "{}?signed_params={}".format(
        absolute_uri("/extensions/msteams/configure/"),
        signed_params,
    )
    welcome = {
        "type": "TextBlock",
        "weight": "Bolder",
        "size": "Large",
        "text": "Welcome to Sentry for Microsoft Teams",
        "wrap": True,
    }
    description = {
        "type": "TextBlock",
        "text":
        "You can use Sentry for Microsoft Teams to get notifications that allow you to assign, ignore, or resolve directly in your chat.",
        "wrap": True,
    }
    instruction = {
        "type":
        "TextBlock",
        "text":
        ("Please click **Complete Setup** to finish the setup process."
         " Don't have a Sentry account? [Sign Up](https://sentry.io/signup/)."
         ),
        "wrap":
        True,
    }
    button = {
        "type": "Action.OpenUrl",
        "title": "Complete Setup",
        "url": url,
    }
    return {
        "type":
        "AdaptiveCard",
        "body": [
            {
                "type":
                "ColumnSet",
                "columns": [
                    {
                        "type": "Column",
                        "items": [logo],
                        "width": "auto"
                    },
                    {
                        "type": "Column",
                        "items": [welcome],
                        "width": "stretch",
                        "verticalContentAlignment": "Center",
                    },
                ],
            },
            description,
            instruction,
        ],
        "actions": [button],
        "$schema":
        "http://adaptivecards.io/schemas/adaptive-card.json",
        "version":
        "1.2",
    }
Ejemplo n.º 48
0
    def serialize(self, obj, attrs, user):
        status = obj.status
        status_details = {}
        if attrs['ignore_until']:
            snooze = attrs['ignore_until']
            if snooze.is_valid(group=obj):
                # counts return the delta remaining when window is not set
                status_details.update({
                    'ignoreCount':
                    (snooze.count -
                     (obj.times_seen - snooze.state['times_seen'])
                     if snooze.count and not snooze.window else snooze.count),
                    'ignoreUntil':
                    snooze.until,
                    'ignoreUserCount':
                    (snooze.user_count -
                     (attrs['user_count'] - snooze.state['users_seen'])
                     if snooze.user_count and not snooze.user_window else
                     snooze.user_count),
                    'ignoreUserWindow':
                    snooze.user_window,
                    'ignoreWindow':
                    snooze.window,
                    'actor':
                    attrs['ignore_actor'],
                })
            else:
                status = GroupStatus.UNRESOLVED
        if status == GroupStatus.UNRESOLVED and obj.is_over_resolve_age():
            status = GroupStatus.RESOLVED
            status_details['autoResolved'] = True
        if status == GroupStatus.RESOLVED:
            status_label = 'resolved'
            if attrs['resolution_type'] == 'release':
                res_type, res_version, _ = attrs['resolution']
                if res_type in (GroupResolution.Type.in_next_release, None):
                    status_details['inNextRelease'] = True
                elif res_type == GroupResolution.Type.in_release:
                    status_details['inRelease'] = res_version
                status_details['actor'] = attrs['resolution_actor']
            elif attrs['resolution_type'] == 'commit':
                status_details['inCommit'] = attrs['resolution']
        elif status == GroupStatus.IGNORED:
            status_label = 'ignored'
        elif status in [
                GroupStatus.PENDING_DELETION, GroupStatus.DELETION_IN_PROGRESS
        ]:
            status_label = 'pending_deletion'
        elif status == GroupStatus.PENDING_MERGE:
            status_label = 'pending_merge'
        else:
            status_label = 'unresolved'

        # If user is not logged in and member of the organization,
        # do not return the permalink which contains private information i.e. org name.
        if user.is_authenticated() and user.get_orgs().filter(
                id=obj.organization.id).exists():
            permalink = absolute_uri(
                reverse('sentry-group',
                        args=[obj.organization.slug, obj.project.slug,
                              obj.id]))
        else:
            permalink = None

        subscription_details = None
        if attrs['subscription'] is not disabled:
            is_subscribed, subscription = attrs['subscription']
            if subscription is not None and subscription.is_active:
                subscription_details = {
                    'reason':
                    SUBSCRIPTION_REASON_MAP.get(
                        subscription.reason,
                        'unknown',
                    ),
                }
        else:
            is_subscribed = False
            subscription_details = {
                'disabled': True,
            }

        share_id = attrs['share_id']

        return {
            'id': six.text_type(obj.id),
            'shareId': share_id,
            'shortId': obj.qualified_short_id,
            'count': six.text_type(attrs['times_seen']),
            'userCount': attrs['user_count'],
            'title': obj.title,
            'culprit': obj.culprit,
            'permalink': permalink,
            'firstSeen': attrs['first_seen'],
            'lastSeen': attrs['last_seen'],
            'logger': obj.logger or None,
            'level': LOG_LEVELS.get(obj.level, 'unknown'),
            'status': status_label,
            'statusDetails': status_details,
            'isPublic': share_id is not None,
            'project': {
                'id': six.text_type(obj.project.id),
                'name': obj.project.name,
                'slug': obj.project.slug,
            },
            'type': obj.get_event_type(),
            'metadata': obj.get_event_metadata(),
            'numComments': obj.num_comments,
            'assignedTo': serialize(attrs['assigned_to'], user,
                                    ActorSerializer()),
            'isBookmarked': attrs['is_bookmarked'],
            'isSubscribed': is_subscribed,
            'subscriptionDetails': subscription_details,
            'hasSeen': attrs['has_seen'],
            'annotations': attrs['annotations'],
        }
Ejemplo n.º 49
0
from typing import Optional

from sentry.integrations.metric_alerts import incident_attachment_info
from sentry.models import Group, GroupStatus, Project, Team, User
from sentry.utils.assets import get_asset_url
from sentry.utils.compat import map
from sentry.utils.http import absolute_uri

from .utils import ACTION_TYPE

ME = "ME"
logo = {
    "type": "Image",
    "url":
    absolute_uri(get_asset_url("sentry", "images/sentry-glyph-black.png")),
    "size": "Medium",
}


def generate_action_payload(action_type, event, rules, integration):
    rule_ids = map(lambda x: x.id, rules)
    # we need nested data or else Teams won't handle the payload correctly
    return {
        "payload": {
            "actionType": action_type,
            "groupId": event.group.id,
            "eventId": event.event_id,
            "rules": rule_ids,
            "integrationId": integration.id,
        }
    }
Ejemplo n.º 50
0
 def test_without_path(self):
     assert absolute_uri() == options.get('system.url-prefix')
Ejemplo n.º 51
0
def get_settings_url(notification: BaseNotification) -> str:
    url_str = f"/settings/account/notifications/{notification.fine_tuning_key}"
    return str(
        urljoin(absolute_uri(url_str), get_referrer_qstring(notification)))
Ejemplo n.º 52
0
 def get(self, request: Request) -> Response:
     sentry_logo = absolute_uri(
         get_asset_url("sentry", "images/logos/logo-sentry.svg"))
     return self.respond({
         "name": "Sentry",
         "description":
         "Connect your Sentry organization into one or more of your Jira cloud instances. Get started streamlining your bug squashing workflow by unifying your Sentry and Jira instances together.",
         "key": JIRA_KEY,
         "baseUrl": absolute_uri(),
         "vendor": {
             "name": "Sentry",
             "url": "https://sentry.io"
         },
         "authentication": {
             "type": "jwt"
         },
         "lifecycle": {
             "installed": "/extensions/jira/installed/",
             "uninstalled": "/extensions/jira/uninstalled/",
         },
         "apiVersion": 1,
         "modules": {
             "postInstallPage": {
                 "url": "/extensions/jira/ui-hook",
                 "name": {
                     "value": "Configure Sentry Add-on"
                 },
                 "key": "post-install-sentry",
             },
             "configurePage": {
                 "url": "/extensions/jira/ui-hook",
                 "name": {
                     "value": "Configure Sentry Add-on"
                 },
                 "key": "configure-sentry",
             },
             "jiraIssueGlances": [{
                 "icon": {
                     "width": 24,
                     "height": 24,
                     "url": sentry_logo
                 },
                 "content": {
                     "type": "label",
                     "label": {
                         "value": "Linked Issues"
                     }
                 },
                 "target": {
                     "type": "web_panel",
                     "url": "/extensions/jira/issue/{issue.key}/",
                 },
                 "name": {
                     "value": "Sentry "
                 },
                 "key": "sentry-issues-glance",
             }],
             "webhooks": [{
                 "event":
                 "jira:issue_updated",
                 "url":
                 reverse("sentry-extensions-jira-issue-updated"),
                 "excludeBody":
                 False,
             }],
         },
         "apiMigrations": {
             "gdpr": True,
             "context-qsh": True,
             "signed-install": True
         },
         "scopes": scopes,
     })
Ejemplo n.º 53
0
 def test_with_path(self):
     assert absolute_uri(
         '/foo/bar') == '%s/foo/bar' % (options.get('system.url-prefix'), )
Ejemplo n.º 54
0
 def get_project_url(self, project):
     return absolute_uri(reverse('sentry-stream', args=[
         project.organization.slug,
         project.slug,
     ]))
Ejemplo n.º 55
0
    def notify_about_activity(self, activity):
        if activity.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE):
            return

        candidate_ids = set(self.get_send_to(activity.project))

        # Never send a notification to the user that performed the action.
        candidate_ids.discard(activity.user_id)

        if activity.type == Activity.ASSIGNED:
            # Only notify the assignee, and only if they are in the candidate set.
            recipient_ids = candidate_ids & set(map(int, (activity.data['assignee'],)))
        elif activity.type == Activity.NOTE:
            recipient_ids = candidate_ids - set(
                UserOption.objects.filter(
                    user__in=candidate_ids,
                    key='subscribe_notes',
                    value=u'0',
                ).values_list('user', flat=True)
            )
        else:
            recipient_ids = candidate_ids

        if not recipient_ids:
            return

        project = activity.project
        org = project.organization
        group = activity.group

        headers = {}

        context = {
            'data': activity.data,
            'author': activity.user,
            'project': project,
            'project_link': absolute_uri(reverse('sentry-stream', kwargs={
                'organization_slug': org.slug,
                'project_id': project.slug,
            })),
        }

        if group:
            group_link = absolute_uri('/{}/{}/issues/{}/'.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 activity.type == Activity.RELEASE:
            context.update({
                'release': Release.objects.get(
                    version=activity.data['version'],
                    project=project,
                ),
                'release_link': absolute_uri('/{}/{}/releases/{}/'.format(
                    org.slug,
                    project.slug,
                    activity.data['version'],
                )),
            })

        template_name = activity.get_type_display()

        # TODO: Everything below should instead use `_send_mail` for consistency.
        subject_prefix = project.get_option('subject_prefix', settings.EMAIL_SUBJECT_PREFIX)
        if subject_prefix:
            subject_prefix = subject_prefix.rstrip() + ' '

        if group:
            subject = '%s%s' % (subject_prefix, group.get_email_subject())
        elif activity.type == Activity.RELEASE:
            subject = '%sRelease %s' % (subject_prefix, activity.data['version'])
        else:
            raise NotImplementedError

        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=activity,
            reply_reference=group,
        )
        msg.add_users(recipient_ids, project=project)
        msg.send()
    def test_simple_get_create(self):
        self.login_as(user=self.user)
        org = self.organization
        group = self.create_group()
        self.create_event(group=group)
        integration = Integration.objects.create(
            provider='example',
            name='Example',
        )
        integration.add_organization(org.id)

        path = '/api/0/issues/{}/integrations/{}/?action=create'.format(
            group.id, integration.id)

        response = self.client.get(path)
        provider = integration.get_provider()

        assert response.data == {
            'id':
            six.text_type(integration.id),
            'name':
            integration.name,
            'icon':
            integration.metadata.get('icon'),
            'domainName':
            integration.metadata.get('domain_name'),
            'accountType':
            integration.metadata.get('account_type'),
            'status':
            integration.get_status_display(),
            'provider': {
                'key': provider.key,
                'name': provider.name,
                'canAdd': provider.can_add,
                'canDisable': provider.can_disable,
                'features': [f.value for f in provider.features],
                'aspects': provider.metadata.aspects,
            },
            'createIssueConfig': [{
                'default': 'message',
                'type': 'string',
                'name': 'title',
                'label': 'Title',
                'required': True,
            }, {
                'default':
                ('Sentry Issue: [%s](%s)\n\n```\n'
                 'Stacktrace (most recent call last):\n\n  '
                 'File "sentry/models/foo.py", line 29, in build_msg\n    '
                 'string_max_length=self.string_max_length)\n\nmessage\n```') %
                (group.qualified_short_id,
                 absolute_uri(group.get_absolute_url())),
                'type':
                'textarea',
                'name':
                'description',
                'label':
                'Description',
                'autosize':
                True,
                'maxRows':
                10,
            }]
        }
Ejemplo n.º 57
0
 def get_project_url(self, project):
     return absolute_uri('/{}/{}/'.format(project.organization.slug, project.slug))
Ejemplo n.º 58
0
 def get_notification_settings_url(self):
     return absolute_uri(reverse('sentry-account-settings-notifications'))
Ejemplo n.º 59
0
    def transform(self, obj, request=None):
        status = obj.get_status()
        if status == GroupStatus.RESOLVED:
            status_label = 'resolved'
        elif status == GroupStatus.IGNORED:
            status_label = 'ignored'
        else:
            status_label = 'unresolved'

        version = obj.last_seen
        if obj.resolved_at:
            version = max(obj.resolved_at, obj.last_seen)
        version = int(version.strftime('%s'))

        d = {
            'id':
            six.text_type(obj.id),
            'count':
            six.text_type(obj.times_seen),
            'title':
            escape(obj.title),
            'message':
            escape(obj.get_legacy_message()),
            'level':
            obj.level,
            'levelName':
            escape(obj.get_level_display()),
            'logger':
            escape(obj.logger),
            'permalink':
            absolute_uri(
                reverse('sentry-group',
                        args=[obj.organization.slug, obj.project.slug,
                              obj.id])),
            'firstSeen':
            self.localize_datetime(obj.first_seen, request=request),
            'lastSeen':
            self.localize_datetime(obj.last_seen, request=request),
            'canResolve':
            request and request.user.is_authenticated(),
            'status':
            status_label,
            'isResolved':
            obj.get_status() == GroupStatus.RESOLVED,
            'isPublic':
            obj.is_public,
            'score':
            getattr(obj, 'sort_value', 0),
            'project': {
                'name': escape(obj.project.name),
                'slug': obj.project.slug,
            },
            'version':
            version,
        }
        if hasattr(obj, 'is_bookmarked'):
            d['isBookmarked'] = obj.is_bookmarked
        if hasattr(obj, 'has_seen'):
            d['hasSeen'] = obj.has_seen
        if hasattr(obj, 'historical_data'):
            d['historicalData'] = obj.historical_data
        if hasattr(obj, 'annotations'):
            d['annotations'] = obj.annotations

        # TODO(dcramer): these aren't tags, and annotations aren't annotations
        if request:
            d['tags'] = get_legacy_annotations(obj, request)
        return d
Ejemplo n.º 60
0
 def generate_footer(self, rule_url):
     return "\nThis work item was automatically created by Sentry via [{}]({})".format(
         self.rule.label,
         absolute_uri(rule_url),
     )