class SentryAppDocs(APIDocsTestCase):
    def setUp(self):
        self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
        self.project = self.create_project(organization=self.org)
        self.group = self.create_group(project=self.project)
        self.sentry_app = self.create_sentry_app(
            name="Hellboy App", published=True, organization=self.org
        )
        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
        self.install.save()
        self.url = reverse(
            "sentry-api-0-sentry-app-installation-external-issues",
            kwargs={"uuid": self.install.uuid},
        )

        self.login_as(user=self.user)

    def test_post(self):
        data = {
            "issueId": self.group.id,
            "webUrl": "https://somerandom.io/project/issue-id",
            "project": "ExternalProj",
            "identifier": "issue-1",
        }
        response = self.client.post(self.url, data)
        request = RequestFactory().post(self.url, data)

        self.validate_schema(request, response)
class SentryAppInstallationTest(TestCase):
    def setUp(self):
        self.user = self.create_user()
        self.proxy = self.create_user()
        self.org = self.create_organization()
        self.application = ApiApplication.objects.create(owner=self.proxy)

        self.sentry_app = SentryApp.objects.create(
            application=self.application,
            name="NullDB",
            proxy_user=self.proxy,
            owner=self.org,
            scope_list=("project:read",),
            webhook_url="http://example.com",
        )

        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)

    def test_paranoid(self):
        self.install.save()
        self.install.delete()
        assert self.install.date_deleted is not None
        assert self.install not in SentryAppInstallation.objects.all()

    def test_date_updated(self):
        self.install.save()
        date_updated = self.install.date_updated
        self.install.save()
        assert not self.install.date_updated == date_updated

    def test_related_names(self):
        self.install.save()
        assert self.install in self.install.sentry_app.installations.all()
        assert self.install in self.install.organization.sentry_app_installations.all()
    def setUp(self):
        self.org = self.create_organization(owner=self.user,
                                            name="Rowdy Tiger")
        self.project = self.create_project(organization=self.org)
        self.group = self.create_group(project=self.project)
        self.sentry_app = self.create_sentry_app(name="Hellboy App",
                                                 published=True,
                                                 organization=self.org)
        self.install = SentryAppInstallation(sentry_app=self.sentry_app,
                                             organization=self.org)
        self.install.save()
        self.external_issue = self.create_platform_external_issue(
            group=self.group,
            service_type=self.sentry_app.slug,
            display_name="App#issue-1",
            web_url=self.sentry_app.webhook_url,
        )
        self.url = reverse(
            "sentry-api-0-sentry-app-installation-external-issue-details",
            kwargs={
                "uuid": self.install.uuid,
                "external_issue_id": self.external_issue.id
            },
        )

        self.login_as(user=self.user)
Esempio n. 4
0
class SentryAppDetailsDocs(APIDocsTestCase):
    def setUp(self):
        self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
        self.project = self.create_project(organization=self.org)
        self.group = self.create_group(project=self.project)
        self.sentry_app = self.create_sentry_app(
            name="Hellboy App", published=True, organization=self.org
        )
        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
        self.install.save()
        self.external_issue = self.create_platform_external_issue(
            group=self.group,
            service_type=self.sentry_app.slug,
            display_name="App#issue-1",
            web_url=self.sentry_app.webhook_url,
        )
        self.url = reverse(
            "sentry-api-0-sentry-app-installation-external-issue-details",
            kwargs={"uuid": self.install.uuid, "external_issue_id": self.external_issue.id},
        )

        self.login_as(user=self.user)

    def test_delete(self):
        response = self.client.delete(self.url)
        request = RequestFactory().delete(self.url)

        self.validate_schema(request, response)
    def setUp(self):
        self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
        self.project = self.create_project(organization=self.org)
        self.group = self.create_group(project=self.project)
        self.sentry_app = self.create_sentry_app(
            name="Hellboy App", published=True, organization=self.org
        )
        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
        self.install.save()
        self.url = reverse(
            "sentry-api-0-sentry-app-installation-external-issues",
            kwargs={"uuid": self.install.uuid},
        )

        self.login_as(user=self.user)
