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.

436 lines
13 KiB

10 years ago
8 years ago
10 years ago
10 years ago
10 years ago
9 years ago
8 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
8 years ago
9 years ago
9 years ago
9 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. import re
  4. from django.conf import settings
  5. from django.contrib import messages
  6. from django.contrib.auth import login as auth_login
  7. from django.contrib.auth import logout as auth_logout
  8. from django.contrib.auth import authenticate
  9. from django.contrib.auth.decorators import login_required
  10. from django.contrib.auth.models import User
  11. from django.core import signing
  12. from django.http import HttpResponseForbidden, HttpResponseBadRequest
  13. from django.shortcuts import redirect, render
  14. from django.utils.timezone import now
  15. from django.views.decorators.csrf import csrf_exempt
  16. from django.views.decorators.http import require_POST
  17. from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm,
  18. InviteTeamMemberForm, RemoveTeamMemberForm,
  19. ReportSettingsForm, SetPasswordForm,
  20. TeamNameForm, AvailableEmailForm,
  21. ExistingEmailForm)
  22. from hc.accounts.models import Profile, Member
  23. from hc.api.models import Channel, Check
  24. from hc.lib.badges import get_badge_url
  25. from hc.payments.models import Subscription
  26. NEXT_WHITELIST = ("/checks/",
  27. "/integrations/add_slack/")
  28. def _make_user(email):
  29. username = str(uuid.uuid4())[:30]
  30. user = User(username=username, email=email)
  31. user.set_unusable_password()
  32. user.save()
  33. # Ensure a profile gets created
  34. Profile.objects.for_user(user)
  35. check = Check(user=user)
  36. check.name = "My First Check"
  37. check.save()
  38. channel = Channel(user=user)
  39. channel.kind = "email"
  40. channel.value = email
  41. channel.email_verified = True
  42. channel.save()
  43. channel.checks.add(check)
  44. return user
  45. def _ensure_own_team(request):
  46. """ Make sure user is switched to their own team. """
  47. if request.team != request.profile:
  48. request.team = request.profile
  49. request.profile.current_team = request.profile
  50. request.profile.save()
  51. def _redirect_after_login(request):
  52. """ Redirect to the URL indicated in ?next= query parameter. """
  53. redirect_url = request.GET.get("next")
  54. if redirect_url in NEXT_WHITELIST:
  55. return redirect(redirect_url)
  56. return redirect("hc-checks")
  57. def login(request):
  58. form = EmailPasswordForm()
  59. magic_form = ExistingEmailForm()
  60. if request.method == 'POST':
  61. if request.POST.get("action") == "login":
  62. form = EmailPasswordForm(request.POST)
  63. if form.is_valid():
  64. auth_login(request, form.user)
  65. return _redirect_after_login(request)
  66. else:
  67. magic_form = ExistingEmailForm(request.POST)
  68. if magic_form.is_valid():
  69. profile = Profile.objects.for_user(magic_form.user)
  70. redirect_url = request.GET.get("next")
  71. if redirect_url in NEXT_WHITELIST:
  72. profile.send_instant_login_link(redirect_url=redirect_url)
  73. else:
  74. profile.send_instant_login_link()
  75. return redirect("hc-login-link-sent")
  76. bad_link = request.session.pop("bad_link", None)
  77. ctx = {
  78. "page": "login",
  79. "form": form,
  80. "magic_form": magic_form,
  81. "bad_link": bad_link
  82. }
  83. return render(request, "accounts/login.html", ctx)
  84. def logout(request):
  85. auth_logout(request)
  86. return redirect("hc-index")
  87. @require_POST
  88. def signup(request):
  89. if not settings.REGISTRATION_OPEN:
  90. return HttpResponseForbidden()
  91. ctx = {}
  92. form = AvailableEmailForm(request.POST)
  93. if form.is_valid():
  94. email = form.cleaned_data["identity"]
  95. user = _make_user(email)
  96. profile = Profile.objects.for_user(user)
  97. profile.send_instant_login_link()
  98. ctx["created"] = True
  99. else:
  100. ctx = {"form": form}
  101. return render(request, "accounts/signup_result.html", ctx)
  102. def login_link_sent(request):
  103. return render(request, "accounts/login_link_sent.html")
  104. def link_sent(request):
  105. return render(request, "accounts/link_sent.html")
  106. def check_token(request, username, token):
  107. if request.user.is_authenticated and request.user.username == username:
  108. # User is already logged in
  109. return _redirect_after_login(request)
  110. # Some email servers open links in emails to check for malicious content.
  111. # To work around this, we sign user in if the method is POST.
  112. #
  113. # If the method is GET, we instead serve a HTML form and a piece
  114. # of Javascript to automatically submit it.
  115. if request.method == "POST":
  116. user = authenticate(username=username, token=token)
  117. if user is not None and user.is_active:
  118. user.profile.token = ""
  119. user.profile.save()
  120. auth_login(request, user)
  121. return _redirect_after_login(request)
  122. request.session["bad_link"] = True
  123. return redirect("hc-login")
  124. return render(request, "accounts/check_token_submit.html")
  125. @login_required
  126. def profile(request):
  127. _ensure_own_team(request)
  128. profile = request.profile
  129. ctx = {
  130. "page": "profile",
  131. "profile": profile,
  132. "show_api_keys": False,
  133. "api_status": "default",
  134. "team_status": "default"
  135. }
  136. if request.method == "POST":
  137. if "change_email" in request.POST:
  138. profile.send_change_email_link()
  139. return redirect("hc-link-sent")
  140. elif "set_password" in request.POST:
  141. profile.send_set_password_link()
  142. return redirect("hc-link-sent")
  143. elif "create_api_keys" in request.POST:
  144. profile.set_api_keys()
  145. ctx["show_api_keys"] = True
  146. ctx["api_keys_created"] = True
  147. ctx["api_status"] = "success"
  148. elif "revoke_api_keys" in request.POST:
  149. profile.api_key_id = ""
  150. profile.api_key = ""
  151. profile.api_key_readonly = ""
  152. profile.save()
  153. ctx["api_keys_revoked"] = True
  154. ctx["api_status"] = "info"
  155. elif "show_api_keys" in request.POST:
  156. ctx["show_api_keys"] = True
  157. elif "invite_team_member" in request.POST:
  158. if not profile.can_invite():
  159. return HttpResponseForbidden()
  160. form = InviteTeamMemberForm(request.POST)
  161. if form.is_valid():
  162. email = form.cleaned_data["email"]
  163. try:
  164. user = User.objects.get(email=email)
  165. except User.DoesNotExist:
  166. user = _make_user(email)
  167. profile.invite(user)
  168. ctx["team_member_invited"] = email
  169. ctx["team_status"] = "success"
  170. elif "remove_team_member" in request.POST:
  171. form = RemoveTeamMemberForm(request.POST)
  172. if form.is_valid():
  173. email = form.cleaned_data["email"]
  174. farewell_user = User.objects.get(email=email)
  175. farewell_user.profile.current_team = None
  176. farewell_user.profile.save()
  177. Member.objects.filter(team=profile,
  178. user=farewell_user).delete()
  179. ctx["team_member_removed"] = email
  180. ctx["team_status"] = "info"
  181. elif "set_team_name" in request.POST:
  182. form = TeamNameForm(request.POST)
  183. if form.is_valid():
  184. profile.team_name = form.cleaned_data["team_name"]
  185. profile.save()
  186. ctx["team_name_updated"] = True
  187. ctx["team_status"] = "success"
  188. return render(request, "accounts/profile.html", ctx)
  189. @login_required
  190. def notifications(request):
  191. _ensure_own_team(request)
  192. profile = request.profile
  193. ctx = {
  194. "status": "default",
  195. "page": "profile",
  196. "profile": profile
  197. }
  198. if request.method == "POST":
  199. form = ReportSettingsForm(request.POST)
  200. if form.is_valid():
  201. if profile.reports_allowed != form.cleaned_data["reports_allowed"]:
  202. profile.reports_allowed = form.cleaned_data["reports_allowed"]
  203. if profile.reports_allowed:
  204. profile.next_report_date = now() + td(days=30)
  205. else:
  206. profile.next_report_date = None
  207. if profile.nag_period != form.cleaned_data["nag_period"]:
  208. # Set the new nag period
  209. profile.nag_period = form.cleaned_data["nag_period"]
  210. # and schedule next_nag_date:
  211. if profile.nag_period:
  212. profile.next_nag_date = now() + profile.nag_period
  213. else:
  214. profile.next_nag_date = None
  215. profile.save()
  216. ctx["status"] = "info"
  217. return render(request, "accounts/notifications.html", ctx)
  218. @login_required
  219. def badges(request):
  220. _ensure_own_team(request)
  221. teams = [request.profile]
  222. for membership in request.user.memberships.all():
  223. teams.append(membership.team)
  224. badge_sets = []
  225. for team in teams:
  226. tags = set()
  227. for check in Check.objects.filter(user=team.user):
  228. tags.update(check.tags_list())
  229. sorted_tags = sorted(tags, key=lambda s: s.lower())
  230. sorted_tags.append("*") # For the "overall status" badge
  231. urls = []
  232. username = team.user.username
  233. for tag in sorted_tags:
  234. if not re.match("^[\w-]+$", tag) and tag != "*":
  235. continue
  236. urls.append({
  237. "svg": get_badge_url(username, tag),
  238. "json": get_badge_url(username, tag, format="json"),
  239. })
  240. badge_sets.append({"team": team, "urls": urls})
  241. ctx = {
  242. "page": "profile",
  243. "badges": badge_sets
  244. }
  245. return render(request, "accounts/badges.html", ctx)
  246. @login_required
  247. def set_password(request, token):
  248. if not request.profile.check_token(token, "set-password"):
  249. return HttpResponseBadRequest()
  250. if request.method == "POST":
  251. form = SetPasswordForm(request.POST)
  252. if form.is_valid():
  253. password = form.cleaned_data["password"]
  254. request.user.set_password(password)
  255. request.user.save()
  256. request.profile.token = ""
  257. request.profile.save()
  258. # Setting a password logs the user out, so here we
  259. # log them back in.
  260. u = authenticate(username=request.user.email, password=password)
  261. auth_login(request, u)
  262. messages.success(request, "Your password has been set!")
  263. return redirect("hc-profile")
  264. return render(request, "accounts/set_password.html", {})
  265. @login_required
  266. def change_email(request, token):
  267. if not request.profile.check_token(token, "change-email"):
  268. return HttpResponseBadRequest()
  269. if request.method == "POST":
  270. form = ChangeEmailForm(request.POST)
  271. if form.is_valid():
  272. request.user.email = form.cleaned_data["email"]
  273. request.user.set_unusable_password()
  274. request.user.save()
  275. request.profile.token = ""
  276. request.profile.save()
  277. return redirect("hc-change-email-done")
  278. else:
  279. form = ChangeEmailForm()
  280. return render(request, "accounts/change_email.html", {"form": form})
  281. def change_email_done(request):
  282. return render(request, "accounts/change_email_done.html")
  283. @csrf_exempt
  284. def unsubscribe_reports(request, username):
  285. signer = signing.TimestampSigner(salt="reports")
  286. try:
  287. username = signer.unsign(username)
  288. except signing.BadSignature:
  289. return render(request, "bad_link.html")
  290. # Some email servers open links in emails to check for malicious content.
  291. # To work around this, we serve a form that auto-submits with JS.
  292. if "ask" in request.GET and request.method != "POST":
  293. return render(request, "accounts/unsubscribe_submit.html")
  294. user = User.objects.get(username=username)
  295. profile = Profile.objects.for_user(user)
  296. profile.reports_allowed = False
  297. profile.next_report_date = None
  298. profile.nag_period = td()
  299. profile.next_nag_date = None
  300. profile.save()
  301. return render(request, "accounts/unsubscribed.html")
  302. @login_required
  303. def switch_team(request, target_username):
  304. try:
  305. target_team = Profile.objects.get(user__username=target_username)
  306. except Profile.DoesNotExist:
  307. return HttpResponseForbidden()
  308. # The rules:
  309. # Superuser can switch to any team.
  310. access_ok = request.user.is_superuser
  311. # Users can switch to their own teams.
  312. if not access_ok and target_team == request.profile:
  313. access_ok = True
  314. # Users can switch to teams they are members of.
  315. if not access_ok:
  316. access_ok = request.user.memberships.filter(team=target_team).exists()
  317. if not access_ok:
  318. return HttpResponseForbidden()
  319. request.profile.current_team = target_team
  320. request.profile.save()
  321. return redirect("hc-checks")
  322. @require_POST
  323. @login_required
  324. def close(request):
  325. user = request.user
  326. # Subscription needs to be canceled before it is deleted:
  327. sub = Subscription.objects.filter(user=user).first()
  328. if sub:
  329. sub.cancel()
  330. user.delete()
  331. # Deleting user also deletes its profile, checks, channels etc.
  332. request.session.flush()
  333. return redirect("hc-index")