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.

187 lines
5.3 KiB

10 years ago
8 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
8 years ago
8 years ago
8 years ago
  1. from datetime import timedelta as td
  2. from django.db.models import F
  3. from django.http import (HttpResponse, HttpResponseForbidden,
  4. HttpResponseNotFound, JsonResponse)
  5. from django.shortcuts import get_object_or_404
  6. from django.utils import timezone
  7. from django.views.decorators.cache import never_cache
  8. from django.views.decorators.csrf import csrf_exempt
  9. from django.views.decorators.http import require_POST
  10. from hc.api import schemas
  11. from hc.api.decorators import check_api_key, uuid_or_400, validate_json
  12. from hc.api.models import Check, Notification, Ping
  13. from hc.lib.badges import check_signature, get_badge_svg
  14. @csrf_exempt
  15. @uuid_or_400
  16. @never_cache
  17. def ping(request, code):
  18. check = get_object_or_404(Check, code=code)
  19. check.n_pings = F("n_pings") + 1
  20. check.last_ping = timezone.now()
  21. check.alert_after = check.get_alert_after()
  22. if check.status in ("new", "paused"):
  23. check.status = "up"
  24. check.save()
  25. check.refresh_from_db()
  26. ping = Ping(owner=check)
  27. headers = request.META
  28. ping.n = check.n_pings
  29. remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"])
  30. ping.remote_addr = remote_addr.split(",")[0]
  31. ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http")
  32. ping.method = headers["REQUEST_METHOD"]
  33. # If User-Agent is longer than 200 characters, truncate it:
  34. ping.ua = headers.get("HTTP_USER_AGENT", "")[:200]
  35. ping.save()
  36. response = HttpResponse("OK")
  37. response["Access-Control-Allow-Origin"] = "*"
  38. return response
  39. def _lookup(user, spec):
  40. unique_fields = spec.get("unique", [])
  41. if unique_fields:
  42. existing_checks = Check.objects.filter(user=user)
  43. if "name" in unique_fields:
  44. existing_checks = existing_checks.filter(name=spec.get("name"))
  45. if "tags" in unique_fields:
  46. existing_checks = existing_checks.filter(tags=spec.get("tags"))
  47. if "timeout" in unique_fields:
  48. timeout = td(seconds=spec["timeout"])
  49. existing_checks = existing_checks.filter(timeout=timeout)
  50. if "grace" in unique_fields:
  51. grace = td(seconds=spec["grace"])
  52. existing_checks = existing_checks.filter(grace=grace)
  53. return existing_checks.first()
  54. def _update(check, spec):
  55. if "name" in spec:
  56. check.name = spec["name"]
  57. if "tags" in spec:
  58. check.tags = spec["tags"]
  59. if "timeout" in spec and "schedule" not in spec:
  60. check.kind = "simple"
  61. check.timeout = td(seconds=spec["timeout"])
  62. if "grace" in spec:
  63. check.grace = td(seconds=spec["grace"])
  64. if "schedule" in spec:
  65. check.kind = "cron"
  66. check.schedule = spec["schedule"]
  67. if "tz" in spec:
  68. check.tz = spec["tz"]
  69. check.save()
  70. # This needs to be done after saving the check, because of
  71. # the M2M relation between checks and channels:
  72. if "channels" in spec:
  73. if spec["channels"] == "*":
  74. check.assign_all_channels()
  75. elif spec["channels"] == "":
  76. check.channel_set.clear()
  77. return check
  78. @csrf_exempt
  79. @check_api_key
  80. @validate_json(schemas.check)
  81. def checks(request):
  82. if request.method == "GET":
  83. q = Check.objects.filter(user=request.user)
  84. doc = {"checks": [check.to_dict() for check in q]}
  85. return JsonResponse(doc)
  86. elif request.method == "POST":
  87. created = False
  88. check = _lookup(request.user, request.json)
  89. if check is None:
  90. check = Check(user=request.user)
  91. created = True
  92. _update(check, request.json)
  93. return JsonResponse(check.to_dict(), status=201 if created else 200)
  94. # If request is neither GET nor POST, return "405 Method not allowed"
  95. return HttpResponse(status=405)
  96. @csrf_exempt
  97. @require_POST
  98. @uuid_or_400
  99. @check_api_key
  100. @validate_json(schemas.check)
  101. def update(request, code):
  102. check = get_object_or_404(Check, code=code)
  103. if check.user != request.user:
  104. return HttpResponseForbidden()
  105. _update(check, request.json)
  106. return JsonResponse(check.to_dict(), status=200)
  107. @csrf_exempt
  108. @require_POST
  109. @uuid_or_400
  110. @check_api_key
  111. def pause(request, code):
  112. check = get_object_or_404(Check, code=code)
  113. if check.user != request.user:
  114. return HttpResponseForbidden()
  115. check.status = "paused"
  116. check.save()
  117. return JsonResponse(check.to_dict())
  118. @never_cache
  119. def badge(request, username, signature, tag):
  120. if not check_signature(username, tag, signature):
  121. return HttpResponseNotFound()
  122. status = "up"
  123. q = Check.objects.filter(user__username=username, tags__contains=tag)
  124. for check in q:
  125. if tag not in check.tags_list():
  126. continue
  127. if status == "up" and check.in_grace_period():
  128. status = "late"
  129. if check.get_status() == "down":
  130. status = "down"
  131. break
  132. svg = get_badge_svg(tag, status)
  133. return HttpResponse(svg, content_type="image/svg+xml")
  134. @csrf_exempt
  135. @uuid_or_400
  136. def bounce(request, code):
  137. notification = get_object_or_404(Notification, code=code)
  138. # If webhook is more than 10 minutes late, don't accept it:
  139. td = timezone.now() - notification.created
  140. if td.total_seconds() > 600:
  141. return HttpResponseForbidden()
  142. notification.error = request.body[:200]
  143. notification.save()
  144. return HttpResponse()