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.

243 lines
6.8 KiB

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