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.

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