api.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import string
  2. from django.core.cache import cache
  3. from django.http import Http404
  4. from django.utils.crypto import get_random_string
  5. from ninja import Field, Router, Schema
  6. from ninja.errors import HttpError
  7. from apps.api_tokens.models import APIToken
  8. from apps.api_tokens.schema import APITokenSchema
  9. from apps.projects.models import Project
  10. from apps.projects.schema import ProjectWithKeysSchema
  11. from glitchtip.api.authentication import AuthHttpRequest
  12. from glitchtip.schema import CamelSchema
  13. from .constants import (
  14. SETUP_WIZARD_CACHE_EMPTY,
  15. SETUP_WIZARD_CACHE_KEY,
  16. SETUP_WIZARD_CACHE_TIMEOUT,
  17. )
  18. class SetupWizardSchema(Schema):
  19. """
  20. A 64 char random string used to provide a shorted lived and secure
  21. way to transfer sensative data.
  22. """
  23. hash: str = Field(min_length=64, max_length=64)
  24. class SetupWizardResultSchema(CamelSchema):
  25. """
  26. Payload containing projects data and api key that sentry-wizard could use
  27. to configure a local project for usage with GlitchTip
  28. """
  29. api_keys: APITokenSchema
  30. projects: list[ProjectWithKeysSchema]
  31. router = Router()
  32. @router.get("wizard/", response=SetupWizardSchema, auth=None)
  33. def setup_wizard(request):
  34. """
  35. First step used by sentry-wizard
  36. Generates a random hash for later usage
  37. """
  38. wizard_hash = get_random_string(
  39. 64, allowed_chars=string.ascii_lowercase + string.digits
  40. )
  41. key = SETUP_WIZARD_CACHE_KEY + wizard_hash
  42. cache.set(key, SETUP_WIZARD_CACHE_EMPTY, SETUP_WIZARD_CACHE_TIMEOUT)
  43. return {"hash": wizard_hash}
  44. @router.get("wizard/{wizard_hash}/")
  45. async def setup_wizard_hash(request, wizard_hash: str, auth=None):
  46. """
  47. Last step used by sentry-wizard
  48. For a specified hash, fetch data for projects with organizations and dsn keys
  49. Hash replaces user authentication
  50. """
  51. key = SETUP_WIZARD_CACHE_KEY + wizard_hash
  52. wizard_data = cache.get(key)
  53. if wizard_data is None:
  54. raise Http404
  55. elif wizard_data == SETUP_WIZARD_CACHE_EMPTY:
  56. raise HttpError(400)
  57. return wizard_data
  58. @router.delete("wizard/{wizard_hash}/")
  59. def setup_wizard_delete(request, wizard_hash: str, auth=None):
  60. """
  61. Delete hash used by sentry-wizard.
  62. It contains sensitive data, so it makes sense to remove when done.
  63. """
  64. cache.delete(SETUP_WIZARD_CACHE_KEY + wizard_hash)
  65. @router.post("wizard-set-token/")
  66. async def setup_wizard_set_token(request: AuthHttpRequest, payload: SetupWizardSchema):
  67. """
  68. Authenticated api for storing projects data to later be used by sentry-wizard
  69. """
  70. wizard_hash = payload.hash
  71. key = SETUP_WIZARD_CACHE_KEY + wizard_hash
  72. wizard_data = cache.get(key)
  73. if wizard_data is None:
  74. raise HttpError(400, "Token not found")
  75. user_id = request.auth.user_id
  76. projects = [
  77. project
  78. async for project in Project.annotate_is_member(
  79. Project.objects.filter(organization__users=user_id), user_id
  80. )
  81. .select_related("organization")
  82. .prefetch_related("projectkey_set")[:50]
  83. ]
  84. scope = getattr(APIToken.scopes, "project:releases")
  85. token = await APIToken.objects.filter(user=user_id, scopes=scope).afirst()
  86. if not token:
  87. token = await APIToken.objects.acreate(user_id=user_id, scopes=scope)
  88. result = SetupWizardResultSchema(api_keys=token, projects=projects)
  89. cache.set(key, result.dict(by_alias=True), SETUP_WIZARD_CACHE_TIMEOUT)