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.

1045 lines
31 KiB

9 years ago
9 years ago
8 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
9 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
6 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
6 years ago
9 years ago
9 years ago
6 years ago
9 years ago
9 years ago
6 years ago
6 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
8 years ago
  1. from datetime import datetime, timedelta as td
  2. import json
  3. from urllib.parse import urlencode
  4. from croniter import croniter
  5. from django.conf import settings
  6. from django.contrib import messages
  7. from django.contrib.auth.decorators import login_required
  8. from django.core import signing
  9. from django.db.models import Count
  10. from django.http import (Http404, HttpResponse, HttpResponseBadRequest,
  11. HttpResponseForbidden, JsonResponse)
  12. from django.shortcuts import get_object_or_404, redirect, render
  13. from django.template.loader import get_template, render_to_string
  14. from django.urls import reverse
  15. from django.utils import timezone
  16. from django.utils.crypto import get_random_string
  17. from django.views.decorators.csrf import csrf_exempt
  18. from django.views.decorators.http import require_POST
  19. from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
  20. Ping, Notification)
  21. from hc.api.transports import Telegram
  22. from hc.front.forms import (AddWebhookForm, NameTagsForm,
  23. TimeoutForm, AddUrlForm, AddEmailForm,
  24. AddOpsGenieForm, CronForm, AddSmsForm)
  25. from hc.front.schemas import telegram_callback
  26. from hc.front.templatetags.hc_extras import (num_down_title, down_title,
  27. sortchecks)
  28. from hc.lib import jsonschema
  29. from pytz import all_timezones
  30. from pytz.exceptions import UnknownTimeZoneError
  31. import requests
  32. VALID_SORT_VALUES = ("name", "-name", "last_ping", "-last_ping", "created")
  33. STATUS_TEXT_TMPL = get_template("front/log_status_text.html")
  34. LAST_PING_TMPL = get_template("front/last_ping_cell.html")
  35. EVENTS_TMPL = get_template("front/details_events.html")
  36. def _tags_statuses(checks):
  37. tags, down, grace, num_down = {}, {}, {}, 0
  38. for check in checks:
  39. status = check.get_status()
  40. if status == "down":
  41. num_down += 1
  42. for tag in check.tags_list():
  43. down[tag] = "down"
  44. elif status == "grace":
  45. for tag in check.tags_list():
  46. grace[tag] = "grace"
  47. else:
  48. for tag in check.tags_list():
  49. tags[tag] = "up"
  50. tags.update(grace)
  51. tags.update(down)
  52. return tags, num_down
  53. @login_required
  54. def my_checks(request):
  55. if request.GET.get("sort") in VALID_SORT_VALUES:
  56. request.profile.sort = request.GET["sort"]
  57. request.profile.save()
  58. checks = list(Check.objects.filter(user=request.team.user).prefetch_related("channel_set"))
  59. sortchecks(checks, request.profile.sort)
  60. tags_statuses, num_down = _tags_statuses(checks)
  61. pairs = list(tags_statuses.items())
  62. pairs.sort(key=lambda pair: pair[0].lower())
  63. channels = Channel.objects.filter(user=request.team.user)
  64. channels = list(channels.order_by("created"))
  65. ctx = {
  66. "page": "checks",
  67. "checks": checks,
  68. "channels": channels,
  69. "num_down": num_down,
  70. "now": timezone.now(),
  71. "tags": pairs,
  72. "ping_endpoint": settings.PING_ENDPOINT,
  73. "timezones": all_timezones,
  74. "num_available": request.team.check_limit - len(checks),
  75. "sort": request.profile.sort
  76. }
  77. return render(request, "front/my_checks.html", ctx)
  78. @login_required
  79. def status(request):
  80. checks = list(Check.objects.filter(user_id=request.team.user_id))
  81. details = []
  82. for check in checks:
  83. ctx = {"check": check}
  84. details.append({
  85. "code": str(check.code),
  86. "status": check.get_status(),
  87. "last_ping": LAST_PING_TMPL.render(ctx)
  88. })
  89. tags_statuses, num_down = _tags_statuses(checks)
  90. return JsonResponse({
  91. "details": details,
  92. "tags": tags_statuses,
  93. "title": num_down_title(num_down)
  94. })
  95. @login_required
  96. @require_POST
  97. def switch_channel(request, code, channel_code):
  98. check = get_object_or_404(Check, code=code)
  99. if check.user_id != request.team.user.id:
  100. return HttpResponseForbidden()
  101. channel = get_object_or_404(Channel, code=channel_code)
  102. if channel.user_id != request.team.user.id:
  103. return HttpResponseForbidden()
  104. if request.POST.get("state") == "on":
  105. channel.checks.add(check)
  106. else:
  107. channel.checks.remove(check)
  108. return HttpResponse()
  109. def index(request):
  110. if request.user.is_authenticated:
  111. return redirect("hc-checks")
  112. check = Check()
  113. ctx = {
  114. "page": "welcome",
  115. "check": check,
  116. "ping_url": check.url(),
  117. "enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
  118. "enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
  119. "enable_discord": settings.DISCORD_CLIENT_ID is not None,
  120. "enable_telegram": settings.TELEGRAM_TOKEN is not None,
  121. "enable_sms": settings.TWILIO_AUTH is not None,
  122. "enable_pd": settings.PD_VENDOR_KEY is not None,
  123. "registration_open": settings.REGISTRATION_OPEN
  124. }
  125. return render(request, "front/welcome.html", ctx)
  126. def docs(request):
  127. ctx = {
  128. "page": "docs",
  129. "section": "home",
  130. "ping_endpoint": settings.PING_ENDPOINT,
  131. "ping_email": "your-uuid-here@%s" % settings.PING_EMAIL_DOMAIN,
  132. "ping_url": settings.PING_ENDPOINT + "your-uuid-here"
  133. }
  134. return render(request, "front/docs.html", ctx)
  135. def docs_api(request):
  136. ctx = {
  137. "page": "docs",
  138. "section": "api",
  139. "SITE_ROOT": settings.SITE_ROOT,
  140. "PING_ENDPOINT": settings.PING_ENDPOINT,
  141. "default_timeout": int(DEFAULT_TIMEOUT.total_seconds()),
  142. "default_grace": int(DEFAULT_GRACE.total_seconds())
  143. }
  144. return render(request, "front/docs_api.html", ctx)
  145. def docs_cron(request):
  146. ctx = {"page": "docs", "section": "cron"}
  147. return render(request, "front/docs_cron.html", ctx)
  148. @require_POST
  149. @login_required
  150. def add_check(request):
  151. num_checks = Check.objects.filter(user=request.team.user).count()
  152. if num_checks >= request.team.check_limit:
  153. return HttpResponseBadRequest()
  154. check = Check(user=request.team.user)
  155. check.save()
  156. check.assign_all_channels()
  157. return redirect("hc-checks")
  158. @require_POST
  159. @login_required
  160. def update_name(request, code):
  161. check = get_object_or_404(Check, code=code)
  162. if check.user_id != request.team.user.id:
  163. return HttpResponseForbidden()
  164. form = NameTagsForm(request.POST)
  165. if form.is_valid():
  166. check.name = form.cleaned_data["name"]
  167. check.tags = form.cleaned_data["tags"]
  168. check.save()
  169. if "/details/" in request.META.get("HTTP_REFERER", ""):
  170. return redirect("hc-details", code)
  171. return redirect("hc-checks")
  172. @require_POST
  173. @login_required
  174. def update_timeout(request, code):
  175. check = get_object_or_404(Check, code=code)
  176. if check.user != request.team.user:
  177. return HttpResponseForbidden()
  178. kind = request.POST.get("kind")
  179. if kind == "simple":
  180. form = TimeoutForm(request.POST)
  181. if not form.is_valid():
  182. return HttpResponseBadRequest()
  183. check.kind = "simple"
  184. check.timeout = form.cleaned_data["timeout"]
  185. check.grace = form.cleaned_data["grace"]
  186. elif kind == "cron":
  187. form = CronForm(request.POST)
  188. if not form.is_valid():
  189. return HttpResponseBadRequest()
  190. check.kind = "cron"
  191. check.schedule = form.cleaned_data["schedule"]
  192. check.tz = form.cleaned_data["tz"]
  193. check.grace = td(minutes=form.cleaned_data["grace"])
  194. if check.last_ping:
  195. check.alert_after = check.get_alert_after()
  196. check.save()
  197. if "/details/" in request.META.get("HTTP_REFERER", ""):
  198. return redirect("hc-details", code)
  199. return redirect("hc-checks")
  200. @require_POST
  201. def cron_preview(request):
  202. schedule = request.POST.get("schedule")
  203. tz = request.POST.get("tz")
  204. ctx = {"tz": tz, "dates": []}
  205. try:
  206. with timezone.override(tz):
  207. now_naive = timezone.make_naive(timezone.now())
  208. it = croniter(schedule, now_naive)
  209. for i in range(0, 6):
  210. naive = it.get_next(datetime)
  211. aware = timezone.make_aware(naive)
  212. ctx["dates"].append((naive, aware))
  213. except UnknownTimeZoneError:
  214. ctx["bad_tz"] = True
  215. except:
  216. ctx["bad_schedule"] = True
  217. return render(request, "front/cron_preview.html", ctx)
  218. @require_POST
  219. def ping_details(request, code, n=None):
  220. if not request.user.is_authenticated:
  221. return HttpResponseForbidden()
  222. check = get_object_or_404(Check, code=code)
  223. if check.user_id != request.team.user.id:
  224. return HttpResponseForbidden()
  225. q = Ping.objects.filter(owner=check)
  226. if n:
  227. q = q.filter(n=n)
  228. ping = q.latest("created")
  229. ctx = {
  230. "check": check,
  231. "ping": ping
  232. }
  233. return render(request, "front/ping_details.html", ctx)
  234. @require_POST
  235. @login_required
  236. def pause(request, code):
  237. check = get_object_or_404(Check, code=code)
  238. if check.user_id != request.team.user.id:
  239. return HttpResponseForbidden()
  240. check.status = "paused"
  241. check.save()
  242. if "/details/" in request.META.get("HTTP_REFERER", ""):
  243. return redirect("hc-details", code)
  244. return redirect("hc-checks")
  245. @require_POST
  246. @login_required
  247. def remove_check(request, code):
  248. check = get_object_or_404(Check, code=code)
  249. if check.user != request.team.user:
  250. return HttpResponseForbidden()
  251. check.delete()
  252. return redirect("hc-checks")
  253. def _get_events(check, limit):
  254. pings = Ping.objects.filter(owner=check).order_by("-id")[:limit]
  255. pings = list(pings)
  256. alerts = []
  257. if len(pings):
  258. cutoff = pings[-1].created
  259. alerts = Notification.objects \
  260. .select_related("channel") \
  261. .filter(owner=check, check_status="down", created__gt=cutoff)
  262. events = pings + list(alerts)
  263. events.sort(key=lambda el: el.created, reverse=True)
  264. return events
  265. @login_required
  266. def log(request, code):
  267. check = get_object_or_404(Check, code=code)
  268. if check.user != request.team.user:
  269. return HttpResponseForbidden()
  270. limit = request.team.ping_log_limit
  271. ctx = {
  272. "check": check,
  273. "events": _get_events(check, limit),
  274. "limit": limit,
  275. "show_limit_notice": check.n_pings > limit and settings.USE_PAYMENTS
  276. }
  277. return render(request, "front/log.html", ctx)
  278. @login_required
  279. def details(request, code):
  280. check = get_object_or_404(Check, code=code)
  281. if check.user != request.team.user:
  282. return HttpResponseForbidden()
  283. channels = Channel.objects.filter(user=request.team.user)
  284. channels = list(channels.order_by("created"))
  285. ctx = {
  286. "page": "details",
  287. "check": check,
  288. "channels": channels
  289. }
  290. return render(request, "front/details.html", ctx)
  291. @login_required
  292. def status_single(request, code):
  293. check = get_object_or_404(Check, code=code)
  294. if check.user_id != request.team.user.id:
  295. return HttpResponseForbidden()
  296. status = check.get_status()
  297. events = _get_events(check, 20)
  298. updated = "1"
  299. if len(events):
  300. updated = events[0].created.strftime("%s.%f")
  301. doc = {
  302. "status": status,
  303. "status_text": STATUS_TEXT_TMPL.render({"check": check}),
  304. "title": down_title(check),
  305. "updated": updated
  306. }
  307. if updated != request.GET.get("u"):
  308. doc["events"] = EVENTS_TMPL.render({"check": check, "events": events})
  309. return JsonResponse(doc)
  310. @login_required
  311. def channels(request):
  312. if request.method == "POST":
  313. code = request.POST["channel"]
  314. try:
  315. channel = Channel.objects.get(code=code)
  316. except Channel.DoesNotExist:
  317. return HttpResponseBadRequest()
  318. if channel.user_id != request.team.user.id:
  319. return HttpResponseForbidden()
  320. new_checks = []
  321. for key in request.POST:
  322. if key.startswith("check-"):
  323. code = key[6:]
  324. try:
  325. check = Check.objects.get(code=code)
  326. except Check.DoesNotExist:
  327. return HttpResponseBadRequest()
  328. if check.user_id != request.team.user.id:
  329. return HttpResponseForbidden()
  330. new_checks.append(check)
  331. channel.checks.set(new_checks)
  332. return redirect("hc-channels")
  333. channels = Channel.objects.filter(user=request.team.user)
  334. channels = channels.order_by("created")
  335. channels = channels.annotate(n_checks=Count("checks"))
  336. num_checks = Check.objects.filter(user=request.team.user).count()
  337. ctx = {
  338. "page": "channels",
  339. "profile": request.team,
  340. "channels": channels,
  341. "num_checks": num_checks,
  342. "enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
  343. "enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
  344. "enable_discord": settings.DISCORD_CLIENT_ID is not None,
  345. "enable_telegram": settings.TELEGRAM_TOKEN is not None,
  346. "enable_sms": settings.TWILIO_AUTH is not None,
  347. "enable_pd": settings.PD_VENDOR_KEY is not None,
  348. "enable_zendesk": settings.ZENDESK_CLIENT_ID is not None,
  349. "use_payments": settings.USE_PAYMENTS
  350. }
  351. return render(request, "front/channels.html", ctx)
  352. @login_required
  353. def channel_checks(request, code):
  354. channel = get_object_or_404(Channel, code=code)
  355. if channel.user_id != request.team.user.id:
  356. return HttpResponseForbidden()
  357. assigned = set(channel.checks.values_list('code', flat=True).distinct())
  358. checks = Check.objects.filter(user=request.team.user).order_by("created")
  359. ctx = {
  360. "checks": checks,
  361. "assigned": assigned,
  362. "channel": channel
  363. }
  364. return render(request, "front/channel_checks.html", ctx)
  365. def verify_email(request, code, token):
  366. channel = get_object_or_404(Channel, code=code)
  367. if channel.make_token() == token:
  368. channel.email_verified = True
  369. channel.save()
  370. return render(request, "front/verify_email_success.html")
  371. return render(request, "bad_link.html")
  372. def unsubscribe_email(request, code, token):
  373. channel = get_object_or_404(Channel, code=code)
  374. if channel.make_token() != token:
  375. return render(request, "bad_link.html")
  376. if channel.kind != "email":
  377. return HttpResponseBadRequest()
  378. channel.delete()
  379. return render(request, "front/unsubscribe_success.html")
  380. @require_POST
  381. @login_required
  382. def remove_channel(request, code):
  383. # user may refresh the page during POST and cause two deletion attempts
  384. channel = Channel.objects.filter(code=code).first()
  385. if channel:
  386. if channel.user != request.team.user:
  387. return HttpResponseForbidden()
  388. channel.delete()
  389. return redirect("hc-channels")
  390. @login_required
  391. def add_email(request):
  392. if request.method == "POST":
  393. form = AddEmailForm(request.POST)
  394. if form.is_valid():
  395. channel = Channel(user=request.team.user, kind="email")
  396. channel.value = form.cleaned_data["value"]
  397. channel.save()
  398. channel.assign_all_checks()
  399. channel.send_verify_link()
  400. return redirect("hc-channels")
  401. else:
  402. form = AddEmailForm()
  403. ctx = {"page": "channels", "form": form}
  404. return render(request, "integrations/add_email.html", ctx)
  405. @login_required
  406. def add_webhook(request):
  407. if request.method == "POST":
  408. form = AddWebhookForm(request.POST)
  409. if form.is_valid():
  410. channel = Channel(user=request.team.user, kind="webhook")
  411. channel.value = form.get_value()
  412. channel.save()
  413. channel.assign_all_checks()
  414. return redirect("hc-channels")
  415. else:
  416. form = AddWebhookForm()
  417. ctx = {
  418. "page": "channels",
  419. "form": form,
  420. "now": timezone.now().replace(microsecond=0).isoformat()
  421. }
  422. return render(request, "integrations/add_webhook.html", ctx)
  423. def _prepare_state(request, session_key):
  424. state = get_random_string()
  425. request.session[session_key] = state
  426. return state
  427. def _get_validated_code(request, session_key, key="code"):
  428. if session_key not in request.session:
  429. return None
  430. session_state = request.session.pop(session_key)
  431. request_state = request.GET.get("state")
  432. if session_state is None or session_state != request_state:
  433. return None
  434. return request.GET.get(key)
  435. def add_pd(request, state=None):
  436. if settings.PD_VENDOR_KEY is None:
  437. raise Http404("pagerduty integration is not available")
  438. if state and request.user.is_authenticated:
  439. if "pd" not in request.session:
  440. return HttpResponseBadRequest()
  441. session_state = request.session.pop("pd")
  442. if session_state != state:
  443. return HttpResponseBadRequest()
  444. if request.GET.get("error") == "cancelled":
  445. messages.warning(request, "PagerDuty setup was cancelled")
  446. return redirect("hc-channels")
  447. channel = Channel()
  448. channel.user = request.team.user
  449. channel.kind = "pd"
  450. channel.value = json.dumps({
  451. "service_key": request.GET.get("service_key"),
  452. "account": request.GET.get("account")
  453. })
  454. channel.save()
  455. channel.assign_all_checks()
  456. messages.success(request, "The PagerDuty integration has been added!")
  457. return redirect("hc-channels")
  458. state = _prepare_state(request, "pd")
  459. callback = settings.SITE_ROOT + reverse("hc-add-pd-state", args=[state])
  460. connect_url = "https://connect.pagerduty.com/connect?" + urlencode({
  461. "vendor": settings.PD_VENDOR_KEY,
  462. "callback": callback
  463. })
  464. ctx = {"page": "channels", "connect_url": connect_url}
  465. return render(request, "integrations/add_pd.html", ctx)
  466. @login_required
  467. def add_pagertree(request):
  468. if request.method == "POST":
  469. form = AddUrlForm(request.POST)
  470. if form.is_valid():
  471. channel = Channel(user=request.team.user, kind="pagertree")
  472. channel.value = form.cleaned_data["value"]
  473. channel.save()
  474. channel.assign_all_checks()
  475. return redirect("hc-channels")
  476. else:
  477. form = AddUrlForm()
  478. ctx = {"page": "channels", "form": form}
  479. return render(request, "integrations/add_pagertree.html", ctx)
  480. def add_slack(request):
  481. if not settings.SLACK_CLIENT_ID and not request.user.is_authenticated:
  482. return redirect("hc-login")
  483. if request.method == "POST":
  484. form = AddUrlForm(request.POST)
  485. if form.is_valid():
  486. channel = Channel(user=request.team.user, kind="slack")
  487. channel.value = form.cleaned_data["value"]
  488. channel.save()
  489. channel.assign_all_checks()
  490. return redirect("hc-channels")
  491. else:
  492. form = AddUrlForm()
  493. ctx = {
  494. "page": "channels",
  495. "form": form,
  496. "slack_client_id": settings.SLACK_CLIENT_ID
  497. }
  498. if settings.SLACK_CLIENT_ID:
  499. ctx["state"] = _prepare_state(request, "slack")
  500. return render(request, "integrations/add_slack.html", ctx)
  501. @login_required
  502. def add_slack_btn(request):
  503. code = _get_validated_code(request, "slack")
  504. if code is None:
  505. return HttpResponseBadRequest()
  506. result = requests.post("https://slack.com/api/oauth.access", {
  507. "client_id": settings.SLACK_CLIENT_ID,
  508. "client_secret": settings.SLACK_CLIENT_SECRET,
  509. "code": code
  510. })
  511. doc = result.json()
  512. if doc.get("ok"):
  513. channel = Channel()
  514. channel.user = request.team.user
  515. channel.kind = "slack"
  516. channel.value = result.text
  517. channel.save()
  518. channel.assign_all_checks()
  519. messages.success(request, "The Slack integration has been added!")
  520. else:
  521. s = doc.get("error")
  522. messages.warning(request, "Error message from slack: %s" % s)
  523. return redirect("hc-channels")
  524. @login_required
  525. def add_hipchat(request):
  526. if "installable_url" in request.GET:
  527. url = request.GET["installable_url"]
  528. assert url.startswith("https://api.hipchat.com")
  529. response = requests.get(url)
  530. if "oauthId" not in response.json():
  531. messages.warning(request, "Something went wrong!")
  532. return redirect("hc-channels")
  533. channel = Channel(kind="hipchat")
  534. channel.user = request.team.user
  535. channel.value = response.text
  536. channel.save()
  537. channel.refresh_hipchat_access_token()
  538. channel.assign_all_checks()
  539. messages.success(request, "The HipChat integration has been added!")
  540. return redirect("hc-channels")
  541. install_url = "https://www.hipchat.com/addons/install?" + urlencode({
  542. "url": settings.SITE_ROOT + reverse("hc-hipchat-capabilities")
  543. })
  544. ctx = {
  545. "page": "channels",
  546. "install_url": install_url
  547. }
  548. return render(request, "integrations/add_hipchat.html", ctx)
  549. def hipchat_capabilities(request):
  550. return render(request, "integrations/hipchat_capabilities.json", {},
  551. content_type="application/json")
  552. @login_required
  553. def add_pushbullet(request):
  554. if settings.PUSHBULLET_CLIENT_ID is None:
  555. raise Http404("pushbullet integration is not available")
  556. if "code" in request.GET:
  557. code = _get_validated_code(request, "pushbullet")
  558. if code is None:
  559. return HttpResponseBadRequest()
  560. result = requests.post("https://api.pushbullet.com/oauth2/token", {
  561. "client_id": settings.PUSHBULLET_CLIENT_ID,
  562. "client_secret": settings.PUSHBULLET_CLIENT_SECRET,
  563. "code": code,
  564. "grant_type": "authorization_code"
  565. })
  566. doc = result.json()
  567. if "access_token" in doc:
  568. channel = Channel(kind="pushbullet")
  569. channel.user = request.team.user
  570. channel.value = doc["access_token"]
  571. channel.save()
  572. channel.assign_all_checks()
  573. messages.success(request,
  574. "The Pushbullet integration has been added!")
  575. else:
  576. messages.warning(request, "Something went wrong")
  577. return redirect("hc-channels")
  578. redirect_uri = settings.SITE_ROOT + reverse("hc-add-pushbullet")
  579. authorize_url = "https://www.pushbullet.com/authorize?" + urlencode({
  580. "client_id": settings.PUSHBULLET_CLIENT_ID,
  581. "redirect_uri": redirect_uri,
  582. "response_type": "code",
  583. "state": _prepare_state(request, "pushbullet")
  584. })
  585. ctx = {
  586. "page": "channels",
  587. "authorize_url": authorize_url
  588. }
  589. return render(request, "integrations/add_pushbullet.html", ctx)
  590. @login_required
  591. def add_discord(request):
  592. if settings.DISCORD_CLIENT_ID is None:
  593. raise Http404("discord integration is not available")
  594. redirect_uri = settings.SITE_ROOT + reverse("hc-add-discord")
  595. if "code" in request.GET:
  596. code = _get_validated_code(request, "discord")
  597. if code is None:
  598. return HttpResponseBadRequest()
  599. result = requests.post("https://discordapp.com/api/oauth2/token", {
  600. "client_id": settings.DISCORD_CLIENT_ID,
  601. "client_secret": settings.DISCORD_CLIENT_SECRET,
  602. "code": code,
  603. "grant_type": "authorization_code",
  604. "redirect_uri": redirect_uri
  605. })
  606. doc = result.json()
  607. if "access_token" in doc:
  608. channel = Channel(kind="discord")
  609. channel.user = request.team.user
  610. channel.value = result.text
  611. channel.save()
  612. channel.assign_all_checks()
  613. messages.success(request,
  614. "The Discord integration has been added!")
  615. else:
  616. messages.warning(request, "Something went wrong")
  617. return redirect("hc-channels")
  618. auth_url = "https://discordapp.com/api/oauth2/authorize?" + urlencode({
  619. "client_id": settings.DISCORD_CLIENT_ID,
  620. "scope": "webhook.incoming",
  621. "redirect_uri": redirect_uri,
  622. "response_type": "code",
  623. "state": _prepare_state(request, "discord")
  624. })
  625. ctx = {
  626. "page": "channels",
  627. "authorize_url": auth_url
  628. }
  629. return render(request, "integrations/add_discord.html", ctx)
  630. def add_pushover(request):
  631. if settings.PUSHOVER_API_TOKEN is None or settings.PUSHOVER_SUBSCRIPTION_URL is None:
  632. raise Http404("pushover integration is not available")
  633. if not request.user.is_authenticated:
  634. ctx = {"page": "channels"}
  635. return render(request, "integrations/add_pushover.html", ctx)
  636. if request.method == "POST":
  637. # Initiate the subscription
  638. state = _prepare_state(request, "pushover")
  639. failure_url = settings.SITE_ROOT + reverse("hc-channels")
  640. success_url = settings.SITE_ROOT + reverse("hc-add-pushover") + "?" + urlencode({
  641. "state": state,
  642. "prio": request.POST.get("po_priority", "0"),
  643. })
  644. subscription_url = settings.PUSHOVER_SUBSCRIPTION_URL + "?" + urlencode({
  645. "success": success_url,
  646. "failure": failure_url,
  647. })
  648. return redirect(subscription_url)
  649. # Handle successful subscriptions
  650. if "pushover_user_key" in request.GET:
  651. key = _get_validated_code(request, "pushover", "pushover_user_key")
  652. if key is None:
  653. return HttpResponseBadRequest()
  654. # Validate priority
  655. prio = request.GET.get("prio")
  656. if prio not in ("-2", "-1", "0", "1", "2"):
  657. return HttpResponseBadRequest()
  658. if request.GET.get("pushover_unsubscribed") == "1":
  659. # Unsubscription: delete all Pushover channels for this user
  660. Channel.objects.filter(user=request.user, kind="po").delete()
  661. return redirect("hc-channels")
  662. # Subscription
  663. channel = Channel(user=request.team.user, kind="po")
  664. channel.value = "%s|%s" % (key, prio)
  665. channel.save()
  666. channel.assign_all_checks()
  667. messages.success(request, "The Pushover integration has been added!")
  668. return redirect("hc-channels")
  669. # Show Integration Settings form
  670. ctx = {
  671. "page": "channels",
  672. "po_retry_delay": td(seconds=settings.PUSHOVER_EMERGENCY_RETRY_DELAY),
  673. "po_expiration": td(seconds=settings.PUSHOVER_EMERGENCY_EXPIRATION),
  674. }
  675. return render(request, "integrations/add_pushover.html", ctx)
  676. @login_required
  677. def add_opsgenie(request):
  678. if request.method == "POST":
  679. form = AddOpsGenieForm(request.POST)
  680. if form.is_valid():
  681. channel = Channel(user=request.team.user, kind="opsgenie")
  682. channel.value = form.cleaned_data["value"]
  683. channel.save()
  684. channel.assign_all_checks()
  685. return redirect("hc-channels")
  686. else:
  687. form = AddUrlForm()
  688. ctx = {"page": "channels", "form": form}
  689. return render(request, "integrations/add_opsgenie.html", ctx)
  690. @login_required
  691. def add_victorops(request):
  692. if request.method == "POST":
  693. form = AddUrlForm(request.POST)
  694. if form.is_valid():
  695. channel = Channel(user=request.team.user, kind="victorops")
  696. channel.value = form.cleaned_data["value"]
  697. channel.save()
  698. channel.assign_all_checks()
  699. return redirect("hc-channels")
  700. else:
  701. form = AddUrlForm()
  702. ctx = {"page": "channels", "form": form}
  703. return render(request, "integrations/add_victorops.html", ctx)
  704. @csrf_exempt
  705. @require_POST
  706. def telegram_bot(request):
  707. try:
  708. doc = json.loads(request.body.decode())
  709. jsonschema.validate(doc, telegram_callback)
  710. except ValueError:
  711. return HttpResponseBadRequest()
  712. except jsonschema.ValidationError:
  713. # We don't recognize the message format, but don't want Telegram
  714. # retrying this over and over again, so respond with 200 OK
  715. return HttpResponse()
  716. if "/start" not in doc["message"]["text"]:
  717. return HttpResponse()
  718. chat = doc["message"]["chat"]
  719. name = max(chat.get("title", ""), chat.get("username", ""))
  720. invite = render_to_string("integrations/telegram_invite.html", {
  721. "qs": signing.dumps((chat["id"], chat["type"], name))
  722. })
  723. Telegram.send(chat["id"], invite)
  724. return HttpResponse()
  725. @login_required
  726. def add_telegram(request):
  727. chat_id, chat_type, chat_name = None, None, None
  728. qs = request.META["QUERY_STRING"]
  729. if qs:
  730. chat_id, chat_type, chat_name = signing.loads(qs, max_age=600)
  731. if request.method == "POST":
  732. channel = Channel(user=request.team.user, kind="telegram")
  733. channel.value = json.dumps({
  734. "id": chat_id,
  735. "type": chat_type,
  736. "name": chat_name
  737. })
  738. channel.save()
  739. channel.assign_all_checks()
  740. messages.success(request, "The Telegram integration has been added!")
  741. return redirect("hc-channels")
  742. ctx = {
  743. "chat_id": chat_id,
  744. "chat_type": chat_type,
  745. "chat_name": chat_name,
  746. "bot_name": settings.TELEGRAM_BOT_NAME
  747. }
  748. return render(request, "integrations/add_telegram.html", ctx)
  749. @login_required
  750. def add_sms(request):
  751. if settings.TWILIO_AUTH is None:
  752. raise Http404("sms integration is not available")
  753. if request.method == "POST":
  754. form = AddSmsForm(request.POST)
  755. if form.is_valid():
  756. channel = Channel(user=request.team.user, kind="sms")
  757. channel.value = json.dumps({
  758. "label": form.cleaned_data["label"],
  759. "value": form.cleaned_data["value"]
  760. })
  761. channel.save()
  762. channel.assign_all_checks()
  763. return redirect("hc-channels")
  764. else:
  765. form = AddSmsForm()
  766. ctx = {
  767. "page": "channels",
  768. "form": form,
  769. "profile": request.team
  770. }
  771. return render(request, "integrations/add_sms.html", ctx)
  772. @login_required
  773. def add_zendesk(request):
  774. if settings.ZENDESK_CLIENT_ID is None:
  775. raise Http404("zendesk integration is not available")
  776. if request.method == "POST":
  777. domain = request.POST.get("subdomain")
  778. request.session["subdomain"] = domain
  779. redirect_uri = settings.SITE_ROOT + reverse("hc-add-zendesk")
  780. auth_url = "https://%s.zendesk.com/oauth/authorizations/new?" % domain
  781. auth_url += urlencode({
  782. "client_id": settings.ZENDESK_CLIENT_ID,
  783. "redirect_uri": redirect_uri,
  784. "response_type": "code",
  785. "scope": "requests:read requests:write",
  786. "state": _prepare_state(request, "zendesk")
  787. })
  788. return redirect(auth_url)
  789. if "code" in request.GET:
  790. code = _get_validated_code(request, "zendesk")
  791. if code is None:
  792. return HttpResponseBadRequest()
  793. domain = request.session.pop("subdomain")
  794. url = "https://%s.zendesk.com/oauth/tokens" % domain
  795. redirect_uri = settings.SITE_ROOT + reverse("hc-add-zendesk")
  796. result = requests.post(url, {
  797. "client_id": settings.ZENDESK_CLIENT_ID,
  798. "client_secret": settings.ZENDESK_CLIENT_SECRET,
  799. "code": code,
  800. "grant_type": "authorization_code",
  801. "redirect_uri": redirect_uri,
  802. "scope": "read"
  803. })
  804. doc = result.json()
  805. if "access_token" in doc:
  806. doc["subdomain"] = domain
  807. channel = Channel(kind="zendesk")
  808. channel.user = request.team.user
  809. channel.value = json.dumps(doc)
  810. channel.save()
  811. channel.assign_all_checks()
  812. messages.success(request,
  813. "The Zendesk integration has been added!")
  814. else:
  815. messages.warning(request, "Something went wrong")
  816. return redirect("hc-channels")
  817. ctx = {"page": "channels"}
  818. return render(request, "integrations/add_zendesk.html", ctx)