mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
712 lines
29 KiB
Python
712 lines
29 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from django.test import override_settings
|
|
from django.urls import reverse
|
|
from rest_framework.response import Response
|
|
|
|
from sentry import deletions
|
|
from sentry.constants import SentryAppStatus
|
|
from sentry.models.apitoken import ApiToken
|
|
from sentry.models.integrations.sentry_app import MASKED_VALUE, SentryApp
|
|
from sentry.models.integrations.sentry_app_installation import SentryAppInstallation
|
|
from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken
|
|
from sentry.models.organization import Organization
|
|
from sentry.models.organizationmember import OrganizationMember
|
|
from sentry.silo.base import SiloMode
|
|
from sentry.testutils.cases import APITestCase
|
|
from sentry.testutils.helpers import Feature, with_feature
|
|
from sentry.testutils.silo import assume_test_silo_mode, control_silo_test
|
|
from sentry.utils import json
|
|
|
|
POPULARITY = 27
|
|
EXPECTED = {
|
|
"events": ["issue"],
|
|
"name": "MyApp",
|
|
"scopes": [
|
|
"event:read",
|
|
"project:read",
|
|
"org:read",
|
|
"org:write",
|
|
"org:admin",
|
|
"org:integrations",
|
|
],
|
|
"webhookUrl": "https://example.com",
|
|
}
|
|
|
|
|
|
class SentryAppsTest(APITestCase):
|
|
endpoint = "sentry-api-0-sentry-apps"
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.default_popularity = SentryApp._meta.get_field("popularity").default
|
|
|
|
def set_up_apps(self):
|
|
self.published_app = self.create_sentry_app(organization=self.organization, published=True)
|
|
self.unpublished_app = self.create_sentry_app(organization=self.organization)
|
|
self.unowned_unpublished_app = self.create_sentry_app(
|
|
organization=self.create_organization(),
|
|
scopes=(),
|
|
webhook_url="https://example.com",
|
|
)
|
|
|
|
def set_up_super_user(self) -> None:
|
|
self.superuser = self.create_user(email="[email protected]", is_superuser=True)
|
|
self.super_org = self.create_organization(owner=self.superuser)
|
|
self.login_as(self.superuser, superuser=True)
|
|
|
|
def set_up_internal_app(self) -> None:
|
|
self.internal_organization = self.create_organization(owner=self.user)
|
|
self.create_project(organization=self.internal_organization)
|
|
self.internal_app = self.create_internal_integration(
|
|
name="Internal", organization=self.internal_organization
|
|
)
|
|
|
|
def assert_response_has_serialized_sentry_app(
|
|
self,
|
|
response: Response,
|
|
sentry_app: SentryApp,
|
|
organization: Organization,
|
|
has_features: bool = False,
|
|
mask_secret: bool = False,
|
|
) -> None:
|
|
assert sentry_app.application is not None
|
|
data = {
|
|
"allowedOrigins": [],
|
|
"author": sentry_app.author,
|
|
"avatars": [],
|
|
"clientId": sentry_app.application.client_id,
|
|
"clientSecret": sentry_app.application.client_secret,
|
|
"events": [],
|
|
"featureData": [],
|
|
"isAlertable": sentry_app.is_alertable,
|
|
"name": sentry_app.name,
|
|
"overview": sentry_app.overview,
|
|
"owner": {"id": organization.id, "slug": organization.slug},
|
|
"popularity": self.default_popularity,
|
|
"redirectUrl": sentry_app.redirect_url,
|
|
"schema": {},
|
|
"scopes": [],
|
|
"slug": sentry_app.slug,
|
|
"status": sentry_app.get_status_display(),
|
|
"uuid": sentry_app.uuid,
|
|
"verifyInstall": sentry_app.verify_install,
|
|
"webhookUrl": sentry_app.webhook_url,
|
|
"metadata": {},
|
|
}
|
|
|
|
if mask_secret:
|
|
data["scopes"] = ["project:write"]
|
|
data["clientSecret"] = MASKED_VALUE
|
|
|
|
if has_features:
|
|
data["featureData"] = [
|
|
{
|
|
"featureId": 0,
|
|
"featureGate": "integrations-api",
|
|
"description": (
|
|
f"{sentry_app.name} can **utilize the Sentry API** to pull data or"
|
|
+ " update resources in Sentry (with permissions granted, of course)."
|
|
),
|
|
}
|
|
]
|
|
|
|
assert data in json.loads(response.content)
|
|
|
|
def get_data(self, **kwargs: Any) -> Mapping[str, Any]:
|
|
return {
|
|
"author": "Sentry",
|
|
"events": ("issue",),
|
|
"isAlertable": False,
|
|
"isInternal": False,
|
|
"name": "MyApp",
|
|
"organization": self.organization.slug,
|
|
"redirectUrl": "",
|
|
"schema": None,
|
|
"scopes": (
|
|
"project:read",
|
|
"event:read",
|
|
"org:read",
|
|
"org:write",
|
|
"org:admin",
|
|
"org:integrations",
|
|
),
|
|
"verifyInstall": True,
|
|
"webhookUrl": "https://example.com",
|
|
**kwargs,
|
|
}
|
|
|
|
def make_token_request(
|
|
self,
|
|
token: ApiToken,
|
|
endpoint: str,
|
|
method: str = "get",
|
|
data: Mapping[str, Any] | None = None,
|
|
) -> Response:
|
|
return getattr(self.client, method)(
|
|
reverse(endpoint),
|
|
format="json",
|
|
**data,
|
|
HTTP_AUTHORIZATION=f"Bearer {token.token}",
|
|
headers={"Content-Type": "application/json"},
|
|
)
|
|
|
|
|
|
@control_silo_test
|
|
class SuperUserGetSentryAppsTest(SentryAppsTest):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.set_up_super_user()
|
|
self.set_up_apps()
|
|
|
|
def test_superuser_sees_all_apps(self):
|
|
response = self.get_success_response()
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
|
|
assert self.published_app.uuid in response_uuids
|
|
assert self.unpublished_app.uuid in response_uuids
|
|
assert self.unowned_unpublished_app.uuid in response_uuids
|
|
|
|
with self.settings(SENTRY_SELF_HOSTED=False):
|
|
self.get_success_response()
|
|
|
|
@override_settings(SENTRY_SELF_HOSTED=False)
|
|
@with_feature("auth:enterprise-superuser-read-write")
|
|
def test_superuser_read_write_sees_all_apps(self):
|
|
self.get_success_response()
|
|
|
|
self.add_user_permission(self.superuser, "superuser.write")
|
|
self.get_success_response()
|
|
|
|
def test_superusers_filter_on_internal_apps(self):
|
|
self.set_up_internal_app()
|
|
new_org = self.create_organization()
|
|
self.create_project(organization=new_org)
|
|
|
|
internal_app = self.create_internal_integration(name="Internal Nosee", organization=new_org)
|
|
|
|
response = self.get_success_response(qs_params={"status": "internal"})
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response, sentry_app=self.internal_app, organization=self.internal_organization
|
|
)
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert internal_app.uuid in response_uuids
|
|
assert self.published_app.uuid not in response_uuids
|
|
assert self.unpublished_app.uuid not in response_uuids
|
|
assert self.unowned_unpublished_app.uuid not in response_uuids
|
|
|
|
def test_superuser_filter_on_published(self):
|
|
response = self.get_success_response(qs_params={"status": "published"})
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response,
|
|
sentry_app=self.published_app,
|
|
organization=self.organization,
|
|
has_features=True,
|
|
)
|
|
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert self.unpublished_app.uuid not in response_uuids
|
|
assert self.unowned_unpublished_app.uuid not in response_uuids
|
|
|
|
def test_superuser_filter_on_unpublished(self):
|
|
response = self.get_success_response(qs_params={"status": "unpublished"})
|
|
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert self.unpublished_app.uuid in response_uuids
|
|
assert self.unowned_unpublished_app.uuid in response_uuids
|
|
assert self.published_app.uuid not in response_uuids
|
|
|
|
|
|
@control_silo_test
|
|
class GetSentryAppsTest(SentryAppsTest):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.set_up_apps()
|
|
self.login_as(self.user)
|
|
|
|
def test_users_see_published_apps(self):
|
|
response = self.get_success_response()
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response,
|
|
sentry_app=self.published_app,
|
|
organization=self.organization,
|
|
has_features=True,
|
|
)
|
|
|
|
def test_users_filter_on_internal_apps(self):
|
|
self.set_up_internal_app()
|
|
response = self.get_success_response(qs_params={"status": "internal"})
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response,
|
|
sentry_app=self.internal_app,
|
|
organization=self.internal_organization,
|
|
)
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert self.published_app.uuid not in response_uuids
|
|
assert self.unpublished_app.uuid not in response_uuids
|
|
assert self.unowned_unpublished_app.uuid not in response_uuids
|
|
|
|
def test_user_filter_on_unpublished(self):
|
|
response = self.get_success_response(qs_params={"status": "unpublished"})
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response,
|
|
sentry_app=self.unpublished_app,
|
|
organization=self.organization,
|
|
has_features=True,
|
|
)
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert self.published_app.uuid not in response_uuids
|
|
assert self.unowned_unpublished_app.uuid not in response_uuids
|
|
|
|
def test_user_filter_on_published(self):
|
|
response = self.get_success_response(qs_params={"status": "published"})
|
|
response_uuids = {o["uuid"] for o in response.data}
|
|
assert self.published_app.uuid in response_uuids
|
|
assert self.unpublished_app not in response_uuids
|
|
assert self.unowned_unpublished_app.uuid not in response_uuids
|
|
|
|
def test_client_secret_is_masked(self):
|
|
user = self.create_user(email="[email protected]")
|
|
self.create_member(organization=self.organization, user=user)
|
|
# Create an app with higher permissions that what the member role has.
|
|
sentry_app = self.create_sentry_app(
|
|
name="Boo Far", organization=self.organization, scopes=("project:write",)
|
|
)
|
|
|
|
response = self.get_success_response(qs_params={"status": "unpublished"})
|
|
self.assert_response_has_serialized_sentry_app(
|
|
response=response,
|
|
sentry_app=sentry_app,
|
|
organization=self.organization,
|
|
has_features=True,
|
|
mask_secret=True,
|
|
)
|
|
|
|
def test_users_dont_see_unpublished_apps_their_org_owns(self):
|
|
response = self.get_success_response()
|
|
assert self.unpublished_app.uuid not in [a["uuid"] for a in response.data]
|
|
|
|
def test_users_dont_see_unpublished_apps_outside_their_orgs(self):
|
|
response = self.get_success_response()
|
|
assert self.unowned_unpublished_app.uuid not in [a["uuid"] for a in response.data]
|
|
|
|
def test_users_dont_see_internal_apps_outside_their_orgs(self):
|
|
new_org = self.create_organization()
|
|
self.create_project(organization=new_org)
|
|
|
|
internal_app = self.create_internal_integration(name="Internal Nosee", organization=new_org)
|
|
|
|
response = self.get_success_response()
|
|
assert internal_app.uuid not in [a["uuid"] for a in response.data]
|
|
|
|
|
|
@control_silo_test
|
|
class SuperUserPostSentryAppsTest(SentryAppsTest):
|
|
method = "post"
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.set_up_super_user()
|
|
|
|
def test_superuser_cannot_create_app_in_nonexistent_organization(self):
|
|
self.create_project(organization=self.super_org)
|
|
sentry_app = self.create_internal_integration(name="Foo Bar")
|
|
|
|
data = self.get_data(name=sentry_app.name, organization="some-non-existent-org")
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {
|
|
"organization": "Organization 'some-non-existent-org' does not exist.",
|
|
}
|
|
|
|
def test_superuser_can_create_with_popularity(self):
|
|
response = self.get_success_response(**self.get_data(popularity=POPULARITY))
|
|
assert {"popularity": POPULARITY}.items() <= json.loads(response.content).items()
|
|
|
|
with self.settings(SENTRY_SELF_HOSTED=False):
|
|
self.get_success_response(**self.get_data(popularity=25, name="myApp 2"))
|
|
|
|
@override_settings(SENTRY_SELF_HOSTED=False)
|
|
@with_feature("auth:enterprise-superuser-read-write")
|
|
def test_superuser_read_cannot_create(self):
|
|
self.get_error_response(**self.get_data(name="POPULARITY"))
|
|
|
|
@override_settings(SENTRY_SELF_HOSTED=False)
|
|
@with_feature("auth:enterprise-superuser-read-write")
|
|
def test_superuser_read_can_create(self):
|
|
self.add_user_permission(self.superuser, "superuser.write")
|
|
self.get_success_response(**self.get_data(popularity=POPULARITY))
|
|
|
|
|
|
@control_silo_test
|
|
class PostWithTokenSentryAppsTest(SentryAppsTest):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.login_as(self.user)
|
|
self.set_up_internal_app()
|
|
|
|
def assert_no_permission(
|
|
self, organization: Organization, sentry_app: SentryApp, slug: str | None
|
|
):
|
|
self.create_project(organization=organization)
|
|
token = ApiToken.objects.get(application=sentry_app.application)
|
|
|
|
data = self.get_data(name=sentry_app.name, organization=slug)
|
|
response = self.make_token_request(token, self.endpoint, method="post", data=data)
|
|
assert response.status_code == 403
|
|
assert response.data["detail"].startswith("You do not have permission")
|
|
|
|
def test_internal_sentry_app_cannot_create_app(self):
|
|
self.assert_no_permission(
|
|
organization=self.internal_organization,
|
|
sentry_app=self.internal_app,
|
|
slug=self.internal_organization.slug,
|
|
)
|
|
|
|
def test_internal_sentry_app_cannot_create_app_without_organization(self):
|
|
self.assert_no_permission(
|
|
organization=self.internal_organization,
|
|
sentry_app=self.internal_app,
|
|
slug=None,
|
|
)
|
|
|
|
def test_internal_sentry_app_cannot_create_app_in_alien_organization(self):
|
|
other_organization = self.create_organization()
|
|
self.assert_no_permission(
|
|
organization=other_organization,
|
|
sentry_app=self.internal_app,
|
|
slug=other_organization.slug,
|
|
)
|
|
|
|
def test_internal_sentry_app_cannot_create_app_in_nonexistent_organization(self):
|
|
self.assert_no_permission(
|
|
organization=self.organization,
|
|
sentry_app=self.internal_app,
|
|
slug="some-non-existent-org",
|
|
)
|
|
|
|
|
|
@control_silo_test
|
|
class PostSentryAppsTest(SentryAppsTest):
|
|
method = "post"
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.login_as(self.user)
|
|
|
|
def assert_sentry_app_status_code(self, sentry_app: SentryApp, status_code: int) -> None:
|
|
token = ApiToken.objects.create(
|
|
application=sentry_app.application,
|
|
user_id=self.user.id,
|
|
refresh_token=None,
|
|
scope_list=["project:read", "event:read", "org:read"],
|
|
)
|
|
|
|
with assume_test_silo_mode(SiloMode.REGION):
|
|
url = reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
|
|
response = self.client.get(
|
|
url, HTTP_ORIGIN="http://example.com", HTTP_AUTHORIZATION=f"Bearer {token.token}"
|
|
)
|
|
assert response.status_code == status_code
|
|
|
|
def test_creates_sentry_app(self):
|
|
response = self.get_success_response(**self.get_data())
|
|
content = json.loads(response.content)
|
|
for key, value in EXPECTED.items():
|
|
assert key in content
|
|
if isinstance(value, list):
|
|
assert sorted(content[key]) == sorted(value)
|
|
else:
|
|
assert content[key] == value
|
|
|
|
def test_non_unique_app_slug_fails(self):
|
|
sentry_app = self.create_sentry_app(name="Foo Bar", organization=self.organization)
|
|
deletions.exec_sync(sentry_app)
|
|
|
|
data = self.get_data(name=sentry_app.name)
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {"name": ["Name Foo Bar is already taken, please use another."]}
|
|
|
|
def test_same_name_internal_integration(self):
|
|
self.create_project(organization=self.organization)
|
|
sentry_app = self.create_internal_integration(
|
|
name="Foo Bar", organization=self.organization
|
|
)
|
|
|
|
data = self.get_data(name=sentry_app.name)
|
|
response = self.get_success_response(**data)
|
|
assert response.data["name"] == sentry_app.name
|
|
assert response.data["slug"] != sentry_app.slug
|
|
|
|
def test_cannot_create_app_without_organization(self):
|
|
self.create_project(organization=self.organization)
|
|
sentry_app = self.create_internal_integration(name="Foo Bar")
|
|
|
|
data = self.get_data(name=sentry_app.name, organization=None)
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {
|
|
"organization": "Please provide a valid value for the 'organization' field.",
|
|
}
|
|
|
|
def test_cannot_create_app_in_alien_organization(self):
|
|
other_organization = self.create_organization()
|
|
self.create_project(organization=other_organization)
|
|
sentry_app = self.create_internal_integration(name="Foo Bar")
|
|
|
|
data = self.get_data(name=sentry_app.name, organization=other_organization.slug)
|
|
response = self.get_error_response(**data, status_code=403)
|
|
assert response.data["detail"].startswith("User does not belong to")
|
|
|
|
def test_user_cannot_create_app_in_nonexistent_organization(self):
|
|
self.create_project(organization=self.organization)
|
|
sentry_app = self.create_internal_integration(name="Foo Bar")
|
|
|
|
data = self.get_data(name=sentry_app.name, organization="some-non-existent-org")
|
|
response = self.get_error_response(**data, status_code=403)
|
|
assert response.data["detail"].startswith("User does not belong to")
|
|
|
|
def test_nonsuperuser_cannot_create_with_popularity(self):
|
|
response = self.get_success_response(**self.get_data(popularity=POPULARITY))
|
|
assert {"popularity": self.default_popularity}.items() <= json.loads(
|
|
response.content
|
|
).items()
|
|
|
|
def test_long_name_internal_integration(self):
|
|
self.create_project(organization=self.organization)
|
|
|
|
response = self.get_error_response(**self.get_data(name="k" * 58), status_code=400)
|
|
assert response.data == {"name": ["Cannot exceed 57 characters"]}
|
|
|
|
def test_invalid_with_missing_webhook_url_scheme(self):
|
|
data = self.get_data(webhookUrl="example.com")
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {"webhookUrl": ["URL must start with http[s]://"]}
|
|
|
|
def test_cannot_create_app_without_correct_permissions(self):
|
|
data = self.get_data(scopes=("project:read",))
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {"events": ["issue webhooks require the event:read permission."]}
|
|
|
|
def test_create_alert_rule_action(self):
|
|
expected = {**EXPECTED, "schema": {"elements": [self.create_alert_rule_action_schema()]}}
|
|
|
|
data = self.get_data(schema={"elements": [self.create_alert_rule_action_schema()]})
|
|
response = self.get_success_response(**data)
|
|
content = json.loads(response.content)
|
|
for key, value in expected.items():
|
|
assert key in content
|
|
if isinstance(value, list):
|
|
assert sorted(content[key]) == sorted(value)
|
|
else:
|
|
assert content[key] == value
|
|
|
|
@patch("sentry.analytics.record")
|
|
def test_wrong_schema_format(self, record):
|
|
kwargs = {
|
|
"schema": {
|
|
"elements": [
|
|
{
|
|
"type": "alert-rule-action",
|
|
"title": "Create task",
|
|
"settings": {
|
|
"type": "alert-rule-settings",
|
|
"uri": "/sentry/alert-rule",
|
|
"required_fields": [
|
|
{
|
|
"type": "select",
|
|
"label": "Channel",
|
|
"name": "channel",
|
|
"options": [
|
|
# Option items should have 2 elements
|
|
# i.e. ['channel_id', '#general']
|
|
["#general"]
|
|
],
|
|
}
|
|
],
|
|
},
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
response = self.get_error_response(**self.get_data(**kwargs), status_code=400)
|
|
assert response.data == {
|
|
"schema": ["['#general'] is too short for element of type 'alert-rule-action'"]
|
|
}
|
|
|
|
# XXX: Compare schema as an object instead of json to avoid key ordering issues
|
|
record.call_args.kwargs["schema"] = json.loads(record.call_args.kwargs["schema"])
|
|
|
|
record.assert_called_with(
|
|
"sentry_app.schema_validation_error",
|
|
schema=kwargs["schema"],
|
|
user_id=self.user.id,
|
|
sentry_app_name="MyApp",
|
|
organization_id=self.organization.id,
|
|
error_message="['#general'] is too short for element of type 'alert-rule-action'",
|
|
)
|
|
|
|
@with_feature("organizations:integrations-event-hooks")
|
|
def test_can_create_with_error_created_hook_with_flag(self):
|
|
expected = {**EXPECTED, "events": ["error"]}
|
|
response = self.get_success_response(**self.get_data(events=("error",)))
|
|
content = json.loads(response.content)
|
|
for key, value in expected.items():
|
|
assert key in content
|
|
if isinstance(value, list):
|
|
assert sorted(content[key]) == sorted(value)
|
|
else:
|
|
assert content[key] == value
|
|
|
|
def test_cannot_create_with_error_created_hook_without_flag(self):
|
|
with Feature({"organizations:integrations-event-hooks": False}):
|
|
response = self.get_error_response(**self.get_data(events=("error",)), status_code=403)
|
|
assert response.data == {
|
|
"non_field_errors": [
|
|
"Your organization does not have access to the 'error' resource subscription."
|
|
]
|
|
}
|
|
|
|
def test_allows_empty_schema(self):
|
|
self.get_success_response(**self.get_data(shema={}))
|
|
|
|
def test_generated_slug_not_entirely_numeric(self):
|
|
response = self.get_success_response(**self.get_data(name="1234"), status_code=201)
|
|
slug = response.data["slug"]
|
|
assert slug.startswith("1234-")
|
|
assert not slug.isdecimal()
|
|
|
|
def test_missing_name(self):
|
|
response = self.get_error_response(**self.get_data(name=None), status_code=400)
|
|
assert "name" in response.data
|
|
|
|
def test_invalid_events(self):
|
|
response = self.get_error_response(**self.get_data(events=["project"]), status_code=400)
|
|
assert "events" in response.data
|
|
|
|
def test_invalid_scope(self):
|
|
response = self.get_error_response(**self.get_data(scopes="not:ascope"), status_code=400)
|
|
assert "scopes" in response.data
|
|
|
|
def test_missing_webhook_url(self):
|
|
response = self.get_error_response(**self.get_data(webhookUrl=None), status_code=400)
|
|
assert "webhookUrl" in response.data
|
|
|
|
def test_allows_empty_permissions(self):
|
|
response = self.get_success_response(**self.get_data(scopes=None))
|
|
assert response.data["scopes"] == []
|
|
|
|
def test_creates_internal_integration(self):
|
|
self.create_project(organization=self.organization)
|
|
|
|
response = self.get_success_response(**self.get_data(isInternal=True))
|
|
assert re.match(r"myapp\-[0-9a-zA-Z]+", response.data["slug"])
|
|
assert response.data["status"] == SentryAppStatus.as_str(SentryAppStatus.INTERNAL)
|
|
assert not response.data["verifyInstall"]
|
|
|
|
# Verify no tokens are created.
|
|
sentry_app = SentryApp.objects.get(slug=response.data["slug"])
|
|
sentry_app_installation = SentryAppInstallation.objects.get(sentry_app=sentry_app)
|
|
|
|
with pytest.raises(SentryAppInstallationToken.DoesNotExist):
|
|
SentryAppInstallationToken.objects.get(sentry_app_installation=sentry_app_installation)
|
|
|
|
def test_no_author_public_integration(self):
|
|
response = self.get_error_response(**self.get_data(author=None), status_code=400)
|
|
assert response.data == {"author": ["author required for public integrations"]}
|
|
|
|
def test_no_author_internal_integration(self):
|
|
self.create_project(organization=self.organization)
|
|
|
|
self.get_success_response(**self.get_data(isInternal=True, author=None))
|
|
|
|
def test_create_integration_with_allowed_origins(self):
|
|
response = self.get_success_response(
|
|
**self.get_data(allowedOrigins=("google.com", "example.com"))
|
|
)
|
|
sentry_app = SentryApp.objects.get(slug=response.data["slug"])
|
|
assert sentry_app.application.get_allowed_origins() == ["google.com", "example.com"]
|
|
|
|
def test_create_internal_integration_with_allowed_origins_and_test_route(self):
|
|
self.create_project(organization=self.organization)
|
|
|
|
data = self.get_data(
|
|
isInternal=True,
|
|
allowedOrigins=("example.com",),
|
|
scopes=("project:read", "event:read", "org:read"),
|
|
)
|
|
response = self.get_success_response(**data)
|
|
sentry_app = SentryApp.objects.get(slug=response.data["slug"])
|
|
assert sentry_app.application.get_allowed_origins() == ["example.com"]
|
|
|
|
self.assert_sentry_app_status_code(sentry_app, status_code=200)
|
|
|
|
def test_create_internal_integration_without_allowed_origins_and_test_route(self):
|
|
self.create_project(organization=self.organization)
|
|
|
|
data = self.get_data(isInternal=True, scopes=("project:read", "event:read", "org:read"))
|
|
response = self.get_success_response(**data)
|
|
sentry_app = SentryApp.objects.get(slug=response.data["slug"])
|
|
assert sentry_app.application.get_allowed_origins() == []
|
|
|
|
self.assert_sentry_app_status_code(sentry_app, status_code=400)
|
|
|
|
def test_members_cant_create(self):
|
|
# create extra owner because we are demoting one
|
|
self.create_member(organization=self.organization, user=self.create_user(), role="owner")
|
|
with assume_test_silo_mode(SiloMode.REGION):
|
|
member_om = OrganizationMember.objects.get(
|
|
user_id=self.user.id, organization=self.organization
|
|
)
|
|
member_om.role = "member"
|
|
member_om.save()
|
|
|
|
self.get_error_response(**self.get_data(), status_code=403)
|
|
|
|
def test_create_integration_exceeding_scopes(self):
|
|
# create extra owner because we are demoting one
|
|
self.create_member(organization=self.organization, user=self.create_user(), role="owner")
|
|
with assume_test_silo_mode(SiloMode.REGION):
|
|
member_om = OrganizationMember.objects.get(
|
|
user_id=self.user.id, organization=self.organization
|
|
)
|
|
member_om.role = "manager"
|
|
member_om.save()
|
|
|
|
data = self.get_data(events=(), scopes=("org:read", "org:write", "org:admin"))
|
|
response = self.get_error_response(**data, status_code=400)
|
|
assert response.data == {
|
|
"scopes": [
|
|
"Requested permission of org:admin exceeds requester's permission."
|
|
" Please contact an administrator to make the requested change.",
|
|
]
|
|
}
|
|
|
|
def test_create_internal_integration_with_non_globally_unique_name(self):
|
|
# Internal integration names should only need to be unique within an organization.
|
|
self.create_project(organization=self.organization)
|
|
|
|
other_organization = self.create_organization()
|
|
other_organization_integration = self.create_sentry_app(
|
|
name="Foo Bar", organization=other_organization
|
|
)
|
|
|
|
self.get_success_response(
|
|
**self.get_data(name=other_organization_integration.name, isInternal=True)
|
|
)
|
|
|
|
other_organization = self.create_organization()
|
|
self.create_project(organization=other_organization)
|
|
other_organization_internal_integration = self.create_internal_integration(
|
|
name="Foo Bar 2", organization=other_organization
|
|
)
|
|
|
|
self.get_success_response(
|
|
**self.get_data(name=other_organization_internal_integration.name, isInternal=True)
|
|
)
|