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.

223 lines
6.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
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. tags = set(request.GET.getlist("tag"))
  87. for tag in tags:
  88. # approximate filtering by tags
  89. q = q.filter(tags__contains=tag)
  90. checks = []
  91. for check in q:
  92. # precise, final filtering
  93. if not tags or check.matches_tag_set(tags):
  94. checks.append(check.to_dict())
  95. return JsonResponse({"checks": checks})
  96. elif request.method == "POST":
  97. created = False
  98. check = _lookup(request.user, request.json)
  99. if check is None:
  100. num_checks = Check.objects.filter(user=request.user).count()
  101. if num_checks >= request.user.profile.check_limit:
  102. return HttpResponseForbidden()
  103. check = Check(user=request.user)
  104. created = True
  105. _update(check, request.json)
  106. return JsonResponse(check.to_dict(), status=201 if created else 200)
  107. # If request is neither GET nor POST, return "405 Method not allowed"
  108. return HttpResponse(status=405)
  109. @csrf_exempt
  110. @uuid_or_400
  111. @check_api_key
  112. @validate_json(schemas.check)
  113. def update(request, code):
  114. check = get_object_or_404(Check, code=code)
  115. if check.user != request.user:
  116. return HttpResponseForbidden()
  117. if request.method == "POST":
  118. _update(check, request.json)
  119. return JsonResponse(check.to_dict())
  120. elif request.method == "DELETE":
  121. response = check.to_dict()
  122. check.delete()
  123. return JsonResponse(response)
  124. # Otherwise, method not allowed
  125. return HttpResponse(status=405)
  126. @csrf_exempt
  127. @require_POST
  128. @uuid_or_400
  129. @check_api_key
  130. def pause(request, code):
  131. check = get_object_or_404(Check, code=code)
  132. if check.user != request.user:
  133. return HttpResponseForbidden()
  134. check.status = "paused"
  135. check.save()
  136. return JsonResponse(check.to_dict())
  137. @never_cache
  138. def badge(request, username, signature, tag, format="svg"):
  139. if not check_signature(username, tag, signature):
  140. return HttpResponseNotFound()
  141. status = "up"
  142. q = Check.objects.filter(user__username=username, tags__contains=tag)
  143. for check in q:
  144. if tag not in check.tags_list():
  145. continue
  146. if status == "up" and check.in_grace_period():
  147. status = "late"
  148. if check.get_status() == "down":
  149. status = "down"
  150. break
  151. if format == "json":
  152. return JsonResponse({"status": status})
  153. svg = get_badge_svg(tag, status)
  154. return HttpResponse(svg, content_type="image/svg+xml")
  155. @csrf_exempt
  156. @uuid_or_400
  157. def bounce(request, code):
  158. notification = get_object_or_404(Notification, code=code)
  159. # If webhook is more than 10 minutes late, don't accept it:
  160. td = timezone.now() - notification.created
  161. if td.total_seconds() > 600:
  162. return HttpResponseForbidden()
  163. notification.error = request.body[:200]
  164. notification.save()
  165. return HttpResponse()
  166. def status(request):
  167. with connection.cursor() as c:
  168. c.execute("SELECT 1")
  169. c.fetchone()
  170. return HttpResponse("OK")