Esempio n. 6
0
    def get(self, request, organization):
        try:
            project = Project.objects.get(id=request.GET["projectId"],
                                          organization_id=organization.id)
        except Project.DoesNotExist:
            return Response([], status=404)

        components = []

        for install in SentryAppInstallation.get_installed_for_org(
                organization.id):
            _components = SentryAppComponent.objects.filter(
                sentry_app_id=install.sentry_app_id)

            if "filter" in request.GET:
                _components = _components.filter(type=request.GET["filter"])

            for component in _components:
                try:
                    sentry_app_components.Preparer.run(component=component,
                                                       install=install,
                                                       project=project)
                    components.append(component)
                except APIError:
                    continue

        return self.paginate(
            request=request,
            queryset=components,
            paginator_cls=OffsetPaginator,
            on_results=lambda x: serialize(x, request.user),
        )
    def setUp(self):
        self.user = self.create_user("*****@*****.**")
        self.org = self.create_organization(name="Jessla", owner=None)
        self.create_member(user=self.user, organization=self.org, role="owner")

        self.sentry_app = self.create_sentry_app(
            name="Tesla App", published=True, organization=self.org
        )
        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
        self.install.save()

        self.login_as(user=self.user)
        self.url = reverse(
            "sentry-api-0-sentry-app-installations",
            kwargs={"organization_slug": self.org.slug},
        )
    def setUp(self):
        self.user = self.create_user()
        self.proxy = self.create_user()
        self.org = self.create_organization()
        self.application = ApiApplication.objects.create(owner=self.proxy)

        self.sentry_app = SentryApp.objects.create(
            application=self.application,
            name="NullDB",
            proxy_user=self.proxy,
            owner=self.org,
            scope_list=("project:read",),
            webhook_url="http://example.com",
        )

        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
Esempio n. 9
0
    def get(self, request: Request, project, rule) -> Response:
        """
        Retrieve a rule

        Return details on an individual rule.

            {method} {path}

        """

        # Serialize Rule object
        serialized_rule = serialize(
            rule, request.user,
            RuleSerializer(request.GET.getlist("expand", [])))

        errors = []
        # Prepare Rule Actions that are SentryApp components using the meta fields
        for action in serialized_rule.get("actions", []):
            if action.get("_sentry_app_installation") and action.get(
                    "_sentry_app_component"):
                installation = SentryAppInstallation(
                    **action.get("_sentry_app_installation", {}))
                component = installation.prepare_ui_component(
                    SentryAppComponent(**action.get("_sentry_app_component")),
                    project,
                    action.get("settings"),
                )
                if component is None:
                    errors.append({
                        "detail":
                        f"Could not fetch details from {installation.sentry_app.name}"
                    })
                    action["disabled"] = True
                    continue

                action["formFields"] = component.schema.get("settings", {})

                # Delete meta fields
                del action["_sentry_app_installation"]
                del action["_sentry_app_component"]

        if len(errors):
            serialized_rule["errors"] = errors

        return Response(serialized_rule)
Esempio n. 10
0
    def get(self, request: Request, organization, alert_rule) -> Response:
        """
        Fetch an alert rule.
        ``````````````````
        :auth: required
        """
        # Serialize Alert Rule
        serialized_rule = serialize(alert_rule, request.user, DetailedAlertRuleSerializer())

        # Prepare AlertRuleTriggerActions that are SentryApp components
        errors = []
        for trigger in serialized_rule.get("triggers", []):
            for action in trigger.get("actions", []):
                if action.get("_sentry_app_installation") and action.get("_sentry_app_component"):
                    installation = SentryAppInstallation(
                        **action.get("_sentry_app_installation", {})
                    )
                    component = installation.prepare_ui_component(
                        SentryAppComponent(**action.get("_sentry_app_component")),
                        None,
                        action.get("settings"),
                    )
                    if component is None:
                        errors.append(
                            {
                                "detail": f"Could not fetch details from {installation.sentry_app.name}"
                            }
                        )
                        action["disabled"] = True
                        continue

                    action["formFields"] = component.schema.get("settings", {})

                    # Delete meta fields
                    del action["_sentry_app_installation"]
                    del action["_sentry_app_component"]

        if len(errors):
            serialized_rule["errors"] = errors

        return Response(serialized_rule)
class SentryAppInstallationTest(TestCase):
    def setUp(self):
        self.user = self.create_user()
        self.proxy = self.create_user()
        self.org = self.create_organization()
        self.application = ApiApplication.objects.create(owner=self.proxy)

        self.sentry_app = SentryApp.objects.create(
            application=self.application,
            name='NullDB',
            proxy_user=self.proxy,
            owner=self.user,
            scope_list=('project:read', ),
            webhook_url='http://example.com',
        )

        self.install = SentryAppInstallation(
            sentry_app=self.sentry_app,
            organization=self.org,
        )

    def test_paranoid(self):
        self.install.save()
        self.install.delete()
        assert self.install.date_deleted is not None
        assert self.install not in SentryAppInstallation.objects.all()

    def test_date_updated(self):
        self.install.save()
        date_updated = self.install.date_updated
        self.install.save()
        assert not self.install.date_updated == date_updated

    def test_related_names(self):
        self.install.save()
        assert self.install in self.install.sentry_app.installations.all()
        assert self.install in \
            self.install.organization.sentry_app_installations.all()
