123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- from __future__ import absolute_import
- from django.http import Http404
- from sentry.utils.sdk import configure_scope
- from sentry.api.authentication import ClientIdSecretAuthentication
- from sentry.api.base import Endpoint
- from sentry.api.permissions import SentryPermission
- from sentry.auth.superuser import is_active_superuser
- from sentry.models import SentryApp, SentryAppInstallation, Organization
- def ensure_scoped_permission(request, allowed_scopes):
- """
- Verifies the User making the request has at least one required scope for
- the endpoint being requested.
- If no scopes were specified in a ``scope_map``, it means the endpoint should
- not be accessible. That is, this function expects every accessible endpoint
- to have a list of scopes.
- That list of scopes may be empty, implying that the User does not need any
- specific scope and the endpoint is public.
- """
- # If no scopes were found at all, the endpoint should not be accessible.
- if allowed_scopes is None:
- return False
- # If there are no scopes listed, it implies a public endpoint.
- if len(allowed_scopes) == 0:
- return True
- return any(request.access.has_scope(s) for s in set(allowed_scopes))
- class SentryAppsPermission(SentryPermission):
- scope_map = {
- 'GET': (), # Public endpoint.
- 'POST': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
- }
- def has_object_permission(self, request, view, organization):
- if not hasattr(request, 'user') or not request.user:
- return False
- self.determine_access(request, organization)
- if is_active_superuser(request):
- return True
- # User must be a part of the Org they're trying to create the app in.
- if organization not in request.user.get_orgs():
- raise Http404
- return ensure_scoped_permission(
- request,
- self.scope_map.get(request.method),
- )
- class SentryAppsBaseEndpoint(Endpoint):
- permission_classes = (SentryAppsPermission, )
- def convert_args(self, request, *args, **kwargs):
- # This baseclass is the the SentryApp collection endpoints:
- #
- # [GET, POST] /sentry-apps
- #
- # The GET endpoint is public and doesn't require (or handle) any query
- # params or request body.
- #
- # The POST endpoint is for creating a Sentry App. Part of that creation
- # is associating it with the Organization that it's created within.
- #
- # So in the case of POST requests, we want to pull the Organization out
- # of the request body so that we can ensure the User making the request
- # has access to it.
- #
- # Since ``convert_args`` is conventionally where you materialize model
- # objects from URI params, we're applying the same logic for a param in
- # the request body.
- #
- if not request.json_body or 'organization' not in request.json_body:
- return (args, kwargs)
- organization = request.user.get_orgs().get(
- slug=request.json_body['organization'],
- )
- self.check_object_permissions(request, organization)
- kwargs['organization'] = organization
- return (args, kwargs)
- class SentryAppPermission(SentryPermission):
- unpublished_scope_map = {
- 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
- 'PUT': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
- 'DELETE': ('org:write', 'org:admin'),
- }
- published_scope_map = {
- 'GET': (), # Public endpoint.
- 'PUT': ('org:write', 'org:admin'),
- 'DELETE': ('org:admin'),
- }
- def has_object_permission(self, request, view, sentry_app):
- if not hasattr(request, 'user') or not request.user:
- return False
- self.determine_access(request, sentry_app.owner)
- if is_active_superuser(request):
- return True
- # User must be in the Org who owns the app.
- if sentry_app.owner not in request.user.get_orgs():
- raise Http404
- return ensure_scoped_permission(
- request,
- self._scopes_for_sentry_app(sentry_app).get(request.method),
- )
- def _scopes_for_sentry_app(self, sentry_app):
- if sentry_app.is_published:
- return self.published_scope_map
- else:
- return self.unpublished_scope_map
- class SentryAppBaseEndpoint(Endpoint):
- permission_classes = (SentryAppPermission, )
- def convert_args(self, request, sentry_app_slug, *args, **kwargs):
- try:
- sentry_app = SentryApp.objects.get(
- slug=sentry_app_slug,
- )
- except SentryApp.DoesNotExist:
- raise Http404
- self.check_object_permissions(request, sentry_app)
- with configure_scope() as scope:
- scope.set_tag("sentry_app", sentry_app.slug)
- kwargs['sentry_app'] = sentry_app
- return (args, kwargs)
- class SentryAppInstallationsPermission(SentryPermission):
- scope_map = {
- 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
- 'POST': ('org:integrations', 'org:write', 'org:admin'),
- }
- def has_object_permission(self, request, view, organization):
- if not hasattr(request, 'user') or not request.user:
- return False
- self.determine_access(request, organization)
- if is_active_superuser(request):
- return True
- if organization not in request.user.get_orgs():
- raise Http404
- return ensure_scoped_permission(
- request,
- self.scope_map.get(request.method),
- )
- class SentryAppInstallationsBaseEndpoint(Endpoint):
- permission_classes = (SentryAppInstallationsPermission, )
- def convert_args(self, request, organization_slug, *args, **kwargs):
- if is_active_superuser(request):
- organizations = Organization.objects.all()
- else:
- organizations = request.user.get_orgs()
- try:
- organization = organizations.get(slug=organization_slug)
- except Organization.DoesNotExist:
- raise Http404
- self.check_object_permissions(request, organization)
- kwargs['organization'] = organization
- return (args, kwargs)
- class SentryAppInstallationPermission(SentryPermission):
- scope_map = {
- 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
- 'DELETE': ('org:integrations', 'org:write', 'org:admin'),
- # NOTE(mn): The only POST endpoint right now is to create External
- # Issues, which uses this baseclass since it's nested under an
- # installation.
- #
- # The scopes below really only make sense for that endpoint. Any other
- # nested endpoints will probably need different scopes - figure out how
- # to deal with that when it happens.
- 'POST': ('org:integrations', 'event:write', 'event:admin'),
- }
- def has_object_permission(self, request, view, installation):
- if not hasattr(request, 'user') or not request.user:
- return False
- self.determine_access(request, installation.organization)
- if is_active_superuser(request):
- return True
- if installation.organization not in request.user.get_orgs():
- raise Http404
- return ensure_scoped_permission(
- request,
- self.scope_map.get(request.method),
- )
- class SentryAppInstallationBaseEndpoint(Endpoint):
- permission_classes = (SentryAppInstallationPermission, )
- def convert_args(self, request, uuid, *args, **kwargs):
- try:
- installation = SentryAppInstallation.objects.get(
- uuid=uuid,
- )
- except SentryAppInstallation.DoesNotExist:
- raise Http404
- self.check_object_permissions(request, installation)
- with configure_scope() as scope:
- scope.set_tag("sentry_app_installation", installation.uuid)
- kwargs['installation'] = installation
- return (args, kwargs)
- class SentryAppAuthorizationsPermission(SentryPermission):
- def has_object_permission(self, request, view, installation):
- if not hasattr(request, 'user') or not request.user:
- return False
- self.determine_access(request, installation.organization)
- if not request.user.is_sentry_app:
- return False
- # Request must be made as the app's Proxy User, using their Client ID
- # and Secret.
- return request.user == installation.sentry_app.proxy_user
- class SentryAppAuthorizationsBaseEndpoint(SentryAppInstallationBaseEndpoint):
- authentication_classes = (ClientIdSecretAuthentication, )
- permission_classes = (SentryAppAuthorizationsPermission, )
|