diff --git a/src/sentry/api/authentication.py b/src/sentry/api/authentication.py index 126c6ba1d33a11..06df68b600c511 100644 --- a/src/sentry/api/authentication.py +++ b/src/sentry/api/authentication.py @@ -26,7 +26,6 @@ from sentry.models.apiapplication import ApiApplication from sentry.models.apikey import ApiKey from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.orgauthtoken import ( OrgAuthToken, is_org_auth_token_auth, @@ -35,6 +34,7 @@ from sentry.models.projectkey import ProjectKey from sentry.models.relay import Relay from sentry.relay.utils import get_header_relay_id, get_header_relay_signature +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.silo.base import SiloLimit, SiloMode from sentry.users.models.user import User from sentry.users.services.user import RpcUser diff --git a/src/sentry/api/bases/sentryapps.py b/src/sentry/api/bases/sentryapps.py index 91e9827ac73beb..242c81858f2f25 100644 --- a/src/sentry/api/bases/sentryapps.py +++ b/src/sentry/api/bases/sentryapps.py @@ -19,12 +19,12 @@ from sentry.coreapi import APIError from sentry.integrations.api.bases.integration import PARANOID_GET from sentry.middleware.stats import add_request_metric_tags -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.organization import OrganizationStatus from sentry.organizations.services.organization import ( RpcUserOrganizationContext, organization_service, ) +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryApp, app_service from sentry.users.services.user import RpcUser from sentry.users.services.user.service import user_service diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/details.py b/src/sentry/api/endpoints/integrations/sentry_apps/details.py index 070abd972ed649..0e848322c970cf 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/details.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/details.py @@ -21,10 +21,10 @@ from sentry.auth.staff import is_active_staff from sentry.constants import SentryAppStatus from sentry.mediators.sentry_app_installations.installation_notifier import InstallationNotifier -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.organizations.services.organization import organization_service from sentry.sentry_apps.logic import SentryAppUpdater +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.utils.audit import create_audit_entry logger = logging.getLogger(__name__) diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/index.py b/src/sentry/api/endpoints/integrations/sentry_apps/index.py index 20d7c7ec610b30..976e85e89cd0ec 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/index.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/index.py @@ -16,8 +16,8 @@ from sentry.auth.staff import is_active_staff from sentry.auth.superuser import is_active_superuser from sentry.constants import SentryAppStatus -from sentry.models.integrations.sentry_app import SentryApp from sentry.sentry_apps.logic import SentryAppCreator +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.users.services.user.service import user_service logger = logging.getLogger(__name__) diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py b/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py index fc7a5c8bef7870..4a0b8553464f18 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py @@ -14,9 +14,9 @@ from sentry.constants import SENTRY_APP_SLUG_MAX_LENGTH, SentryAppStatus from sentry.features.exceptions import FeatureNotRegistered from sentry.integrations.models.integration_feature import IntegrationFeature, IntegrationTypes -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.installations import SentryAppInstallationCreator +from sentry.sentry_apps.models.sentry_app import SentryApp class SentryAppInstallationsSerializer(serializers.Serializer): diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/interaction.py b/src/sentry/api/endpoints/integrations/sentry_apps/interaction.py index 08ce955a0f7e0c..ed3bd99d45015c 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/interaction.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/interaction.py @@ -9,7 +9,7 @@ from sentry.api.base import StatsMixin, region_silo_endpoint from sentry.api.bases import RegionSentryAppBaseEndpoint, SentryAppStatsPermission from sentry.api.bases.sentryapps import COMPONENT_TYPES -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryApp, app_service from sentry.tsdb.base import TSDBModel diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/internal_app_token/index.py b/src/sentry/api/endpoints/integrations/sentry_apps/internal_app_token/index.py index 390703007f2ba5..376ef35414bc9a 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/internal_app_token/index.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/internal_app_token/index.py @@ -13,9 +13,9 @@ from sentry.api.serializers.models.apitoken import ApiTokenSerializer from sentry.exceptions import ApiTokenLimitError from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import MASKED_VALUE from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.installations import SentryAppInstallationTokenCreator +from sentry.sentry_apps.models.sentry_app import MASKED_VALUE @control_silo_endpoint diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/organization_sentry_apps.py b/src/sentry/api/endpoints/integrations/sentry_apps/organization_sentry_apps.py index 5e96d176c6b4aa..1c89fd73ee2b39 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/organization_sentry_apps.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/organization_sentry_apps.py @@ -9,9 +9,9 @@ from sentry.api.paginator import OffsetPaginator from sentry.api.serializers import serialize from sentry.constants import SentryAppStatus -from sentry.models.integrations.sentry_app import SentryApp from sentry.organizations.services.organization import RpcOrganization from sentry.organizations.services.organization.model import RpcUserOrganizationContext +from sentry.sentry_apps.models.sentry_app import SentryApp @control_silo_endpoint diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/rotate_secret.py b/src/sentry/api/endpoints/integrations/sentry_apps/rotate_secret.py index a8b6dff9ad16e3..fa171c6f96ca95 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/rotate_secret.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/rotate_secret.py @@ -14,8 +14,8 @@ from sentry.auth.superuser import superuser_has_permission from sentry.constants import SentryAppStatus from sentry.models.apiapplication import generate_token -from sentry.models.integrations.sentry_app import SentryApp from sentry.organizations.services.organization import organization_service +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.users.services.user.service import user_service logger = logging.getLogger(__name__) diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/stats/index.py b/src/sentry/api/endpoints/integrations/sentry_apps/stats/index.py index f4628b812af78f..c3b9b63732d6fb 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/stats/index.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/stats/index.py @@ -9,7 +9,7 @@ from sentry.api.permissions import SuperuserOrStaffFeatureFlaggedPermission from sentry.api.serializers import serialize from sentry.models.avatars.sentry_app_avatar import SentryAppAvatar -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp @control_silo_endpoint diff --git a/src/sentry/api/serializers/models/sentry_app.py b/src/sentry/api/serializers/models/sentry_app.py index 4d6cf5051555a4..64851baa6652df 100644 --- a/src/sentry/api/serializers/models/sentry_app.py +++ b/src/sentry/api/serializers/models/sentry_app.py @@ -13,8 +13,8 @@ from sentry.integrations.models.integration_feature import IntegrationFeature, IntegrationTypes from sentry.models.apiapplication import ApiApplication from sentry.models.avatars.sentry_app_avatar import SentryAppAvatar -from sentry.models.integrations.sentry_app import MASKED_VALUE, SentryApp from sentry.organizations.services.organization import organization_service +from sentry.sentry_apps.models.sentry_app import MASKED_VALUE, SentryApp from sentry.users.models.user import User from sentry.users.services.user.service import user_service diff --git a/src/sentry/api/serializers/models/sentry_app_installation.py b/src/sentry/api/serializers/models/sentry_app_installation.py index a62216908ccf4f..418b770cfb6522 100644 --- a/src/sentry/api/serializers/models/sentry_app_installation.py +++ b/src/sentry/api/serializers/models/sentry_app_installation.py @@ -6,8 +6,8 @@ from sentry.api.serializers import Serializer, register from sentry.constants import SentryAppInstallationStatus from sentry.hybridcloud.services.organization_mapping import organization_mapping_service -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.users.models.user import User from sentry.users.services.user import RpcUser diff --git a/src/sentry/api/serializers/rest_framework/sentry_app.py b/src/sentry/api/serializers/rest_framework/sentry_app.py index 81c7c788c5da62..4e6c4adaef73f9 100644 --- a/src/sentry/api/serializers/rest_framework/sentry_app.py +++ b/src/sentry/api/serializers/rest_framework/sentry_app.py @@ -7,7 +7,7 @@ from sentry.api.validators.sentry_apps.schema import validate_ui_element_schema from sentry.integrations.models.integration_feature import Feature from sentry.models.apiscopes import ApiScopes -from sentry.models.integrations.sentry_app import ( +from sentry.sentry_apps.models.sentry_app import ( REQUIRED_EVENT_PERMISSIONS, UUID_CHARS_IN_SLUG, VALID_EVENT_RESOURCES, diff --git a/src/sentry/api/serializers/rest_framework/sentry_app_request.py b/src/sentry/api/serializers/rest_framework/sentry_app_request.py index c217f6ecbd2b80..fb75908ea6f33c 100644 --- a/src/sentry/api/serializers/rest_framework/sentry_app_request.py +++ b/src/sentry/api/serializers/rest_framework/sentry_app_request.py @@ -7,9 +7,9 @@ from sentry import eventstore from sentry.api.serializers import Serializer -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.organization import Organization from sentry.models.project import Project +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.utils.sentry_apps.webhooks import TIMEOUT_STATUS_CODE diff --git a/src/sentry/auth/access.py b/src/sentry/auth/access.py index 0d17ab0bb24589..5d87b8cd814c0a 100644 --- a/src/sentry/auth/access.py +++ b/src/sentry/auth/access.py @@ -29,7 +29,6 @@ from sentry.auth.superuser import get_superuser_scopes, is_active_superuser from sentry.auth.system import SystemToken, is_system_auth from sentry.models.apikey import ApiKey -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.organization import Organization from sentry.models.organizationmember import OrganizationMember from sentry.models.organizationmemberteam import OrganizationMemberTeam @@ -39,6 +38,7 @@ from sentry.organizations.services.organization.serial import summarize_member from sentry.roles import organization_roles from sentry.roles.manager import OrganizationRole, TeamRole +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.users.models.user import User from sentry.users.services.user import RpcUser from sentry.utils import metrics diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index 91f426ee2bf9b4..2221b5f0f3e49a 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -396,6 +396,7 @@ def env( "sentry.analytics.events", "sentry.nodestore", "sentry.users", + "sentry.sentry_apps", "sentry.integrations", "sentry.monitors", "sentry.uptime", diff --git a/src/sentry/deletions/__init__.py b/src/sentry/deletions/__init__.py index a16779d60ff485..831a0ebe99a70e 100644 --- a/src/sentry/deletions/__init__.py +++ b/src/sentry/deletions/__init__.py @@ -99,6 +99,7 @@ def load_defaults() -> None: from sentry.models.commitfilechange import CommitFileChange from sentry.models.rulefirehistory import RuleFireHistory from sentry.monitors import models as monitor_models + from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.snuba import models as snuba_models from . import defaults @@ -163,7 +164,7 @@ def load_defaults() -> None: default_manager.register( RepositoryProjectPathConfig, defaults.RepositoryProjectPathConfigDeletionTask ) - default_manager.register(models.SentryApp, defaults.SentryAppDeletionTask) + default_manager.register(SentryApp, defaults.SentryAppDeletionTask) default_manager.register( models.SentryAppInstallation, defaults.SentryAppInstallationDeletionTask ) diff --git a/src/sentry/hybridcloud/apigateway/proxy.py b/src/sentry/hybridcloud/apigateway/proxy.py index a6ddeae1e5a41e..da301b31987236 100644 --- a/src/sentry/hybridcloud/apigateway/proxy.py +++ b/src/sentry/hybridcloud/apigateway/proxy.py @@ -18,9 +18,9 @@ from sentry import options from sentry.api.exceptions import RequestTimeout -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.organizationmapping import OrganizationMapping +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.silo.util import ( PROXY_DIRECT_LOCATION_HEADER, clean_outbound_headers, diff --git a/src/sentry/integrations/models/integration_feature.py b/src/sentry/integrations/models/integration_feature.py index 01709720a8c00d..de19d6af31210b 100644 --- a/src/sentry/integrations/models/integration_feature.py +++ b/src/sentry/integrations/models/integration_feature.py @@ -17,7 +17,7 @@ ) from sentry.db.models.manager.base import BaseManager from sentry.integrations.models.doc_integration import DocIntegration -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp class Feature: @@ -209,7 +209,7 @@ def feature_str(self) -> str: @property def description(self) -> str: from sentry.integrations.models.doc_integration import DocIntegration - from sentry.models.integrations.sentry_app import SentryApp + from sentry.sentry_apps.models.sentry_app import SentryApp if self.user_description: return self.user_description diff --git a/src/sentry/integrations/models/utils.py b/src/sentry/integrations/models/utils.py index 613cb04b0b93a3..3c19d174bc4334 100644 --- a/src/sentry/integrations/models/utils.py +++ b/src/sentry/integrations/models/utils.py @@ -10,7 +10,7 @@ ) from sentry.integrations.models.integration import Integration from sentry.integrations.services.integration.model import RpcIntegration - from sentry.models.integrations.sentry_app import SentryApp + from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app.model import RpcSentryApp diff --git a/src/sentry/integrations/services/integration/impl.py b/src/sentry/integrations/services/integration/impl.py index 0b87209a1ca7d7..c47cfa906d9274 100644 --- a/src/sentry/integrations/services/integration/impl.py +++ b/src/sentry/integrations/services/integration/impl.py @@ -34,9 +34,9 @@ serialize_integration_external_project, serialize_organization_integration, ) -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.rules.actions.notify_event_service import find_alert_rule_action_ui_component +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.shared_integrations.exceptions import ApiError from sentry.utils import json, metrics from sentry.utils.sentry_apps import send_and_save_webhook_request diff --git a/src/sentry/mediators/external_requests/util.py b/src/sentry/mediators/external_requests/util.py index 4258a3689a256f..13923f59430024 100644 --- a/src/sentry/mediators/external_requests/util.py +++ b/src/sentry/mediators/external_requests/util.py @@ -6,7 +6,7 @@ from requests.models import Response from sentry.http import safe_urlopen -from sentry.models.integrations.sentry_app import SentryApp, track_response_code +from sentry.sentry_apps.models.sentry_app import SentryApp, track_response_code from sentry.utils.sentry_apps import SentryAppWebhookRequestsBuffer from sentry.utils.sentry_apps.webhooks import TIMEOUT_STATUS_CODE diff --git a/src/sentry/mediators/sentry_app_installations/installation_notifier.py b/src/sentry/mediators/sentry_app_installations/installation_notifier.py index 3727e17da8e54c..462cf74395fa27 100644 --- a/src/sentry/mediators/sentry_app_installations/installation_notifier.py +++ b/src/sentry/mediators/sentry_app_installations/installation_notifier.py @@ -6,8 +6,8 @@ from sentry.mediators.mediator import Mediator from sentry.mediators.param import Param from sentry.models.apigrant import ApiGrant -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.users.services.user.model import RpcUser from sentry.utils.sentry_apps import send_and_save_webhook_request diff --git a/src/sentry/mediators/token_exchange/grant_exchanger.py b/src/sentry/mediators/token_exchange/grant_exchanger.py index 3519b19187e6d4..ac350523ca1e88 100644 --- a/src/sentry/mediators/token_exchange/grant_exchanger.py +++ b/src/sentry/mediators/token_exchange/grant_exchanger.py @@ -12,8 +12,8 @@ from sentry.models.apiapplication import ApiApplication from sentry.models.apigrant import ApiGrant from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryAppInstallation from sentry.silo.safety import unguarded_write from sentry.users.models.user import User diff --git a/src/sentry/mediators/token_exchange/refresher.py b/src/sentry/mediators/token_exchange/refresher.py index 6f731f39876200..0988719f47178c 100644 --- a/src/sentry/mediators/token_exchange/refresher.py +++ b/src/sentry/mediators/token_exchange/refresher.py @@ -9,8 +9,8 @@ from sentry.mediators.token_exchange.validator import Validator from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryAppInstallation from sentry.users.models.user import User diff --git a/src/sentry/mediators/token_exchange/validator.py b/src/sentry/mediators/token_exchange/validator.py index c6c0c1dc1d9fc0..4b88fac49e4e3b 100644 --- a/src/sentry/mediators/token_exchange/validator.py +++ b/src/sentry/mediators/token_exchange/validator.py @@ -5,7 +5,7 @@ from sentry.mediators.mediator import Mediator from sentry.mediators.param import Param from sentry.models.apiapplication import ApiApplication -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryAppInstallation from sentry.users.models.user import User diff --git a/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py b/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py index 21be74b41bd569..aec705c0495561 100644 --- a/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py +++ b/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py @@ -31,10 +31,10 @@ import sentry.models.apitoken import sentry.models.broadcast import sentry.models.groupshare -import sentry.models.integrations.sentry_app import sentry.models.integrations.sentry_app_installation import sentry.models.scheduledeletion import sentry.models.servicehook +import sentry.sentry_apps.models.sentry_app import sentry.users.models.authenticator import sentry.users.models.user import sentry.utils.security.hash @@ -1842,7 +1842,7 @@ class Migration(CheckedMigration): ( "uuid", models.CharField( - default=sentry.models.integrations.sentry_app.default_uuid, max_length=64 + default=sentry.sentry_apps.models.sentry_app.default_uuid, max_length=64 ), ), ("redirect_url", models.URLField(null=True)), diff --git a/src/sentry/models/avatars/sentry_app_avatar.py b/src/sentry/models/avatars/sentry_app_avatar.py index a6d1e78e55bf31..8d517fd615c171 100644 --- a/src/sentry/models/avatars/sentry_app_avatar.py +++ b/src/sentry/models/avatars/sentry_app_avatar.py @@ -13,7 +13,7 @@ from . import ControlAvatarBase if TYPE_CHECKING: - from sentry.models.integrations.sentry_app import SentryApp + from sentry.sentry_apps.models.sentry_app import SentryApp class SentryAppAvatarTypes(Enum): diff --git a/src/sentry/models/integrations/__init__.py b/src/sentry/models/integrations/__init__.py index 19c56736fce763..bd3e6b4842da80 100644 --- a/src/sentry/models/integrations/__init__.py +++ b/src/sentry/models/integrations/__init__.py @@ -9,10 +9,10 @@ # REQUIRED for migrations to run. from sentry.integrations.types import ExternalProviders # NOQA -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_component import SentryAppComponent from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.integrations.sentry_app_installation_for_provider import ( SentryAppInstallationForProvider, ) from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken +from sentry.sentry_apps.models.sentry_app import SentryApp diff --git a/src/sentry/models/integrations/sentry_app.py b/src/sentry/models/integrations/sentry_app.py index 84d04faa63fece..db625c60ee0d34 100644 --- a/src/sentry/models/integrations/sentry_app.py +++ b/src/sentry/models/integrations/sentry_app.py @@ -1,263 +1,3 @@ -import hmac -import itertools -import uuid -from hashlib import sha256 -from typing import Any, ClassVar +from sentry.sentry_apps.models.sentry_app import SentryApp -from django.db import models, router, transaction -from django.db.models import QuerySet -from django.utils import timezone -from rest_framework.request import Request - -from sentry.backup.dependencies import NormalizedModelName, get_model_name -from sentry.backup.sanitize import SanitizableField, Sanitizer -from sentry.backup.scopes import RelocationScope -from sentry.constants import ( - SENTRY_APP_SLUG_MAX_LENGTH, - SentryAppInstallationStatus, - SentryAppStatus, -) -from sentry.db.models import ( - ArrayField, - BoundedPositiveIntegerField, - FlexibleForeignKey, - Model, - control_silo_model, -) -from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey -from sentry.db.models.fields.jsonfield import JSONField -from sentry.db.models.fields.slug import SentrySlugField -from sentry.db.models.paranoia import ParanoidManager, ParanoidModel -from sentry.hybridcloud.models.outbox import ControlOutbox, outbox_context -from sentry.hybridcloud.outbox.category import OutboxCategory, OutboxScope -from sentry.models.apiscopes import HasApiScopes -from sentry.types.region import find_all_region_names -from sentry.utils import metrics - -# When a developer selects to receive " Webhooks" it really means -# listening to a list of specific events. This is a mapping of what those -# specific events are for each resource. -EVENT_EXPANSION = { - "issue": [ - "issue.created", - "issue.resolved", - "issue.ignored", - "issue.assigned", - "issue.unresolved", - ], - "error": ["error.created"], - "comment": ["comment.created", "comment.updated", "comment.deleted"], -} - -# We present Webhook Subscriptions per-resource (Issue, Project, etc.), not -# per-event-type (issue.created, project.deleted, etc.). These are valid -# resources a Sentry App may subscribe to. -VALID_EVENT_RESOURCES = ("issue", "error", "comment") - -REQUIRED_EVENT_PERMISSIONS = { - "issue": "event:read", - "error": "event:read", - "project": "project:read", - "member": "member:read", - "organization": "org:read", - "team": "team:read", - "comment": "event:read", -} - -# The only events valid for Sentry Apps are the ones listed in the values of -# EVENT_EXPANSION above. This list is likely a subset of all valid ServiceHook -# events. -VALID_EVENTS = tuple(itertools.chain(*EVENT_EXPANSION.values())) - -MASKED_VALUE = "*" * 64 - -UUID_CHARS_IN_SLUG = 6 - - -def default_uuid(): - return str(uuid.uuid4()) - - -def track_response_code(status, integration_slug, webhook_event): - metrics.incr( - "integration-platform.http_response", - sample_rate=1.0, - tags={"status": status, "integration": integration_slug, "webhook_event": webhook_event}, - ) - - -class SentryAppManager(ParanoidManager["SentryApp"]): - def get_alertable_sentry_apps(self, organization_id: int) -> QuerySet: - return self.filter( - installations__organization_id=organization_id, - is_alertable=True, - installations__status=SentryAppInstallationStatus.INSTALLED, - installations__date_deleted=None, - ).distinct() - - def visible_for_user(self, request: Request) -> QuerySet: - from sentry.auth.superuser import is_active_superuser - - if is_active_superuser(request): - return self.all() - - return self.filter(status=SentryAppStatus.PUBLISHED) - - -@control_silo_model -class SentryApp(ParanoidModel, HasApiScopes, Model): - __relocation_scope__ = RelocationScope.Global - - application = models.OneToOneField( - "sentry.ApiApplication", null=True, on_delete=models.SET_NULL, related_name="sentry_app" - ) - - # Much of the OAuth system in place currently depends on a User existing. - # This "proxy user" represents the SentryApp in those cases. - proxy_user = models.OneToOneField( - "sentry.User", null=True, on_delete=models.SET_NULL, related_name="sentry_app" - ) - - # The Organization the Sentry App was created in "owns" it. Members of that - # Org have differing access, dependent on their role within the Org. - owner_id = HybridCloudForeignKey("sentry.Organization", on_delete="CASCADE") - - name = models.TextField() - slug = SentrySlugField(max_length=SENTRY_APP_SLUG_MAX_LENGTH, unique=True, db_index=False) - author = models.TextField(null=True) - status = BoundedPositiveIntegerField( - default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True - ) - uuid = models.CharField(max_length=64, default=default_uuid) - - redirect_url = models.URLField(null=True) - webhook_url = models.URLField(max_length=512, null=True) - # does the application subscribe to `event.alert`, - # meaning can it be used in alert rules as a {service} ? - is_alertable = models.BooleanField(default=False) - - # does the application need to wait for verification - # on behalf of the external service to know if its installations - # are successfully installed ? - verify_install = models.BooleanField(default=True) - - events = ArrayField(of=models.TextField, null=True) - - overview = models.TextField(null=True) - schema = JSONField(default=dict) - - date_added = models.DateTimeField(default=timezone.now) - date_updated = models.DateTimeField(default=timezone.now) - date_published = models.DateTimeField(null=True, blank=True) - - creator_user = FlexibleForeignKey( - "sentry.User", null=True, on_delete=models.SET_NULL, db_constraint=False - ) - creator_label = models.TextField(null=True) - - popularity = models.PositiveSmallIntegerField(null=True, default=1) - metadata = JSONField(default=dict) - - objects: ClassVar[SentryAppManager] = SentryAppManager() - - class Meta: - app_label = "sentry" - db_table = "sentry_sentryapp" - - @property - def is_published(self): - return self.status == SentryAppStatus.PUBLISHED - - @property - def is_unpublished(self): - return self.status == SentryAppStatus.UNPUBLISHED - - @property - def is_internal(self): - return self.status == SentryAppStatus.INTERNAL - - @property - def is_publish_request_inprogress(self): - return self.status == SentryAppStatus.PUBLISH_REQUEST_INPROGRESS - - @property - def slug_for_metrics(self): - if self.is_internal: - return "internal" - if self.is_unpublished: - return "unpublished" - return self.slug - - def save(self, *args, **kwargs): - self.date_updated = timezone.now() - with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp)), flush=False): - result = super().save(*args, **kwargs) - for outbox in self.outboxes_for_update(): - outbox.save() - return result - - def update(self, *args, **kwargs): - with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp)), flush=False): - result = super().update(*args, **kwargs) - for outbox in self.outboxes_for_update(): - outbox.save() - return result - - def is_installed_on(self, organization): - from sentry.models.integrations.sentry_app_installation import SentryAppInstallation - - return SentryAppInstallation.objects.filter( - organization_id=organization.id, - sentry_app=self, - ).exists() - - def build_signature(self, body): - assert self.application is not None - secret = self.application.client_secret - return hmac.new( - key=secret.encode("utf-8"), msg=body.encode("utf-8"), digestmod=sha256 - ).hexdigest() - - def show_auth_info(self, access): - encoded_scopes = set({"%s" % scope for scope in list(access.scopes)}) - return set(self.scope_list).issubset(encoded_scopes) - - def outboxes_for_update(self) -> list[ControlOutbox]: - return [ - ControlOutbox( - shard_scope=OutboxScope.APP_SCOPE, - shard_identifier=self.id, - object_identifier=self.id, - category=OutboxCategory.SENTRY_APP_UPDATE, - region_name=region_name, - ) - for region_name in find_all_region_names() - ] - - def delete(self, *args, **kwargs): - from sentry.models.avatars.sentry_app_avatar import SentryAppAvatar - - with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp))): - for outbox in self.outboxes_for_update(): - outbox.save() - - SentryAppAvatar.objects.filter(sentry_app=self).delete() - return super().delete(*args, **kwargs) - - def _disable(self): - self.events = [] - self.save(update_fields=["events"]) - - @classmethod - def sanitize_relocation_json( - cls, json: Any, sanitizer: Sanitizer, model_name: NormalizedModelName | None = None - ) -> None: - model_name = get_model_name(cls) if model_name is None else model_name - super().sanitize_relocation_json(json, sanitizer, model_name) - - sanitizer.set_string(json, SanitizableField(model_name, "author")) - sanitizer.set_string(json, SanitizableField(model_name, "creator_label")) - sanitizer.set_json(json, SanitizableField(model_name, "metadata"), {}) - sanitizer.set_string(json, SanitizableField(model_name, "overview")) - sanitizer.set_json(json, SanitizableField(model_name, "schema"), {}) - json["fields"]["events"] = "[]" +__all__ = ("SentryApp",) diff --git a/src/sentry/models/integrations/sentry_app_installation.py b/src/sentry/models/integrations/sentry_app_installation.py index 845bd30f30c926..9faee1f3f2a0ed 100644 --- a/src/sentry/models/integrations/sentry_app_installation.py +++ b/src/sentry/models/integrations/sentry_app_installation.py @@ -170,7 +170,7 @@ def save(self, *args, **kwargs): @property def api_application_id(self) -> int | None: - from sentry.models.integrations.sentry_app import SentryApp + from sentry.sentry_apps.models.sentry_app import SentryApp try: return self.sentry_app.application_id diff --git a/src/sentry/receivers/outbox/control.py b/src/sentry/receivers/outbox/control.py index a6326373de373b..b826a66ecedcf2 100644 --- a/src/sentry/receivers/outbox/control.py +++ b/src/sentry/receivers/outbox/control.py @@ -22,12 +22,12 @@ from sentry.issues.services.issue import issue_service from sentry.models.apiapplication import ApiApplication from sentry.models.files.utils import get_relocation_storage -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.organizationmapping import OrganizationMapping from sentry.organizations.services.organization import RpcOrganizationSignal, organization_service from sentry.receivers.outbox import maybe_process_tombstone from sentry.relocation.services.relocation_export.service import region_relocation_export_service +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app.service import get_by_application_id, get_installation logger = logging.getLogger(__name__) diff --git a/src/sentry/sentry_apps/apps.py b/src/sentry/sentry_apps/apps.py deleted file mode 100644 index 9926fd95f7ab12..00000000000000 --- a/src/sentry/sentry_apps/apps.py +++ /dev/null @@ -1,3 +0,0 @@ -from sentry.sentry_apps.logic import SentryAppUpdater - -__all__ = ("SentryAppUpdater",) diff --git a/src/sentry/sentry_apps/installations.py b/src/sentry/sentry_apps/installations.py index 599bb70ecf4759..14a8532bc81862 100644 --- a/src/sentry/sentry_apps/installations.py +++ b/src/sentry/sentry_apps/installations.py @@ -13,9 +13,9 @@ from sentry.models.apiapplication import ApiApplication from sentry.models.apigrant import ApiGrant from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.hook import hook_service from sentry.tasks.sentry_apps import installation_webhook from sentry.users.models.user import User diff --git a/src/sentry/sentry_apps/logic.py b/src/sentry/sentry_apps/logic.py index 6adcd4c797869c..6dbd83f5315bb4 100644 --- a/src/sentry/sentry_apps/logic.py +++ b/src/sentry/sentry_apps/logic.py @@ -24,19 +24,19 @@ from sentry.models.apiapplication import ApiApplication from sentry.models.apiscopes import add_scope_hierarchy from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import ( - EVENT_EXPANSION, - REQUIRED_EVENT_PERMISSIONS, - UUID_CHARS_IN_SLUG, - SentryApp, - default_uuid, -) from sentry.models.integrations.sentry_app_component import SentryAppComponent from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.installations import ( SentryAppInstallationCreator, SentryAppInstallationTokenCreator, ) +from sentry.sentry_apps.models.sentry_app import ( + EVENT_EXPANSION, + REQUIRED_EVENT_PERMISSIONS, + UUID_CHARS_IN_SLUG, + SentryApp, + default_uuid, +) from sentry.tasks.sentry_apps import create_or_update_service_hooks_for_sentry_app from sentry.users.models.user import User from sentry.users.services.user.model import RpcUser diff --git a/src/sentry/sentry_apps/models/__init__.py b/src/sentry/sentry_apps/models/__init__.py new file mode 100644 index 00000000000000..7429e9d8142365 --- /dev/null +++ b/src/sentry/sentry_apps/models/__init__.py @@ -0,0 +1,3 @@ +from .sentry_app import SentryApp + +__all__ = ("SentryApp",) diff --git a/src/sentry/sentry_apps/models/sentry_app.py b/src/sentry/sentry_apps/models/sentry_app.py new file mode 100644 index 00000000000000..84d04faa63fece --- /dev/null +++ b/src/sentry/sentry_apps/models/sentry_app.py @@ -0,0 +1,263 @@ +import hmac +import itertools +import uuid +from hashlib import sha256 +from typing import Any, ClassVar + +from django.db import models, router, transaction +from django.db.models import QuerySet +from django.utils import timezone +from rest_framework.request import Request + +from sentry.backup.dependencies import NormalizedModelName, get_model_name +from sentry.backup.sanitize import SanitizableField, Sanitizer +from sentry.backup.scopes import RelocationScope +from sentry.constants import ( + SENTRY_APP_SLUG_MAX_LENGTH, + SentryAppInstallationStatus, + SentryAppStatus, +) +from sentry.db.models import ( + ArrayField, + BoundedPositiveIntegerField, + FlexibleForeignKey, + Model, + control_silo_model, +) +from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey +from sentry.db.models.fields.jsonfield import JSONField +from sentry.db.models.fields.slug import SentrySlugField +from sentry.db.models.paranoia import ParanoidManager, ParanoidModel +from sentry.hybridcloud.models.outbox import ControlOutbox, outbox_context +from sentry.hybridcloud.outbox.category import OutboxCategory, OutboxScope +from sentry.models.apiscopes import HasApiScopes +from sentry.types.region import find_all_region_names +from sentry.utils import metrics + +# When a developer selects to receive " Webhooks" it really means +# listening to a list of specific events. This is a mapping of what those +# specific events are for each resource. +EVENT_EXPANSION = { + "issue": [ + "issue.created", + "issue.resolved", + "issue.ignored", + "issue.assigned", + "issue.unresolved", + ], + "error": ["error.created"], + "comment": ["comment.created", "comment.updated", "comment.deleted"], +} + +# We present Webhook Subscriptions per-resource (Issue, Project, etc.), not +# per-event-type (issue.created, project.deleted, etc.). These are valid +# resources a Sentry App may subscribe to. +VALID_EVENT_RESOURCES = ("issue", "error", "comment") + +REQUIRED_EVENT_PERMISSIONS = { + "issue": "event:read", + "error": "event:read", + "project": "project:read", + "member": "member:read", + "organization": "org:read", + "team": "team:read", + "comment": "event:read", +} + +# The only events valid for Sentry Apps are the ones listed in the values of +# EVENT_EXPANSION above. This list is likely a subset of all valid ServiceHook +# events. +VALID_EVENTS = tuple(itertools.chain(*EVENT_EXPANSION.values())) + +MASKED_VALUE = "*" * 64 + +UUID_CHARS_IN_SLUG = 6 + + +def default_uuid(): + return str(uuid.uuid4()) + + +def track_response_code(status, integration_slug, webhook_event): + metrics.incr( + "integration-platform.http_response", + sample_rate=1.0, + tags={"status": status, "integration": integration_slug, "webhook_event": webhook_event}, + ) + + +class SentryAppManager(ParanoidManager["SentryApp"]): + def get_alertable_sentry_apps(self, organization_id: int) -> QuerySet: + return self.filter( + installations__organization_id=organization_id, + is_alertable=True, + installations__status=SentryAppInstallationStatus.INSTALLED, + installations__date_deleted=None, + ).distinct() + + def visible_for_user(self, request: Request) -> QuerySet: + from sentry.auth.superuser import is_active_superuser + + if is_active_superuser(request): + return self.all() + + return self.filter(status=SentryAppStatus.PUBLISHED) + + +@control_silo_model +class SentryApp(ParanoidModel, HasApiScopes, Model): + __relocation_scope__ = RelocationScope.Global + + application = models.OneToOneField( + "sentry.ApiApplication", null=True, on_delete=models.SET_NULL, related_name="sentry_app" + ) + + # Much of the OAuth system in place currently depends on a User existing. + # This "proxy user" represents the SentryApp in those cases. + proxy_user = models.OneToOneField( + "sentry.User", null=True, on_delete=models.SET_NULL, related_name="sentry_app" + ) + + # The Organization the Sentry App was created in "owns" it. Members of that + # Org have differing access, dependent on their role within the Org. + owner_id = HybridCloudForeignKey("sentry.Organization", on_delete="CASCADE") + + name = models.TextField() + slug = SentrySlugField(max_length=SENTRY_APP_SLUG_MAX_LENGTH, unique=True, db_index=False) + author = models.TextField(null=True) + status = BoundedPositiveIntegerField( + default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True + ) + uuid = models.CharField(max_length=64, default=default_uuid) + + redirect_url = models.URLField(null=True) + webhook_url = models.URLField(max_length=512, null=True) + # does the application subscribe to `event.alert`, + # meaning can it be used in alert rules as a {service} ? + is_alertable = models.BooleanField(default=False) + + # does the application need to wait for verification + # on behalf of the external service to know if its installations + # are successfully installed ? + verify_install = models.BooleanField(default=True) + + events = ArrayField(of=models.TextField, null=True) + + overview = models.TextField(null=True) + schema = JSONField(default=dict) + + date_added = models.DateTimeField(default=timezone.now) + date_updated = models.DateTimeField(default=timezone.now) + date_published = models.DateTimeField(null=True, blank=True) + + creator_user = FlexibleForeignKey( + "sentry.User", null=True, on_delete=models.SET_NULL, db_constraint=False + ) + creator_label = models.TextField(null=True) + + popularity = models.PositiveSmallIntegerField(null=True, default=1) + metadata = JSONField(default=dict) + + objects: ClassVar[SentryAppManager] = SentryAppManager() + + class Meta: + app_label = "sentry" + db_table = "sentry_sentryapp" + + @property + def is_published(self): + return self.status == SentryAppStatus.PUBLISHED + + @property + def is_unpublished(self): + return self.status == SentryAppStatus.UNPUBLISHED + + @property + def is_internal(self): + return self.status == SentryAppStatus.INTERNAL + + @property + def is_publish_request_inprogress(self): + return self.status == SentryAppStatus.PUBLISH_REQUEST_INPROGRESS + + @property + def slug_for_metrics(self): + if self.is_internal: + return "internal" + if self.is_unpublished: + return "unpublished" + return self.slug + + def save(self, *args, **kwargs): + self.date_updated = timezone.now() + with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp)), flush=False): + result = super().save(*args, **kwargs) + for outbox in self.outboxes_for_update(): + outbox.save() + return result + + def update(self, *args, **kwargs): + with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp)), flush=False): + result = super().update(*args, **kwargs) + for outbox in self.outboxes_for_update(): + outbox.save() + return result + + def is_installed_on(self, organization): + from sentry.models.integrations.sentry_app_installation import SentryAppInstallation + + return SentryAppInstallation.objects.filter( + organization_id=organization.id, + sentry_app=self, + ).exists() + + def build_signature(self, body): + assert self.application is not None + secret = self.application.client_secret + return hmac.new( + key=secret.encode("utf-8"), msg=body.encode("utf-8"), digestmod=sha256 + ).hexdigest() + + def show_auth_info(self, access): + encoded_scopes = set({"%s" % scope for scope in list(access.scopes)}) + return set(self.scope_list).issubset(encoded_scopes) + + def outboxes_for_update(self) -> list[ControlOutbox]: + return [ + ControlOutbox( + shard_scope=OutboxScope.APP_SCOPE, + shard_identifier=self.id, + object_identifier=self.id, + category=OutboxCategory.SENTRY_APP_UPDATE, + region_name=region_name, + ) + for region_name in find_all_region_names() + ] + + def delete(self, *args, **kwargs): + from sentry.models.avatars.sentry_app_avatar import SentryAppAvatar + + with outbox_context(transaction.atomic(using=router.db_for_write(SentryApp))): + for outbox in self.outboxes_for_update(): + outbox.save() + + SentryAppAvatar.objects.filter(sentry_app=self).delete() + return super().delete(*args, **kwargs) + + def _disable(self): + self.events = [] + self.save(update_fields=["events"]) + + @classmethod + def sanitize_relocation_json( + cls, json: Any, sanitizer: Sanitizer, model_name: NormalizedModelName | None = None + ) -> None: + model_name = get_model_name(cls) if model_name is None else model_name + super().sanitize_relocation_json(json, sanitizer, model_name) + + sanitizer.set_string(json, SanitizableField(model_name, "author")) + sanitizer.set_string(json, SanitizableField(model_name, "creator_label")) + sanitizer.set_json(json, SanitizableField(model_name, "metadata"), {}) + sanitizer.set_string(json, SanitizableField(model_name, "overview")) + sanitizer.set_json(json, SanitizableField(model_name, "schema"), {}) + json["fields"]["events"] = "[]" diff --git a/src/sentry/sentry_apps/services/app/impl.py b/src/sentry/sentry_apps/services/app/impl.py index 4b5e2d3a97cc69..81ffa6e90fd1d7 100644 --- a/src/sentry/sentry_apps/services/app/impl.py +++ b/src/sentry/sentry_apps/services/app/impl.py @@ -11,7 +11,6 @@ from sentry.constants import SentryAppInstallationStatus, SentryAppStatus from sentry.hybridcloud.rpc.filter_query import FilterQueryDatabaseImpl, OpaqueSerializedResponse from sentry.mediators import alert_rule_actions -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_component import SentryAppComponent from sentry.models.integrations.sentry_app_installation import ( SentryAppInstallation, @@ -19,6 +18,7 @@ ) from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken from sentry.sentry_apps.logic import SentryAppCreator +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import ( AppService, RpcAlertRuleActionResult, diff --git a/src/sentry/sentry_apps/services/app/serial.py b/src/sentry/sentry_apps/services/app/serial.py index d39a4989fc56cb..0f06d3d20729c4 100644 --- a/src/sentry/sentry_apps/services/app/serial.py +++ b/src/sentry/sentry_apps/services/app/serial.py @@ -2,8 +2,8 @@ from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken from sentry.models.integrations import SentryAppComponent -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import ( RpcApiApplication, RpcSentryApp, diff --git a/src/sentry/tasks/sentry_apps.py b/src/sentry/tasks/sentry_apps.py index 0d2fec7202bb92..a664fac1389e7d 100644 --- a/src/sentry/tasks/sentry_apps.py +++ b/src/sentry/tasks/sentry_apps.py @@ -14,11 +14,11 @@ from sentry.eventstore.models import Event, GroupEvent from sentry.models.activity import Activity from sentry.models.group import Group -from sentry.models.integrations.sentry_app import VALID_EVENTS from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.organization import Organization from sentry.models.project import Project from sentry.models.servicehook import ServiceHook, ServiceHookProject +from sentry.sentry_apps.models.sentry_app import VALID_EVENTS from sentry.sentry_apps.services.app.service import app_service from sentry.shared_integrations.exceptions import ApiHostError, ApiTimeoutError, ClientError from sentry.silo.base import SiloMode diff --git a/src/sentry/testutils/factories.py b/src/sentry/testutils/factories.py index 2e53ff5bea081f..dbdc21980a3b91 100644 --- a/src/sentry/testutils/factories.py +++ b/src/sentry/testutils/factories.py @@ -101,7 +101,6 @@ from sentry.models.grouphistory import GroupHistory from sentry.models.grouplink import GroupLink from sentry.models.grouprelease import GroupRelease -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.integrations.sentry_app_installation_for_provider import ( SentryAppInstallationForProvider, @@ -142,6 +141,7 @@ SentryAppInstallationTokenCreator, ) from sentry.sentry_apps.logic import SentryAppCreator +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app.serial import serialize_sentry_app_installation from sentry.sentry_apps.services.hook import hook_service from sentry.signals import project_created diff --git a/src/sentry/testutils/helpers/backups.py b/src/sentry/testutils/helpers/backups.py index 10378c9000cf08..7b6f60ec7d0bbc 100644 --- a/src/sentry/testutils/helpers/backups.py +++ b/src/sentry/testutils/helpers/backups.py @@ -78,7 +78,6 @@ from sentry.models.groupseen import GroupSeen from sentry.models.groupshare import GroupShare from sentry.models.groupsubscription import GroupSubscription -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.options.option import ControlOption, Option from sentry.models.options.organization_option import OrganizationOption from sentry.models.options.project_template_option import ProjectTemplateOption @@ -98,6 +97,7 @@ from sentry.monitors.models import Monitor, MonitorType, ScheduleType from sentry.nodestore.django.models import Node from sentry.sentry_apps.logic import SentryAppUpdater +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.silo.base import SiloMode from sentry.silo.safety import unguarded_write from sentry.testutils.cases import TestCase, TransactionTestCase diff --git a/src/sentry/utils/sentry_apps/request_buffer.py b/src/sentry/utils/sentry_apps/request_buffer.py index 359c027b5c7680..b412198254d96c 100644 --- a/src/sentry/utils/sentry_apps/request_buffer.py +++ b/src/sentry/utils/sentry_apps/request_buffer.py @@ -10,11 +10,11 @@ from redis.client import Pipeline from requests.models import Response -from sentry.models.integrations.sentry_app import VALID_EVENTS +from sentry.sentry_apps.models.sentry_app import VALID_EVENTS from sentry.utils import json, redis if TYPE_CHECKING: - from sentry.models.integrations.sentry_app import SentryApp + from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app.model import RpcSentryApp BUFFER_SIZE = 100 diff --git a/src/sentry/utils/sentry_apps/webhooks.py b/src/sentry/utils/sentry_apps/webhooks.py index f08f75a63f452c..8e35192a329b44 100644 --- a/src/sentry/utils/sentry_apps/webhooks.py +++ b/src/sentry/utils/sentry_apps/webhooks.py @@ -14,8 +14,8 @@ from sentry.integrations.models.utils import get_redis_key from sentry.integrations.notify_disable import notify_disable from sentry.integrations.request_buffer import IntegrationRequestBuffer -from sentry.models.integrations.sentry_app import SentryApp, track_response_code from sentry.models.organization import Organization +from sentry.sentry_apps.models.sentry_app import SentryApp, track_response_code from sentry.shared_integrations.exceptions import ApiHostError, ApiTimeoutError, ClientError from sentry.utils.audit import create_system_audit_entry from sentry.utils.sentry_apps import SentryAppWebhookRequestsBuffer diff --git a/src/sentry/web/frontend/debug/debug_sentry_app_notify_disable.py b/src/sentry/web/frontend/debug/debug_sentry_app_notify_disable.py index b014935f18d1ba..a919a993d464e5 100644 --- a/src/sentry/web/frontend/debug/debug_sentry_app_notify_disable.py +++ b/src/sentry/web/frontend/debug/debug_sentry_app_notify_disable.py @@ -3,9 +3,9 @@ from sentry.constants import SentryAppStatus from sentry.integrations.notify_disable import get_provider_type, get_url -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.organization import Organization +from sentry.sentry_apps.models.sentry_app import SentryApp from .mail import MailPreview @@ -38,9 +38,11 @@ def get(self, request: HttpRequest) -> HttpResponse: context={ "integration_name": integration_name, "integration_link": integration_link, - "webhook_url": self.sentry_app.webhook_url - if "sentry-app" in redis_key and self.sentry_app.webhook_url - else "", + "webhook_url": ( + self.sentry_app.webhook_url + if "sentry-app" in redis_key and self.sentry_app.webhook_url + else "" + ), "dashboard_link": f"{integration_link}dashboard/", }, ).render(request) diff --git a/tests/sentry/api/endpoints/test_organization_sentry_apps.py b/tests/sentry/api/endpoints/test_organization_sentry_apps.py index b258fe40dca262..1310343aa60a95 100644 --- a/tests/sentry/api/endpoints/test_organization_sentry_apps.py +++ b/tests/sentry/api/endpoints/test_organization_sentry_apps.py @@ -1,7 +1,7 @@ import orjson from django.urls import reverse -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import APITestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/api/endpoints/test_sentry_app_components.py b/tests/sentry/api/endpoints/test_sentry_app_components.py index 5388f6c079be16..d866ddf7d3f4eb 100644 --- a/tests/sentry/api/endpoints/test_sentry_app_components.py +++ b/tests/sentry/api/endpoints/test_sentry_app_components.py @@ -3,7 +3,7 @@ from sentry.api.serializers.base import serialize from sentry.constants import SentryAppInstallationStatus from sentry.coreapi import APIError -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import APITestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/api/endpoints/test_sentry_app_details.py b/tests/sentry/api/endpoints/test_sentry_app_details.py index 14bbb62136d70a..9344b03c073a94 100644 --- a/tests/sentry/api/endpoints/test_sentry_app_details.py +++ b/tests/sentry/api/endpoints/test_sentry_app_details.py @@ -8,10 +8,10 @@ ) from sentry.constants import SentryAppStatus from sentry.models.auditlogentry import AuditLogEntry -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.organizationmember import OrganizationMember from sentry.models.servicehook import ServiceHook +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.helpers import with_feature diff --git a/tests/sentry/api/endpoints/test_sentry_app_rotate_secret.py b/tests/sentry/api/endpoints/test_sentry_app_rotate_secret.py index e90e4b407428d6..cc3960af6c58ee 100644 --- a/tests/sentry/api/endpoints/test_sentry_app_rotate_secret.py +++ b/tests/sentry/api/endpoints/test_sentry_app_rotate_secret.py @@ -1,7 +1,7 @@ from django.urls import reverse from sentry.models.apiapplication import ApiApplication -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import APITestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/api/endpoints/test_sentry_apps.py b/tests/sentry/api/endpoints/test_sentry_apps.py index 31ecdbef022aba..0dec7b8e473cca 100644 --- a/tests/sentry/api/endpoints/test_sentry_apps.py +++ b/tests/sentry/api/endpoints/test_sentry_apps.py @@ -14,11 +14,11 @@ 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.sentry_apps.models.sentry_app import MASKED_VALUE, SentryApp from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.helpers import Feature, with_feature diff --git a/tests/sentry/api/endpoints/test_sentry_internal_app_tokens.py b/tests/sentry/api/endpoints/test_sentry_internal_app_tokens.py index 75de54aa6f5798..20f2971b316248 100644 --- a/tests/sentry/api/endpoints/test_sentry_internal_app_tokens.py +++ b/tests/sentry/api/endpoints/test_sentry_internal_app_tokens.py @@ -2,7 +2,7 @@ from rest_framework import status from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import MASKED_VALUE +from sentry.sentry_apps.models.sentry_app import MASKED_VALUE from sentry.testutils.cases import APITestCase from sentry.testutils.helpers.options import override_options from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/deletions/test_sentry_app.py b/tests/sentry/deletions/test_sentry_app.py index af2b7952652b1b..6df4c829621073 100644 --- a/tests/sentry/deletions/test_sentry_app.py +++ b/tests/sentry/deletions/test_sentry_app.py @@ -3,8 +3,8 @@ from sentry import deletions from sentry.models.apiapplication import ApiApplication -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test from sentry.users.models.user import User diff --git a/tests/sentry/mediators/token_exchange/test_grant_exchanger.py b/tests/sentry/mediators/token_exchange/test_grant_exchanger.py index 94d402452afe46..c408d0e399ff60 100644 --- a/tests/sentry/mediators/token_exchange/test_grant_exchanger.py +++ b/tests/sentry/mediators/token_exchange/test_grant_exchanger.py @@ -7,8 +7,8 @@ from sentry.mediators.token_exchange.grant_exchanger import GrantExchanger from sentry.models.apiapplication import ApiApplication from sentry.models.apigrant import ApiGrant -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import app_service from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/mediators/token_exchange/test_refresher.py b/tests/sentry/mediators/token_exchange/test_refresher.py index 09ee97e8d7f1da..8dfa2d4e747409 100644 --- a/tests/sentry/mediators/token_exchange/test_refresher.py +++ b/tests/sentry/mediators/token_exchange/test_refresher.py @@ -6,8 +6,8 @@ from sentry.mediators.token_exchange.refresher import Refresher from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import app_service from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/mediators/token_exchange/test_validator.py b/tests/sentry/mediators/token_exchange/test_validator.py index fdfb8f83378474..1d81eaae8f892e 100644 --- a/tests/sentry/mediators/token_exchange/test_validator.py +++ b/tests/sentry/mediators/token_exchange/test_validator.py @@ -4,7 +4,7 @@ from sentry.coreapi import APIUnauthorized from sentry.mediators.token_exchange.validator import Validator -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import app_service from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/models/test_sentryappinstallation.py b/tests/sentry/models/test_sentryappinstallation.py index e21920e9ec3a65..f41f2f844c7c10 100644 --- a/tests/sentry/models/test_sentryappinstallation.py +++ b/tests/sentry/models/test_sentryappinstallation.py @@ -2,8 +2,8 @@ import sentry.hybridcloud.rpc.caching as caching_module from sentry.models.apiapplication import ApiApplication -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test from sentry.types.region import get_region_for_organization diff --git a/tests/sentry/models/test_sentryapp.py b/tests/sentry/sentry_apps/models/test_sentryapp.py similarity index 98% rename from tests/sentry/models/test_sentryapp.py rename to tests/sentry/sentry_apps/models/test_sentryapp.py index b67b13d6e5d57e..68a30e115aab66 100644 --- a/tests/sentry/models/test_sentryapp.py +++ b/tests/sentry/sentry_apps/models/test_sentryapp.py @@ -2,7 +2,7 @@ from sentry.hybridcloud.models.outbox import ControlOutbox from sentry.hybridcloud.outbox.category import OutboxCategory from sentry.models.apiapplication import ApiApplication -from sentry.models.integrations.sentry_app import SentryApp +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/sentry_apps/services/test_hook_service.py b/tests/sentry/sentry_apps/services/test_hook_service.py index 6868fc1556bb2c..f6653a10422778 100644 --- a/tests/sentry/sentry_apps/services/test_hook_service.py +++ b/tests/sentry/sentry_apps/services/test_hook_service.py @@ -1,6 +1,6 @@ -from sentry.models.integrations.sentry_app import EVENT_EXPANSION from sentry.models.servicehook import ServiceHook from sentry.sentry_apps.logic import consolidate_events, expand_events +from sentry.sentry_apps.models.sentry_app import EVENT_EXPANSION from sentry.sentry_apps.services.hook import RpcServiceHook, hook_service from sentry.silo.base import SiloMode from sentry.testutils.cases import TestCase diff --git a/tests/sentry/sentry_apps/test_sentry_app_creator.py b/tests/sentry/sentry_apps/test_sentry_app_creator.py index c80a6b5a3fc3e9..961f42fd8ff8f5 100644 --- a/tests/sentry/sentry_apps/test_sentry_app_creator.py +++ b/tests/sentry/sentry_apps/test_sentry_app_creator.py @@ -6,10 +6,10 @@ from sentry.integrations.models.integration_feature import IntegrationFeature, IntegrationTypes from sentry.models.apiapplication import ApiApplication from sentry.models.auditlogentry import AuditLogEntry -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_component import SentryAppComponent from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.logic import SentryAppCreator +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test from sentry.users.models.user import User diff --git a/tests/sentry/sentry_apps/test_sentry_app_updater.py b/tests/sentry/sentry_apps/test_sentry_app_updater.py index 6c6dff003570d9..188d8471c0d7a3 100644 --- a/tests/sentry/sentry_apps/test_sentry_app_updater.py +++ b/tests/sentry/sentry_apps/test_sentry_app_updater.py @@ -6,10 +6,10 @@ from sentry.constants import SentryAppStatus from sentry.coreapi import APIError from sentry.models.apitoken import ApiToken -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_component import SentryAppComponent from sentry.models.servicehook import ServiceHook from sentry.sentry_apps.logic import SentryAppUpdater, expand_events +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.silo.base import SiloMode from sentry.testutils.cases import TestCase from sentry.testutils.silo import assume_test_silo_mode, control_silo_test diff --git a/tests/sentry/tasks/test_sentry_apps.py b/tests/sentry/tasks/test_sentry_apps.py index 16d083adf9e10a..6f9e012e1398c6 100644 --- a/tests/sentry/tasks/test_sentry_apps.py +++ b/tests/sentry/tasks/test_sentry_apps.py @@ -17,9 +17,9 @@ from sentry.integrations.request_buffer import IntegrationRequestBuffer from sentry.models.activity import Activity from sentry.models.auditlogentry import AuditLogEntry -from sentry.models.integrations.sentry_app import SentryApp from sentry.models.integrations.sentry_app_installation import SentryAppInstallation from sentry.models.rule import Rule +from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.shared_integrations.exceptions import ClientError from sentry.tasks.post_process import post_process_group from sentry.tasks.sentry_apps import (