class SentryAppInstallationDocs(APIDocsTestCase):
    def setUp(self):
        self.user = self.create_user("*****@*****.**")
        self.org = self.create_organization(name="Jessla", owner=None)
        self.create_member(user=self.user, organization=self.org, role="owner")

        self.sentry_app = self.create_sentry_app(
            name="Tesla App", published=True, organization=self.org
        )
        self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
        self.install.save()

        self.login_as(user=self.user)
        self.url = reverse(
            "sentry-api-0-sentry-app-installations",
            kwargs={"organization_slug": self.org.slug},
        )

    def test_get(self):
        response = self.client.get(self.url)
        request = RequestFactory().get(self.url)

        self.validate_schema(request, response)
Esempio n. 13
0
    def get_custom_actions(self,
                           project: Project) -> Sequence[Mapping[str, Any]]:
        action_list = []
        for install in SentryAppInstallation.get_installed_for_org(
                project.organization_id):
            component = install.prepare_sentry_app_components(
                "alert-rule-action", project)
            if component:
                kwargs = {
                    "install": install,
                    "event_action": self,
                }
                action_details = serialize(
                    component, None, SentryAppAlertRuleActionSerializer(),
                    **kwargs)
                action_list.append(action_details)

        return action_list
    def setUp(self):
        self.user = self.create_user()
        self.proxy = self.create_user()
        self.org = self.create_organization()
        self.application = ApiApplication.objects.create(owner=self.proxy)

        self.sentry_app = SentryApp.objects.create(
            application=self.application,
            name='NullDB',
            proxy_user=self.proxy,
            owner=self.user,
            scope_list=('project:read', ),
            webhook_url='http://example.com',
        )

        self.install = SentryAppInstallation(
            sentry_app=self.sentry_app,
            organization=self.org,
        )
Esempio n. 15
0
    def get(self, request, organization):
        """
        Fetches actions that an alert rule can perform for an organization
        """
        if not features.has(
                "organizations:incidents", organization, actor=request.user):
            raise ResourceDoesNotExist

        actions = []

        # Cache Integration objects in this data structure to save DB calls.
        provider_integrations = defaultdict(list)
        for integration in get_available_action_integrations_for_org(
                organization):
            provider_integrations[integration.provider].append(integration)

        for registered_type in AlertRuleTriggerAction.get_registered_types():
            # Used cached integrations for each `registered_type` instead of making N calls.
            if registered_type.integration_provider:
                actions += [
                    build_action_response(registered_type,
                                          integration=integration,
                                          organization=organization)
                    for integration in provider_integrations[
                        registered_type.integration_provider]
                ]

            # Add all alertable SentryApps to the list.
            elif registered_type.type == AlertRuleTriggerAction.Type.SENTRY_APP:
                actions += [
                    build_action_response(registered_type,
                                          sentry_app_installation=install)
                    for install in SentryAppInstallation.get_installed_for_org(
                        organization.id).filter(sentry_app__is_alertable=True,
                                                )
                ]

            else:
                actions.append(build_action_response(registered_type))
        return Response(actions, status=status.HTTP_200_OK)
