|
@@ -1,100 +1,47 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import logging
|
|
|
-import re
|
|
|
-from typing import Callable, Mapping, Type
|
|
|
+from typing import Callable, List, Type
|
|
|
|
|
|
-from django.http import HttpRequest, HttpResponse
|
|
|
+from django.http import HttpRequest
|
|
|
+from django.http.response import HttpResponseBase
|
|
|
|
|
|
-from sentry.silo import SiloMode
|
|
|
-
|
|
|
-from .parsers import (
|
|
|
- BitbucketRequestParser,
|
|
|
- BitbucketServerRequestParser,
|
|
|
- GithubEnterpriseRequestParser,
|
|
|
- GithubRequestParser,
|
|
|
- GitlabRequestParser,
|
|
|
- JiraRequestParser,
|
|
|
- JiraServerRequestParser,
|
|
|
- MsTeamsRequestParser,
|
|
|
- SlackRequestParser,
|
|
|
- VstsRequestParser,
|
|
|
+from sentry.middleware.integrations.classifications import (
|
|
|
+ BaseClassification,
|
|
|
+ IntegrationClassification,
|
|
|
+ PluginClassification,
|
|
|
)
|
|
|
-from .parsers.base import BaseRequestParser
|
|
|
+from sentry.silo import SiloMode
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
-ACTIVE_PARSERS = [
|
|
|
- BitbucketRequestParser,
|
|
|
- BitbucketServerRequestParser,
|
|
|
- GithubEnterpriseRequestParser,
|
|
|
- GithubRequestParser,
|
|
|
- GitlabRequestParser,
|
|
|
- JiraRequestParser,
|
|
|
- JiraServerRequestParser,
|
|
|
- MsTeamsRequestParser,
|
|
|
- SlackRequestParser,
|
|
|
- VstsRequestParser,
|
|
|
-]
|
|
|
+ResponseHandler = Callable[[HttpRequest], HttpResponseBase]
|
|
|
|
|
|
|
|
|
class IntegrationControlMiddleware:
|
|
|
- integration_prefix: str = "/extensions/"
|
|
|
- """Prefix for all integration requests. See `src/sentry/web/urls.py`"""
|
|
|
- setup_suffix: str = "/setup/"
|
|
|
- """Suffix for PipelineAdvancerView on installation. See `src/sentry/web/urls.py`"""
|
|
|
+ classifications: List[Type[BaseClassification]] = [
|
|
|
+ IntegrationClassification,
|
|
|
+ PluginClassification,
|
|
|
+ ]
|
|
|
+ """Classifications to determine whether request must be parsed, sorted in priority order."""
|
|
|
|
|
|
- integration_parsers: Mapping[str, Type[BaseRequestParser]] = {
|
|
|
- parser.provider: parser for parser in ACTIVE_PARSERS
|
|
|
- }
|
|
|
-
|
|
|
- def __init__(self, get_response: Callable[[], HttpResponse]):
|
|
|
+ def __init__(self, get_response: ResponseHandler):
|
|
|
self.get_response = get_response
|
|
|
|
|
|
- def _identify_provider(self, request: HttpRequest) -> str | None:
|
|
|
- """
|
|
|
- Parses the provider out of the request path
|
|
|
- e.g. `/extensions/slack/commands/` -> `slack`
|
|
|
- """
|
|
|
- integration_prefix_regex = re.escape(self.integration_prefix)
|
|
|
- provider_regex = rf"^{integration_prefix_regex}(\w+)"
|
|
|
- result = re.search(provider_regex, request.path)
|
|
|
- if not result:
|
|
|
- logger.error(
|
|
|
- "integration_control.invalid_provider",
|
|
|
- extra={"path": request.path},
|
|
|
- )
|
|
|
- return None
|
|
|
- return result.group(1)
|
|
|
-
|
|
|
def _should_operate(self, request: HttpRequest) -> bool:
|
|
|
"""
|
|
|
Determines whether this middleware will operate or just pass the request along.
|
|
|
"""
|
|
|
- is_correct_silo = SiloMode.get_current_mode() == SiloMode.CONTROL
|
|
|
- is_integration = request.path.startswith(self.integration_prefix)
|
|
|
- is_not_setup = not request.path.endswith(self.setup_suffix)
|
|
|
- return is_correct_silo and is_integration and is_not_setup
|
|
|
+ return SiloMode.get_current_mode() == SiloMode.CONTROL
|
|
|
|
|
|
def __call__(self, request: HttpRequest):
|
|
|
if not self._should_operate(request):
|
|
|
return self.get_response(request)
|
|
|
|
|
|
- provider = self._identify_provider(request)
|
|
|
- if not provider:
|
|
|
- return self.get_response(request)
|
|
|
-
|
|
|
- parser_class = self.integration_parsers.get(provider)
|
|
|
- if not parser_class:
|
|
|
- logger.error(
|
|
|
- "integration_control.unknown_provider",
|
|
|
- extra={"path": request.path, "provider": provider},
|
|
|
- )
|
|
|
- return self.get_response(request)
|
|
|
-
|
|
|
- parser = parser_class(
|
|
|
- request=request,
|
|
|
- response_handler=self.get_response,
|
|
|
- )
|
|
|
+ # Check request against each classification, if a match is found, return early
|
|
|
+ for classification in self.classifications:
|
|
|
+ _cls = classification(response_handler=self.get_response)
|
|
|
+ if _cls.should_operate(request):
|
|
|
+ return _cls.get_response(request)
|
|
|
|
|
|
- return parser.get_response()
|
|
|
+ return self.get_response(request)
|