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.

214 lines
6.0 KiB

10 years ago
8 years ago
10 years ago
10 years ago
10 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. from datetime import timedelta as td
  2. from django.db import connection
  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
  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. headers = request.META
  20. remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"])
  21. remote_addr = remote_addr.split(",")[0]
  22. scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http")
  23. method = headers["REQUEST_METHOD"]
  24. ua = headers.get("HTTP_USER_AGENT", "")
  25. body = request.body[:10000]
  26. check.ping(remote_addr, scheme, method, ua, body)
  27. response = HttpResponse("OK")
  28. response["Access-Control-Allow-Origin"] = "*"
  29. return response
  30. def _lookup(user, spec):
  31. unique_fields = spec.get("unique", [])
  32. if unique_fields:
  33. existing_checks = Check.objects.filter(user=user)
  34. if "name" in unique_fields:
  35. existing_checks = existing_checks.filter(name=spec.get("name"))
  36. if "tags" in unique_fields:
  37. existing_checks = existing_checks.filter(tags=spec.get("tags"))
  38. if "timeout" in unique_fields:
  39. timeout = td(seconds=spec["timeout"])
  40. existing_checks = existing_checks.filter(timeout=timeout)
  41. if "grace" in unique_fields:
  42. grace = td(seconds=spec["grace"])
  43. existing_checks = existing_checks.filter(grace=grace)
  44. return existing_checks.first()
  45. def _update(check, spec):
  46. if "name" in spec:
  47. check.name = spec["name"]
  48. if "tags" in spec:
  49. check.tags = spec["tags"]
  50. if "timeout" in spec and "schedule" not in spec:
  51. check.kind = "simple"
  52. check.timeout = td(seconds=spec["timeout"])
  53. if "grace" in spec:
  54. check.grace = td(seconds=spec["grace"])
  55. if "schedule" in spec:
  56. check.kind = "cron"
  57. check.schedule = spec["schedule"]
  58. if "tz" in spec:
  59. check.tz = spec["tz"]
  60. check.save()
  61. # This needs to be done after saving the check, because of
  62. # the M2M relation between checks and channels:
  63. if "channels" in spec:
  64. if spec["channels"] == "*":
  65. check.assign_all_channels()
  66. elif spec["channels"] == "":
  67. check.channel_set.clear()
  68. return check
  69. @csrf_exempt
  70. @check_api_key
  71. @validate_json(schemas.check)
  72. def checks(request):
  73. if request.method == "GET":
  74. q = Check.objects.filter(user=request.user)
  75. tags = set(request.GET.getlist("tag"))
  76. for tag in tags:
  77. # approximate filtering by tags
  78. q = q.filter(tags__contains=tag)
  79. checks = []
  80. for check in q:
  81. # precise, final filtering
  82. if not tags or check.matches_tag_set(tags):
  83. checks.append(check.to_dict())
  84. return JsonResponse({"checks": checks})
  85. elif request.method == "POST":
  86. created = False
  87. check = _lookup(request.user, request.json)
  88. if check is None:
  89. num_checks = Check.objects.filter(user=request.user).count()
  90. if num_checks >= request.user.profile.check_limit:
  91. return HttpResponseForbidden()
  92. check = Check(user=request.user)
  93. created = True
  94. _update(check, request.json)
  95. return JsonResponse(check.to_dict(), status=201 if created else 200)
  96. # If request is neither GET nor POST, return "405 Method not allowed"
  97. return HttpResponse(status=405)
  98. @csrf_exempt
  99. @uuid_or_400
  100. @check_api_key
  101. @validate_json(schemas.check)
  102. def update(request, code):
  103. check = get_object_or_404(Check, code=code)
  104. if check.user != request.user:
  105. return HttpResponseForbidden()
  106. if request.method == "POST":
  107. _update(check, request.json)
  108. return JsonResponse(check.to_dict())
  109. elif request.method == "DELETE":
  110. response = check.to_dict()
  111. check.delete()
  112. return JsonResponse(response)
  113. # Otherwise, method not allowed
  114. return HttpResponse(status=405)
  115. @csrf_exempt
  116. @require_POST
  117. @uuid_or_400
  118. @check_api_key
  119. def pause(request, code):
  120. check = get_object_or_404(Check, code=code)
  121. if check.user != request.user:
  122. return HttpResponseForbidden()
  123. check.status = "paused"
  124. check.save()
  125. return JsonResponse(check.to_dict())
  126. @never_cache
  127. def badge(request, username, signature, tag, format="svg"):
  128. if not check_signature(username, tag, signature):
  129. return HttpResponseNotFound()
  130. status = "up"
  131. q = Check.objects.filter(user__username=username, tags__contains=tag)
  132. for check in q:
  133. if tag not in check.tags_list():
  134. continue
  135. if status == "up" and check.in_grace_period():
  136. status = "late"
  137. if check.get_status() == "down":
  138. status = "down"
  139. break
  140. if format == "json":
  141. return JsonResponse({"status": status})
  142. svg = get_badge_svg(tag, status)
  143. return HttpResponse(svg, content_type="image/svg+xml")
  144. @csrf_exempt
  145. @uuid_or_400
  146. def bounce(request, code):
  147. notification = get_object_or_404(Notification, code=code)
  148. # If webhook is more than 10 minutes late, don't accept it:
  149. td = timezone.now() - notification.created
  150. if td.total_seconds() > 600:
  151. return HttpResponseForbidden()
  152. notification.error = request.body[:200]
  153. notification.save()
  154. notification.channel.email_verified = False
  155. notification.channel.save()
  156. return HttpResponse()
  157. def status(request):
  158. with connection.cursor() as c:
  159. c.execute("SELECT 1")
  160. c.fetchone()
  161. return HttpResponse("OK")