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.

220 lines
6.1 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
8 years ago
8 years ago
  1. from datetime import timedelta as td
  2. from django.conf import settings
  3. from django.db import connection
  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
  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. headers = request.META
  21. remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"])
  22. remote_addr = remote_addr.split(",")[0]
  23. scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http")
  24. method = headers["REQUEST_METHOD"]
  25. ua = headers.get("HTTP_USER_AGENT", "")
  26. check.ping(remote_addr, scheme, method, ua, request.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)
  132. if tag != "*":
  133. q = q.filter(tags__contains=tag)
  134. label = tag
  135. else:
  136. label = settings.MASTER_BADGE_LABEL
  137. for check in q:
  138. if tag != "*" and tag not in check.tags_list():
  139. continue
  140. if status == "up" and check.in_grace_period():
  141. status = "late"
  142. if check.get_status() == "down":
  143. status = "down"
  144. break
  145. if format == "json":
  146. return JsonResponse({"status": status})
  147. svg = get_badge_svg(label, status)
  148. return HttpResponse(svg, content_type="image/svg+xml")
  149. @csrf_exempt
  150. @uuid_or_400
  151. def bounce(request, code):
  152. notification = get_object_or_404(Notification, code=code)
  153. # If webhook is more than 10 minutes late, don't accept it:
  154. td = timezone.now() - notification.created
  155. if td.total_seconds() > 600:
  156. return HttpResponseForbidden()
  157. notification.error = request.body[:200]
  158. notification.save()
  159. notification.channel.email_verified = False
  160. notification.channel.save()
  161. return HttpResponse()
  162. def status(request):
  163. with connection.cursor() as c:
  164. c.execute("SELECT 1")
  165. c.fetchone()
  166. return HttpResponse("OK")