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.

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