From fdf9c607e52ff05811da5fe9c14c303f1298a6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Mon, 9 May 2016 15:35:13 +0300 Subject: [PATCH] Team Access, test cleanup --- hc/accounts/admin.py | 14 +++-- hc/accounts/forms.py | 4 ++ .../management/commands/createprofiles.py | 3 +- hc/accounts/middleware.py | 10 ++++ .../migrations/0006_profile_current_team.py | 21 +++++++ hc/accounts/models.py | 14 +---- hc/accounts/tests/test_check_token.py | 2 - hc/accounts/tests/test_profile.py | 36 +++++++----- hc/accounts/tests/test_switch_team.py | 37 ++++++++++++ hc/accounts/urls.py | 4 ++ hc/accounts/views.py | 46 +++++++++++---- hc/api/management/commands/prunepings.py | 2 +- hc/api/management/commands/prunepingsslow.py | 2 +- hc/api/management/commands/sendreports.py | 6 -- hc/api/tests/test_create_check.py | 2 - hc/api/tests/test_list_checks.py | 8 +-- hc/front/tests/test_channel_checks.py | 8 +-- hc/front/tests/test_log.py | 6 -- hc/front/tests/test_remove_channel.py | 8 +-- hc/front/tests/test_remove_check.py | 8 +-- hc/front/tests/test_update_channel.py | 26 +++------ hc/front/tests/test_update_name.py | 7 --- hc/front/tests/test_update_timeout.py | 6 -- hc/front/urls.py | 1 - hc/front/views.py | 57 ++++++------------- hc/payments/views.py | 2 +- hc/settings.py | 1 + hc/test.py | 15 +++++ templates/accounts/profile.html | 42 ++++++++++++++ templates/base.html | 19 ++++++- 30 files changed, 255 insertions(+), 162 deletions(-) create mode 100644 hc/accounts/middleware.py create mode 100644 hc/accounts/migrations/0006_profile_current_team.py create mode 100644 hc/accounts/tests/test_switch_team.py diff --git a/hc/accounts/admin.py b/hc/accounts/admin.py index 1c019e5a..6985c154 100644 --- a/hc/accounts/admin.py +++ b/hc/accounts/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User +from django.core.urlresolvers import reverse from hc.accounts.models import Profile from hc.api.models import Channel, Check @@ -18,8 +19,8 @@ class ProfileAdmin(admin.ModelAdmin): class HcUserAdmin(UserAdmin): actions = ["send_report"] - list_display = ('id', 'username', 'email', 'date_joined', 'involvement', - 'is_staff') + list_display = ('id', 'email', 'date_joined', 'involvement', + 'is_staff', 'checks') ordering = ["-id"] @@ -46,10 +47,15 @@ class HcUserAdmin(UserAdmin): involvement.allow_tags = True + def checks(self, user): + url = reverse("hc-switch-team", args=[user.username]) + return "Checks" % url + + checks.allow_tags = True + def send_report(self, request, qs): for user in qs: - profile = Profile.objects.for_user(user) - profile.send_report() + user.profile.send_report() self.message_user(request, "%d email(s) sent" % qs.count()) diff --git a/hc/accounts/forms.py b/hc/accounts/forms.py index 48aeabfb..544cc3b3 100644 --- a/hc/accounts/forms.py +++ b/hc/accounts/forms.py @@ -27,3 +27,7 @@ class InviteTeamMemberForm(forms.Form): class RemoveTeamMemberForm(forms.Form): email = LowercaseEmailField() + + +class TeamNameForm(forms.Form): + team_name = forms.CharField(max_length=200, required=True) diff --git a/hc/accounts/management/commands/createprofiles.py b/hc/accounts/management/commands/createprofiles.py index c1fbf7a8..23dc795d 100644 --- a/hc/accounts/management/commands/createprofiles.py +++ b/hc/accounts/management/commands/createprofiles.py @@ -8,7 +8,6 @@ class Command(BaseCommand): def handle(self, *args, **options): for user in User.objects.all(): - # this should create profile object if it does not exist - Profile.objects.for_user(user) + Profile.objects.get_or_create(user_id=user.id) print("Done.") diff --git a/hc/accounts/middleware.py b/hc/accounts/middleware.py new file mode 100644 index 00000000..bba200fb --- /dev/null +++ b/hc/accounts/middleware.py @@ -0,0 +1,10 @@ +class TeamAccessMiddleware(object): + def process_request(self, request): + if not request.user.is_authenticated(): + return + + profile = request.user.profile + if profile.current_team: + request.team = profile.current_team + else: + request.team = profile diff --git a/hc/accounts/migrations/0006_profile_current_team.py b/hc/accounts/migrations/0006_profile_current_team.py new file mode 100644 index 00000000..2d93e974 --- /dev/null +++ b/hc/accounts/migrations/0006_profile_current_team.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-05-09 10:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0005_auto_20160509_0801'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='current_team', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.Profile'), + ), + ] diff --git a/hc/accounts/models.py b/hc/accounts/models.py index 9a229314..d2283ac7 100644 --- a/hc/accounts/models.py +++ b/hc/accounts/models.py @@ -13,27 +13,17 @@ from django.utils import timezone from hc.lib import emails -class ProfileManager(models.Manager): - - def for_user(self, user): - profile, created = Profile.objects.get_or_create(user_id=user.id) - return profile - - class Profile(models.Model): # Owner: user = models.OneToOneField(User, blank=True, null=True) - team_name = models.CharField(max_length=200, blank=True) team_access_allowed = models.BooleanField(default=False) - next_report_date = models.DateTimeField(null=True, blank=True) reports_allowed = models.BooleanField(default=True) ping_log_limit = models.IntegerField(default=100) token = models.CharField(max_length=128, blank=True) api_key = models.CharField(max_length=128, blank=True) - - objects = ProfileManager() + current_team = models.ForeignKey("self", null=True) def __str__(self): return self.team_name or self.user.email @@ -85,7 +75,7 @@ class Profile(models.Model): member = Member(team=self, user=user) member.save() - Profile.objects.for_user(user).send_instant_login_link(self) + user.profile.send_instant_login_link(self) class Member(models.Model): diff --git a/hc/accounts/tests/test_check_token.py b/hc/accounts/tests/test_check_token.py index 51ca036b..d1a33112 100644 --- a/hc/accounts/tests/test_check_token.py +++ b/hc/accounts/tests/test_check_token.py @@ -1,5 +1,4 @@ from django.contrib.auth.hashers import make_password -from hc.accounts.models import Profile from hc.test import BaseTestCase @@ -7,7 +6,6 @@ class CheckTokenTestCase(BaseTestCase): def setUp(self): super(CheckTokenTestCase, self).setUp() - self.profile = Profile(user=self.alice) self.profile.token = make_password("secret-token") self.profile.save() diff --git a/hc/accounts/tests/test_profile.py b/hc/accounts/tests/test_profile.py index 04441461..97cabc55 100644 --- a/hc/accounts/tests/test_profile.py +++ b/hc/accounts/tests/test_profile.py @@ -2,11 +2,11 @@ from django.contrib.auth.models import User from django.core import mail from hc.test import BaseTestCase -from hc.accounts.models import Profile, Member +from hc.accounts.models import Member from hc.api.models import Check -class LoginTestCase(BaseTestCase): +class ProfileTestCase(BaseTestCase): def test_it_sends_set_password_link(self): self.client.login(username="alice@example.org", password="password") @@ -16,8 +16,9 @@ class LoginTestCase(BaseTestCase): assert r.status_code == 302 # profile.token should be set now - profile = Profile.objects.for_user(self.alice) - self.assertTrue(len(profile.token) > 10) + self.alice.profile.refresh_from_db() + token = self.alice.profile.token + self.assertTrue(len(token) > 10) # And an email should have been sent self.assertEqual(len(mail.outbox), 1) @@ -31,8 +32,9 @@ class LoginTestCase(BaseTestCase): r = self.client.post("/accounts/profile/", form) assert r.status_code == 200 - profile = Profile.objects.for_user(self.alice) - self.assertTrue(len(profile.api_key) > 10) + self.alice.profile.refresh_from_db() + api_key = self.alice.profile.api_key + self.assertTrue(len(api_key) > 10) def test_it_revokes_api_key(self): self.client.login(username="alice@example.org", password="password") @@ -41,15 +43,14 @@ class LoginTestCase(BaseTestCase): r = self.client.post("/accounts/profile/", form) assert r.status_code == 200 - profile = Profile.objects.for_user(self.alice) - self.assertEqual(profile.api_key, "") + self.alice.profile.refresh_from_db() + self.assertEqual(self.alice.profile.api_key, "") def test_it_sends_report(self): check = Check(name="Test Check", user=self.alice) check.save() - profile = Profile.objects.for_user(self.alice) - profile.send_report() + self.alice.profile.send_report() # And an email should have been sent self.assertEqual(len(mail.outbox), 1) @@ -65,8 +66,7 @@ class LoginTestCase(BaseTestCase): r = self.client.post("/accounts/profile/", form) assert r.status_code == 200 - profile = Profile.objects.for_user(self.alice) - member = profile.member_set.get() + member = self.alice.profile.member_set.get() self.assertEqual(member.user.email, "bob@example.org") @@ -81,7 +81,7 @@ class LoginTestCase(BaseTestCase): bob = User(username="bob", email="bob@example.org") bob.save() - m = Member(team=Profile.objects.for_user(self.alice), user=bob) + m = Member(team=self.alice.profile, user=bob) m.save() form = {"remove_team_member": "1", "email": "bob@example.org"} @@ -89,3 +89,13 @@ class LoginTestCase(BaseTestCase): assert r.status_code == 200 self.assertEqual(Member.objects.count(), 0) + + def test_it_sets_team_name(self): + self.client.login(username="alice@example.org", password="password") + + form = {"set_team_name": "1", "team_name": "Alpha Team"} + r = self.client.post("/accounts/profile/", form) + assert r.status_code == 200 + + self.alice.profile.refresh_from_db() + self.assertEqual(self.alice.profile.team_name, "Alpha Team") diff --git a/hc/accounts/tests/test_switch_team.py b/hc/accounts/tests/test_switch_team.py new file mode 100644 index 00000000..c9e37f39 --- /dev/null +++ b/hc/accounts/tests/test_switch_team.py @@ -0,0 +1,37 @@ +from django.contrib.auth.models import User + +from hc.test import BaseTestCase +from hc.accounts.models import Member, Profile + + +class SwitchTeamTestCase(BaseTestCase): + + def setUp(self): + super(SwitchTeamTestCase, self).setUp() + + self.bob = User(username="bob", email="bob@example.org") + self.bob.set_password("password") + self.bob.save() + + bobs_profile = Profile(user=self.bob) + bobs_profile.save() + + + m = Member(team=bobs_profile, user=self.alice) + m.save() + + def test_it_switches(self): + self.client.login(username="alice@example.org", password="password") + + url = "/accounts/switch_team/%s/" % self.bob.username + r = self.client.get(url, follow=True) + + self.assertContains(r, "bob@example.org") + + + def test_it_checks_team_membership(self): + self.client.login(username="charlie@example.org", password="password") + + url = "/accounts/switch_team/%s/" % self.bob.username + r = self.client.get(url) + self.assertEqual(r.status_code, 403) diff --git a/hc/accounts/urls.py b/hc/accounts/urls.py index 333a0c8f..452c4c09 100644 --- a/hc/accounts/urls.py +++ b/hc/accounts/urls.py @@ -21,4 +21,8 @@ urlpatterns = [ url(r'^set_password/([\w-]+)/$', views.set_password, name="hc-set-password"), + url(r'^switch_team/([\w-]+)/$', + views.switch_team, name="hc-switch-team"), + + ] diff --git a/hc/accounts/views.py b/hc/accounts/views.py index 2efbfdc6..5078eac6 100644 --- a/hc/accounts/views.py +++ b/hc/accounts/views.py @@ -8,11 +8,11 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.hashers import check_password from django.contrib.auth.models import User from django.core import signing -from django.http import HttpResponseBadRequest +from django.http import HttpResponseForbidden, HttpResponseBadRequest from django.shortcuts import redirect, render from hc.accounts.forms import (EmailPasswordForm, InviteTeamMemberForm, RemoveTeamMemberForm, ReportSettingsForm, - SetPasswordForm) + SetPasswordForm, TeamNameForm) from hc.accounts.models import Profile, Member from hc.api.models import Channel, Check @@ -23,6 +23,9 @@ def _make_user(email): user.set_unusable_password() user.save() + profile = Profile(user=user) + profile.save() + channel = Channel() channel.user = user channel.kind = "email" @@ -67,8 +70,7 @@ def login(request): user = _make_user(email) _associate_demo_check(request, user) - profile = Profile.objects.for_user(user) - profile.send_instant_login_link() + user.profile.send_instant_login_link() return redirect("hc-login-link-sent") else: @@ -106,9 +108,8 @@ def check_token(request, username, token): # This should get rid of "welcome_code" in session request.session.flush() - profile = Profile.objects.for_user(user) - profile.token = "" - profile.save() + user.profile.token = "" + user.profile.save() auth_login(request, user) return redirect("hc-checks") @@ -119,7 +120,7 @@ def check_token(request, username, token): @login_required def profile(request): - profile = Profile.objects.for_user(request.user) + profile = request.user.profile show_api_key = False if request.method == "POST": @@ -161,6 +162,12 @@ def profile(request): email = form.cleaned_data["email"] Member.objects.filter(team=profile, user__email=email).delete() messages.info(request, "%s removed from team!" % email) + elif "set_team_name" in request.POST: + form = TeamNameForm(request.POST) + if form.is_valid(): + profile.team_name = form.cleaned_data["team_name"] + profile.save() + messages.info(request, "Team Name updated!") ctx = { "profile": profile, @@ -172,7 +179,7 @@ def profile(request): @login_required def set_password(request, token): - profile = Profile.objects.for_user(request.user) + profile = request.user.profile if not check_password(token, profile.token): return HttpResponseBadRequest() @@ -204,8 +211,23 @@ def unsubscribe_reports(request, username): return HttpResponseBadRequest() user = User.objects.get(username=username) - profile = Profile.objects.for_user(user) - profile.reports_allowed = False - profile.save() + user.profile.reports_allowed = False + user.profile.save() return render(request, "accounts/unsubscribed.html") + + +def switch_team(request, target_username): + other_user = User.objects.get(username=target_username) + + # Superuser can switch to any team. + # Other users can only switch to a team they are members of. + if not request.user.is_superuser: + q = Member.objects.filter(team=other_user.profile, user=request.user) + if q.count() == 0: + return HttpResponseForbidden() + + request.user.profile.current_team = other_user.profile + request.user.profile.save() + + return redirect("hc-checks") diff --git a/hc/api/management/commands/prunepings.py b/hc/api/management/commands/prunepings.py index 0050d6e8..7a233001 100644 --- a/hc/api/management/commands/prunepings.py +++ b/hc/api/management/commands/prunepings.py @@ -11,7 +11,7 @@ class Command(BaseCommand): def handle(self, *args, **options): # Create any missing user profiles for user in User.objects.filter(profile=None): - Profile.objects.for_user(user) + Profile.objects.get_or_create(user_id=user.id) q = Ping.objects q = q.annotate(limit=F("owner__user__profile__ping_log_limit")) diff --git a/hc/api/management/commands/prunepingsslow.py b/hc/api/management/commands/prunepingsslow.py index 56b0bcdd..1ceabf85 100644 --- a/hc/api/management/commands/prunepingsslow.py +++ b/hc/api/management/commands/prunepingsslow.py @@ -18,7 +18,7 @@ class Command(BaseCommand): def handle(self, *args, **options): # Create any missing user profiles for user in User.objects.filter(profile=None): - Profile.objects.for_user(user) + Profile.objects.get_or_create(user_id=user.id) checks = Check.objects.filter(user__isnull=False) checks = checks.annotate(limit=F("user__profile__ping_log_limit")) diff --git a/hc/api/management/commands/sendreports.py b/hc/api/management/commands/sendreports.py index 9ef55eb2..47afb724 100644 --- a/hc/api/management/commands/sendreports.py +++ b/hc/api/management/commands/sendreports.py @@ -1,6 +1,5 @@ from datetime import timedelta -from django.contrib.auth.models import User from django.core.management.base import BaseCommand from django.db.models import Q from django.utils import timezone @@ -19,11 +18,6 @@ class Command(BaseCommand): tmpl = "Sending monthly report to %s" def handle(self, *args, **options): - # Create any missing profiles - for u in User.objects.filter(profile__isnull=True): - self.stdout.write("Creating profile for %s" % u.email) - Profile.objects.for_user(u) - now = timezone.now() month_before = now - timedelta(days=30) diff --git a/hc/api/tests/test_create_check.py b/hc/api/tests/test_create_check.py index 52d3084f..a18a639a 100644 --- a/hc/api/tests/test_create_check.py +++ b/hc/api/tests/test_create_check.py @@ -9,8 +9,6 @@ class CreateCheckTestCase(BaseTestCase): def setUp(self): super(CreateCheckTestCase, self).setUp() - self.profile = Profile(user=self.alice, api_key="abc") - self.profile.save() def post(self, url, data): return self.client.post(url, json.dumps(data), diff --git a/hc/api/tests/test_list_checks.py b/hc/api/tests/test_list_checks.py index 15a36e62..2ee8563b 100644 --- a/hc/api/tests/test_list_checks.py +++ b/hc/api/tests/test_list_checks.py @@ -3,14 +3,13 @@ from datetime import timedelta as td from hc.api.models import Check, User from hc.test import BaseTestCase -from hc.accounts.models import Profile + class ListChecksTestCase(BaseTestCase): def setUp(self): super(ListChecksTestCase, self).setUp() - self.profile = Profile(user=self.alice, api_key="abc") - self.profile.save() + self.checks = [ Check(user=self.alice, name="Alice 1", timeout=td(seconds=3600), grace=td(seconds=900)), Check(user=self.alice, name="Alice 2", timeout=td(seconds=86400), grace=td(seconds=3600)), @@ -40,8 +39,9 @@ class ListChecksTestCase(BaseTestCase): bob = User(username="bob", email="bob@example.com") bob.save() bob_check = Check(user=bob, name="Bob 1") + bob_check.save() - r = self.get("/api/v1/checks/", { "api_key": "abc" }) + r = self.get("/api/v1/checks/", {"api_key": "abc"}) self.assertEqual(len(r.json()["checks"]), 2) checks = { check["name"]: check for check in r.json()["checks"] } diff --git a/hc/front/tests/test_channel_checks.py b/hc/front/tests/test_channel_checks.py index adfc082c..1f6c8672 100644 --- a/hc/front/tests/test_channel_checks.py +++ b/hc/front/tests/test_channel_checks.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Channel from hc.test import BaseTestCase @@ -20,14 +18,10 @@ class ChannelChecksTestCase(BaseTestCase): self.assertContains(r, "Assign Checks to Channel", status_code=200) def test_it_checks_owner(self): - mallory = User(username="mallory", email="mallory@example.org") - mallory.set_password("password") - mallory.save() - # channel does not belong to mallory so this should come back # with 403 Forbidden: url = "/integrations/%s/checks/" % self.channel.code - self.client.login(username="mallory@example.org", password="password") + self.client.login(username="charlie@example.org", password="password") r = self.client.get(url) assert r.status_code == 403 diff --git a/hc/front/tests/test_log.py b/hc/front/tests/test_log.py index d4f420be..c39d4145 100644 --- a/hc/front/tests/test_log.py +++ b/hc/front/tests/test_log.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Check, Ping from hc.test import BaseTestCase @@ -37,10 +35,6 @@ class LogTestCase(BaseTestCase): assert r.status_code == 404 def test_it_checks_ownership(self): - charlie = User(username="charlie", email="charlie@example.org") - charlie.set_password("password") - charlie.save() - url = "/checks/%s/log/" % self.check.code self.client.login(username="charlie@example.org", password="password") r = self.client.get(url) diff --git a/hc/front/tests/test_remove_channel.py b/hc/front/tests/test_remove_channel.py index d6a94fd2..aa083b43 100644 --- a/hc/front/tests/test_remove_channel.py +++ b/hc/front/tests/test_remove_channel.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Channel from hc.test import BaseTestCase @@ -31,11 +29,7 @@ class RemoveChannelTestCase(BaseTestCase): def test_it_checks_owner(self): url = "/integrations/%s/remove/" % self.channel.code - mallory = User(username="mallory", email="mallory@example.org") - mallory.set_password("password") - mallory.save() - - self.client.login(username="mallory@example.org", password="password") + self.client.login(username="charlie@example.org", password="password") r = self.client.post(url) assert r.status_code == 403 diff --git a/hc/front/tests/test_remove_check.py b/hc/front/tests/test_remove_check.py index 10dc73aa..c0d5277e 100644 --- a/hc/front/tests/test_remove_check.py +++ b/hc/front/tests/test_remove_check.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Check from hc.test import BaseTestCase @@ -30,11 +28,7 @@ class RemoveCheckTestCase(BaseTestCase): def test_it_checks_owner(self): url = "/checks/%s/remove/" % self.check.code - mallory = User(username="mallory", email="mallory@example.org") - mallory.set_password("password") - mallory.save() - - self.client.login(username="mallory@example.org", password="password") + self.client.login(username="charlie@example.org", password="password") r = self.client.post(url) assert r.status_code == 403 diff --git a/hc/front/tests/test_update_channel.py b/hc/front/tests/test_update_channel.py index d5900385..6eeb5108 100644 --- a/hc/front/tests/test_update_channel.py +++ b/hc/front/tests/test_update_channel.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Channel, Check from hc.test import BaseTestCase @@ -31,35 +29,27 @@ class UpdateChannelTestCase(BaseTestCase): assert checks[0].code == self.check.code def test_it_checks_channel_user(self): - mallory = User(username="mallory", email="mallory@example.org") - mallory.set_password("password") - mallory.save() - payload = {"channel": self.channel.code} - self.client.login(username="mallory@example.org", password="password") + self.client.login(username="charlie@example.org", password="password") r = self.client.post("/integrations/", data=payload) - # self.channel does not belong to mallory, this should fail-- + # self.channel does not belong to charlie, this should fail-- assert r.status_code == 403 def test_it_checks_check_user(self): - mallory = User(username="mallory", email="mallory@example.org") - mallory.set_password("password") - mallory.save() - - mc = Channel(user=mallory, kind="email") - mc.email = "mallory@example.org" - mc.save() + charlies_channel = Channel(user=self.charlie, kind="email") + charlies_channel.email = "charlie@example.org" + charlies_channel.save() payload = { - "channel": mc.code, + "channel": charlies_channel.code, "check-%s" % self.check.code: True } - self.client.login(username="mallory@example.org", password="password") + self.client.login(username="charlie@example.org", password="password") r = self.client.post("/integrations/", data=payload) - # mc belongs to mallorym but self.check does not-- + # mc belongs to charlie but self.check does not-- assert r.status_code == 403 def test_it_handles_missing_channel(self): diff --git a/hc/front/tests/test_update_name.py b/hc/front/tests/test_update_name.py index 9ef8327d..78035468 100644 --- a/hc/front/tests/test_update_name.py +++ b/hc/front/tests/test_update_name.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Check from hc.test import BaseTestCase @@ -23,11 +21,6 @@ class UpdateNameTestCase(BaseTestCase): assert check.name == "Alice Was Here" def test_it_checks_ownership(self): - - charlie = User(username="charlie", email="charlie@example.org") - charlie.set_password("password") - charlie.save() - url = "/checks/%s/name/" % self.check.code payload = {"name": "Charlie Sent This"} diff --git a/hc/front/tests/test_update_timeout.py b/hc/front/tests/test_update_timeout.py index 10c5acbd..5daeb213 100644 --- a/hc/front/tests/test_update_timeout.py +++ b/hc/front/tests/test_update_timeout.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import User - from hc.api.models import Check from hc.test import BaseTestCase @@ -41,10 +39,6 @@ class UpdateTimeoutTestCase(BaseTestCase): assert r.status_code == 404 def test_it_checks_ownership(self): - charlie = User(username="charlie", email="charlie@example.org") - charlie.set_password("password") - charlie.save() - url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 3600, "grace": 60} diff --git a/hc/front/urls.py b/hc/front/urls.py index fae8a337..18744795 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -8,7 +8,6 @@ urlpatterns = [ url(r'^checks/add/$', views.add_check, name="hc-add-check"), url(r'^checks/([\w-]+)/name/$', views.update_name, name="hc-update-name"), url(r'^checks/([\w-]+)/timeout/$', views.update_timeout, name="hc-update-timeout"), - url(r'^checks/([\w-]+)/email/$', views.email_preview), url(r'^checks/([\w-]+)/remove/$', views.remove_check, name="hc-remove-check"), url(r'^checks/([\w-]+)/log/$', views.log, name="hc-log"), url(r'^docs/$', views.docs, name="hc-docs"), diff --git a/hc/front/views.py b/hc/front/views.py index 9ce1d9ff..e5ac8791 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -11,7 +11,6 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.six.moves.urllib.parse import urlencode -from hc.accounts.models import Profile from hc.api.decorators import uuid_or_400 from hc.api.models import Channel, Check, Ping, DEFAULT_TIMEOUT, DEFAULT_GRACE from hc.front.forms import (AddChannelForm, AddWebhookForm, NameTagsForm, @@ -28,7 +27,7 @@ def pairwise(iterable): @login_required def my_checks(request): - checks = Check.objects.filter(user=request.user).order_by("created") + checks = Check.objects.filter(user=request.team.user).order_by("created") counter = Counter() down_tags, grace_tags = set(), set() @@ -122,7 +121,7 @@ def about(request): def add_check(request): assert request.method == "POST" - check = Check(user=request.user) + check = Check(user=request.team.user) check.save() check.assign_all_channels() @@ -136,7 +135,7 @@ def update_name(request, code): assert request.method == "POST" check = get_object_or_404(Check, code=code) - if check.user_id != request.user.id: + if check.user_id != request.team.user.id: return HttpResponseForbidden() form = NameTagsForm(request.POST) @@ -154,7 +153,7 @@ def update_timeout(request, code): assert request.method == "POST" check = get_object_or_404(Check, code=code) - if check.user != request.user: + if check.user != request.team.user: return HttpResponseForbidden() form = TimeoutForm(request.POST) @@ -166,36 +165,13 @@ def update_timeout(request, code): return redirect("hc-checks") -@login_required -@uuid_or_400 -def email_preview(request, code): - """ A debug view to see how email will look. - - Will keep it around until I'm happy with email stying. - - """ - - check = Check.objects.get(code=code) - if check.user != request.user: - return HttpResponseForbidden() - - ctx = { - "check": check, - "checks": check.user.check_set.all(), - "now": timezone.now() - - } - - return render(request, "emails/alert/body.html", ctx) - - @login_required @uuid_or_400 def remove_check(request, code): assert request.method == "POST" check = get_object_or_404(Check, code=code) - if check.user != request.user: + if check.user != request.team.user: return HttpResponseForbidden() check.delete() @@ -207,11 +183,10 @@ def remove_check(request, code): @uuid_or_400 def log(request, code): check = get_object_or_404(Check, code=code) - if check.user != request.user: + if check.user != request.team.user: return HttpResponseForbidden() - profile = Profile.objects.for_user(request.user) - limit = profile.ping_log_limit + limit = request.team.ping_log_limit pings = Ping.objects.filter(owner=check).order_by("-id")[:limit] pings = list(pings.iterator()) @@ -264,7 +239,7 @@ def channels(request): channel = Channel.objects.get(code=code) except Channel.DoesNotExist: return HttpResponseBadRequest() - if channel.user_id != request.user.id: + if channel.user_id != request.team.user.id: return HttpResponseForbidden() new_checks = [] @@ -275,17 +250,17 @@ def channels(request): check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() - if check.user_id != request.user.id: + if check.user_id != request.team.user.id: return HttpResponseForbidden() new_checks.append(check) channel.checks = new_checks return redirect("hc-channels") - channels = Channel.objects.filter(user=request.user).order_by("created") + channels = Channel.objects.filter(user=request.team.user).order_by("created") channels = channels.annotate(n_checks=Count("checks")) - num_checks = Check.objects.filter(user=request.user).count() + num_checks = Check.objects.filter(user=request.team.user).count() ctx = { "page": "channels", @@ -300,7 +275,7 @@ def do_add_channel(request, data): form = AddChannelForm(data) if form.is_valid(): channel = form.save(commit=False) - channel.user = request.user + channel.user = request.team.user channel.save() channel.assign_all_checks() @@ -323,11 +298,11 @@ def add_channel(request): @uuid_or_400 def channel_checks(request, code): channel = get_object_or_404(Channel, code=code) - if channel.user_id != request.user.id: + if channel.user_id != request.team.user.id: return HttpResponseForbidden() assigned = set(channel.checks.values_list('code', flat=True).distinct()) - checks = Check.objects.filter(user=request.user).order_by("created") + checks = Check.objects.filter(user=request.team.user).order_by("created") ctx = { "checks": checks, @@ -357,7 +332,7 @@ def remove_channel(request, code): # user may refresh the page during POST and cause two deletion attempts channel = Channel.objects.filter(code=code).first() if channel: - if channel.user != request.user: + if channel.user != request.team.user: return HttpResponseForbidden() channel.delete() @@ -375,7 +350,7 @@ def add_webhook(request): if request.method == "POST": form = AddWebhookForm(request.POST) if form.is_valid(): - channel = Channel(user=request.user, kind="webhook") + channel = Channel(user=request.team.user, kind="webhook") channel.value = form.get_value() channel.save() diff --git a/hc/payments/views.py b/hc/payments/views.py index d9f8a4b6..b80aaa2c 100644 --- a/hc/payments/views.py +++ b/hc/payments/views.py @@ -106,7 +106,7 @@ def create_plan(request): sub.save() # Update user's profile - profile = Profile.objects.for_user(request.user) + profile = request.user.profile if plan_id == "P5": profile.ping_log_limit = 1000 profile.team_access_allowed = True diff --git a/hc/settings.py b/hc/settings.py index feeec279..c4f5b928 100644 --- a/hc/settings.py +++ b/hc/settings.py @@ -49,6 +49,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', + 'hc.accounts.middleware.TeamAccessMiddleware', ) AUTHENTICATION_BACKENDS = ( diff --git a/hc/test.py b/hc/test.py index 9fdb1495..2b1ee979 100644 --- a/hc/test.py +++ b/hc/test.py @@ -1,11 +1,26 @@ from django.contrib.auth.models import User from django.test import TestCase +from hc.accounts.models import Profile + class BaseTestCase(TestCase): def setUp(self): super(BaseTestCase, self).setUp() + + # Normal user for tests self.alice = User(username="alice", email="alice@example.org") self.alice.set_password("password") self.alice.save() + + self.profile = Profile(user=self.alice, api_key="abc") + self.profile.save() + + # "malicious user for tests + self.charlie = User(username="charlie", email="charlie@example.org") + self.charlie.set_password("password") + self.charlie.save() + + charlies_profile = Profile(user=self.charlie) + charlies_profile.save() diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html index 194daaa1..a584fec2 100644 --- a/templates/accounts/profile.html +++ b/templates/accounts/profile.html @@ -131,6 +131,14 @@

{% endif %} +
+ + Set Team Name + + + {% endblock %} {% block scripts %} diff --git a/templates/base.html b/templates/base.html index 08d5b5ad..f6c5f1d0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -110,11 +110,26 @@