Esempio n. 16
0
 def get_custom_actions(self,
                        project: Project) -> Sequence[Mapping[str, Any]]:
     action_list = []
     for install in SentryAppInstallation.get_installed_for_org(
             project.organization_id):
         _components = SentryAppComponent.objects.filter(
             sentry_app_id=install.sentry_app_id, type="alert-rule-action")
         for component in _components:
             try:
                 sentry_app_components.Preparer.run(component=component,
                                                    install=install,
                                                    project=project)
                 kwargs = {
                     "install": install,
                     "event_action": self,
                 }
                 action_details = serialize(
                     component, None, SentryAppAlertRuleActionSerializer(),
                     **kwargs)
                 action_list.append(action_details)
             except APIError:
                 continue
     return action_list
    def get(self, request, project):
        """
        Retrieve the list of configuration options for a given project.
        """

        action_list = []
        condition_list = []
        filter_list = []

        project_has_filters = features.has("projects:alert-filters", project)
        can_create_tickets = features.has(
            "organizations:integrations-ticket-rules", project.organization)
        has_percent_condition = features.has(
            "organizations:issue-percent-filters", project.organization)
        # TODO: conditions need to be based on actions
        for rule_type, rule_cls in rules:
            node = rule_cls(project)
            # skip over conditions if they are not in the migrated set for a project with alert-filters
            if project_has_filters and node.id in MIGRATED_CONDITIONS:
                continue

            if not can_create_tickets and node.id in TICKET_ACTIONS:
                continue

            context = {
                "id": node.id,
                "label": node.label,
                "enabled": node.is_enabled()
            }
            if hasattr(node, "prompt"):
                context["prompt"] = node.prompt

            if hasattr(node, "form_fields"):
                context["formFields"] = node.form_fields

            if node.id in TICKET_ACTIONS:
                context["actionType"] = "ticket"
                context["ticketType"] = node.ticket_type
                context["link"] = node.link

            # It is possible for a project to have no services. In that scenario we do
            # not want the front end to render the action as the action does not have
            # options.
            if (node.id ==
                    "sentry.rules.actions.notify_event_service.NotifyEventServiceAction"
                    and len(node.get_services()) == 0):
                continue

            if rule_type.startswith("condition/"):
                if (has_percent_condition or context["id"] !=
                        "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition"
                    ):
                    condition_list.append(context)
            elif rule_type.startswith("filter/"):
                filter_list.append(context)
            elif rule_type.startswith("action/"):
                action_list.append(context)

        for install in SentryAppInstallation.get_installed_for_org(
                project.organization_id):
            _components = SentryAppComponent.objects.filter(
                sentry_app_id=install.sentry_app_id, type="alert-rule-action")
            for component in _components:
                try:
                    sentry_app_components.Preparer.run(component=component,
                                                       install=install,
                                                       project=project)
                    action_list.append(
                        serialize(component, request.user,
                                  SentryAppAlertRuleActionSerializer()))

                except APIError:
                    continue

        context = {
            "actions": action_list,
            "conditions": condition_list,
            "filters": filter_list
        }

        return Response(context)
Esempio n. 18
0
def installations_to_notify(organization, event):
    installations = SentryAppInstallation.get_installed_for_org(
        organization.id).select_related("sentry_app")

    return [i for i in installations if event in i.sentry_app.events]
Esempio n. 19
0
def _process_resource_change(action, sender, instance_id, retryer=None, *args, **kwargs):
    # The class is serialized as a string when enqueueing the class.
    model = TYPES[sender]
    # The Event model has different hooks for the different event types. The sender
    # determines which type eg. Error and therefore the 'name' eg. error
    if issubclass(model, Event):
        if not kwargs.get("instance"):
            extra = {"sender": sender, "action": action, "event_id": instance_id}
            logger.info("process_resource_change.event_missing_event", extra=extra)
            return

        name = sender.lower()
    else:
        # Some resources are named differently than their model. eg. Group vs Issue.
        # Looks up the human name for the model. Defaults to the model name.
        name = RESOURCE_RENAMES.get(model.__name__, model.__name__.lower())

    # By default, use Celery's `current` but allow a value to be passed for the
    # bound Task.
    retryer = retryer or current

    # We may run into a race condition where this task executes before the
    # transaction that creates the Group has committed.
    try:
        if issubclass(model, Event):
            # XXX:(Meredith): Passing through the entire event was an intentional choice
            # to avoid having to query NodeStore again for data we had previously in
            # post_process. While this is not ideal, changing this will most likely involve
            # an overhaul of how we do things in post_process, not just this task alone.
            instance = kwargs.get("instance")
        else:
            instance = model.objects.get(id=instance_id)
    except model.DoesNotExist as e:
        # Explicitly requeue the task, so we don't report this to Sentry until
        # we hit the max number of retries.
        return retryer.retry(exc=e)

    event = f"{name}.{action}"

    if event not in VALID_EVENTS:
        return

    org = None

    if isinstance(instance, Group) or isinstance(instance, Event):
        org = Organization.objects.get_from_cache(
            id=Project.objects.get_from_cache(id=instance.project_id).organization_id
        )

    installations = filter(
        lambda i: event in i.sentry_app.events,
        SentryAppInstallation.get_installed_for_org(org.id).select_related("sentry_app"),
    )

    for installation in installations:
        data = {}
        if isinstance(instance, Event):
            data[name] = _webhook_event_data(instance, instance.group_id, instance.project_id)
        else:
            data[name] = serialize(instance)

        # Trigger a new task for each webhook
        send_resource_change_webhook.delay(installation_id=installation.id, event=event, data=data)