sentryapps.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. from __future__ import absolute_import
  2. from django.http import Http404
  3. from sentry.utils.sdk import configure_scope
  4. from sentry.api.authentication import ClientIdSecretAuthentication
  5. from sentry.api.base import Endpoint
  6. from sentry.api.permissions import SentryPermission
  7. from sentry.auth.superuser import is_active_superuser
  8. from sentry.models import SentryApp, SentryAppInstallation, Organization
  9. def ensure_scoped_permission(request, allowed_scopes):
  10. """
  11. Verifies the User making the request has at least one required scope for
  12. the endpoint being requested.
  13. If no scopes were specified in a ``scope_map``, it means the endpoint should
  14. not be accessible. That is, this function expects every accessible endpoint
  15. to have a list of scopes.
  16. That list of scopes may be empty, implying that the User does not need any
  17. specific scope and the endpoint is public.
  18. """
  19. # If no scopes were found at all, the endpoint should not be accessible.
  20. if allowed_scopes is None:
  21. return False
  22. # If there are no scopes listed, it implies a public endpoint.
  23. if len(allowed_scopes) == 0:
  24. return True
  25. return any(request.access.has_scope(s) for s in set(allowed_scopes))
  26. class SentryAppsPermission(SentryPermission):
  27. scope_map = {
  28. 'GET': (), # Public endpoint.
  29. 'POST': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
  30. }
  31. def has_object_permission(self, request, view, organization):
  32. if not hasattr(request, 'user') or not request.user:
  33. return False
  34. self.determine_access(request, organization)
  35. if is_active_superuser(request):
  36. return True
  37. # User must be a part of the Org they're trying to create the app in.
  38. if organization not in request.user.get_orgs():
  39. raise Http404
  40. return ensure_scoped_permission(
  41. request,
  42. self.scope_map.get(request.method),
  43. )
  44. class SentryAppsBaseEndpoint(Endpoint):
  45. permission_classes = (SentryAppsPermission, )
  46. def convert_args(self, request, *args, **kwargs):
  47. # This baseclass is the the SentryApp collection endpoints:
  48. #
  49. # [GET, POST] /sentry-apps
  50. #
  51. # The GET endpoint is public and doesn't require (or handle) any query
  52. # params or request body.
  53. #
  54. # The POST endpoint is for creating a Sentry App. Part of that creation
  55. # is associating it with the Organization that it's created within.
  56. #
  57. # So in the case of POST requests, we want to pull the Organization out
  58. # of the request body so that we can ensure the User making the request
  59. # has access to it.
  60. #
  61. # Since ``convert_args`` is conventionally where you materialize model
  62. # objects from URI params, we're applying the same logic for a param in
  63. # the request body.
  64. #
  65. if not request.json_body or 'organization' not in request.json_body:
  66. return (args, kwargs)
  67. organization = request.user.get_orgs().get(
  68. slug=request.json_body['organization'],
  69. )
  70. self.check_object_permissions(request, organization)
  71. kwargs['organization'] = organization
  72. return (args, kwargs)
  73. class SentryAppPermission(SentryPermission):
  74. unpublished_scope_map = {
  75. 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
  76. 'PUT': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
  77. 'DELETE': ('org:write', 'org:admin'),
  78. }
  79. published_scope_map = {
  80. 'GET': (), # Public endpoint.
  81. 'PUT': ('org:write', 'org:admin'),
  82. 'DELETE': ('org:admin'),
  83. }
  84. def has_object_permission(self, request, view, sentry_app):
  85. if not hasattr(request, 'user') or not request.user:
  86. return False
  87. self.determine_access(request, sentry_app.owner)
  88. if is_active_superuser(request):
  89. return True
  90. # User must be in the Org who owns the app.
  91. if sentry_app.owner not in request.user.get_orgs():
  92. raise Http404
  93. return ensure_scoped_permission(
  94. request,
  95. self._scopes_for_sentry_app(sentry_app).get(request.method),
  96. )
  97. def _scopes_for_sentry_app(self, sentry_app):
  98. if sentry_app.is_published:
  99. return self.published_scope_map
  100. else:
  101. return self.unpublished_scope_map
  102. class SentryAppBaseEndpoint(Endpoint):
  103. permission_classes = (SentryAppPermission, )
  104. def convert_args(self, request, sentry_app_slug, *args, **kwargs):
  105. try:
  106. sentry_app = SentryApp.objects.get(
  107. slug=sentry_app_slug,
  108. )
  109. except SentryApp.DoesNotExist:
  110. raise Http404
  111. self.check_object_permissions(request, sentry_app)
  112. with configure_scope() as scope:
  113. scope.set_tag("sentry_app", sentry_app.slug)
  114. kwargs['sentry_app'] = sentry_app
  115. return (args, kwargs)
  116. class SentryAppInstallationsPermission(SentryPermission):
  117. scope_map = {
  118. 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
  119. 'POST': ('org:integrations', 'org:write', 'org:admin'),
  120. }
  121. def has_object_permission(self, request, view, organization):
  122. if not hasattr(request, 'user') or not request.user:
  123. return False
  124. self.determine_access(request, organization)
  125. if is_active_superuser(request):
  126. return True
  127. if organization not in request.user.get_orgs():
  128. raise Http404
  129. return ensure_scoped_permission(
  130. request,
  131. self.scope_map.get(request.method),
  132. )
  133. class SentryAppInstallationsBaseEndpoint(Endpoint):
  134. permission_classes = (SentryAppInstallationsPermission, )
  135. def convert_args(self, request, organization_slug, *args, **kwargs):
  136. if is_active_superuser(request):
  137. organizations = Organization.objects.all()
  138. else:
  139. organizations = request.user.get_orgs()
  140. try:
  141. organization = organizations.get(slug=organization_slug)
  142. except Organization.DoesNotExist:
  143. raise Http404
  144. self.check_object_permissions(request, organization)
  145. kwargs['organization'] = organization
  146. return (args, kwargs)
  147. class SentryAppInstallationPermission(SentryPermission):
  148. scope_map = {
  149. 'GET': ('org:read', 'org:integrations', 'org:write', 'org:admin'),
  150. 'DELETE': ('org:integrations', 'org:write', 'org:admin'),
  151. # NOTE(mn): The only POST endpoint right now is to create External
  152. # Issues, which uses this baseclass since it's nested under an
  153. # installation.
  154. #
  155. # The scopes below really only make sense for that endpoint. Any other
  156. # nested endpoints will probably need different scopes - figure out how
  157. # to deal with that when it happens.
  158. 'POST': ('org:integrations', 'event:write', 'event:admin'),
  159. }
  160. def has_object_permission(self, request, view, installation):
  161. if not hasattr(request, 'user') or not request.user:
  162. return False
  163. self.determine_access(request, installation.organization)
  164. if is_active_superuser(request):
  165. return True
  166. if installation.organization not in request.user.get_orgs():
  167. raise Http404
  168. return ensure_scoped_permission(
  169. request,
  170. self.scope_map.get(request.method),
  171. )
  172. class SentryAppInstallationBaseEndpoint(Endpoint):
  173. permission_classes = (SentryAppInstallationPermission, )
  174. def convert_args(self, request, uuid, *args, **kwargs):
  175. try:
  176. installation = SentryAppInstallation.objects.get(
  177. uuid=uuid,
  178. )
  179. except SentryAppInstallation.DoesNotExist:
  180. raise Http404
  181. self.check_object_permissions(request, installation)
  182. with configure_scope() as scope:
  183. scope.set_tag("sentry_app_installation", installation.uuid)
  184. kwargs['installation'] = installation
  185. return (args, kwargs)
  186. class SentryAppAuthorizationsPermission(SentryPermission):
  187. def has_object_permission(self, request, view, installation):
  188. if not hasattr(request, 'user') or not request.user:
  189. return False
  190. self.determine_access(request, installation.organization)
  191. if not request.user.is_sentry_app:
  192. return False
  193. # Request must be made as the app's Proxy User, using their Client ID
  194. # and Secret.
  195. return request.user == installation.sentry_app.proxy_user
  196. class SentryAppAuthorizationsBaseEndpoint(SentryAppInstallationBaseEndpoint):
  197. authentication_classes = (ClientIdSecretAuthentication, )
  198. permission_classes = (SentryAppAuthorizationsPermission, )