You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
3.4 KiB

  1. import json
  2. from functools import wraps
  3. from django.db.models import Q
  4. from django.http import HttpResponse, JsonResponse
  5. from hc.accounts.models import Project
  6. from hc.lib.jsonschema import ValidationError, validate
  7. def error(msg, status=400):
  8. return JsonResponse({"error": msg}, status=status)
  9. def authorize(f):
  10. @wraps(f)
  11. def wrapper(request, *args, **kwds):
  12. if "HTTP_X_API_KEY" in request.META:
  13. api_key = request.META["HTTP_X_API_KEY"]
  14. elif hasattr(request, "json"):
  15. api_key = str(request.json.get("api_key", ""))
  16. else:
  17. api_key = ""
  18. if len(api_key) != 32:
  19. return error("missing api key", 401)
  20. try:
  21. request.project = Project.objects.get(api_key=api_key)
  22. except Project.DoesNotExist:
  23. return error("wrong api key", 401)
  24. request.readonly = False
  25. return f(request, *args, **kwds)
  26. return wrapper
  27. def authorize_read(f):
  28. @wraps(f)
  29. def wrapper(request, *args, **kwds):
  30. if "HTTP_X_API_KEY" in request.META:
  31. api_key = request.META["HTTP_X_API_KEY"]
  32. elif hasattr(request, "json"):
  33. api_key = str(request.json.get("api_key", ""))
  34. else:
  35. api_key = ""
  36. if len(api_key) != 32:
  37. return error("missing api key", 401)
  38. write_key_match = Q(api_key=api_key)
  39. read_key_match = Q(api_key_readonly=api_key)
  40. try:
  41. request.project = Project.objects.get(write_key_match | read_key_match)
  42. except Project.DoesNotExist:
  43. return error("wrong api key", 401)
  44. request.readonly = api_key == request.project.api_key_readonly
  45. return f(request, *args, **kwds)
  46. return wrapper
  47. def validate_json(schema={"type": "object"}):
  48. """ Parse request json and validate it against `schema`.
  49. Put the parsed result in `request.json`.
  50. If schema is None then only parse and check if the root
  51. element is a dict. Supports a limited subset of JSON schema spec.
  52. """
  53. def decorator(f):
  54. @wraps(f)
  55. def wrapper(request, *args, **kwds):
  56. if request.method == "POST" and request.body:
  57. try:
  58. request.json = json.loads(request.body.decode())
  59. except ValueError:
  60. return error("could not parse request body")
  61. else:
  62. request.json = {}
  63. try:
  64. validate(request.json, schema)
  65. except ValidationError as e:
  66. return error("json validation error: %s" % e)
  67. return f(request, *args, **kwds)
  68. return wrapper
  69. return decorator
  70. def cors(*methods):
  71. methods = set(methods)
  72. methods.add("OPTIONS")
  73. methods_str = ", ".join(methods)
  74. def decorator(f):
  75. @wraps(f)
  76. def wrapper(request, *args, **kwds):
  77. if request.method == "OPTIONS":
  78. # Handle OPTIONS here
  79. response = HttpResponse(status=204)
  80. elif request.method in methods:
  81. response = f(request, *args, **kwds)
  82. else:
  83. response = HttpResponse(status=405)
  84. response["Access-Control-Allow-Origin"] = "*"
  85. response["Access-Control-Allow-Headers"] = "X-Api-Key"
  86. response["Access-Control-Allow-Methods"] = methods_str
  87. response["Access-Control-Max-Age"] = "600"
  88. return response
  89. return wrapper
  90. return decorator