0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-11 15:51:19 +00:00

Bugfix: don't allow duplicate team memberships

This commit is contained in:
Pēteris Caune 2020-08-19 12:07:48 +03:00
parent 9a1127005e
commit 2346ac3e80
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
6 changed files with 66 additions and 5 deletions

View file

@ -11,8 +11,9 @@ All notable changes to this project will be documented in this file.
- Host a read-only dashboard (from github.com/healthchecks/dashboard/)
## Bug Fixes
- Handle excessively long email addresses in the signup form.
- Handle excessively long email addresses in the team member invite form.
- Handle excessively long email addresses in the signup form
- Handle excessively long email addresses in the team member invite form
- Don't allow duplicate team memberships
## v1.16.0 - 2020-08-04

View file

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-08-19 07:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0031_auto_20200803_1413'),
]
operations = [
migrations.AddConstraint(
model_name='member',
constraint=models.UniqueConstraint(fields=('user', 'project'), name='accounts_member_no_duplicates'),
),
]

View file

@ -319,9 +319,16 @@ class Project(models.Model):
return used < self.owner_profile.team_limit
def invite(self, user):
if Member.objects.filter(user=user, project=self).exists():
return False
if self.owner_id == user.id:
return False
Member.objects.create(user=user, project=self)
checks_url = reverse("hc-checks", args=[self.code])
user.profile.send_instant_login_link(self, redirect_url=checks_url)
return True
def set_next_nag_date(self):
""" Set next_nag_date on profiles of all members of this project. """
@ -373,5 +380,12 @@ class Member(models.Model):
project = models.ForeignKey(Project, models.CASCADE)
transfer_request_date = models.DateTimeField(null=True, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "project"], name="accounts_member_no_duplicates"
)
]
def can_accept(self):
return self.user.profile.can_accept(self.project)

View file

@ -108,6 +108,26 @@ class ProjectTestCase(BaseTestCase):
q = TokenBucket.objects.filter(value="invite-%d" % self.alice.id)
self.assertFalse(q.exists())
def test_it_rejects_duplicate_membership(self):
self.client.login(username="alice@example.org", password="password")
form = {"invite_team_member": "1", "email": "bob@example.org"}
r = self.client.post(self.url, form)
self.assertContains(r, "bob@example.org is already a member")
# The number of memberships should have not increased
self.assertEqual(self.project.member_set.count(), 1)
def test_it_rejects_owner_as_a_member(self):
self.client.login(username="alice@example.org", password="password")
form = {"invite_team_member": "1", "email": "alice@example.org"}
r = self.client.post(self.url, form)
self.assertContains(r, "alice@example.org is already a member")
# The number of memberships should have not increased
self.assertEqual(self.project.member_set.count(), 1)
def test_it_rejects_too_long_email_addresses(self):
self.client.login(username="alice@example.org", password="password")

View file

@ -304,9 +304,12 @@ def project(request, code):
except User.DoesNotExist:
user = _make_user(email, with_project=False)
project.invite(user)
ctx["team_member_invited"] = email
ctx["team_status"] = "success"
if project.invite(user):
ctx["team_member_invited"] = email
ctx["team_status"] = "success"
else:
ctx["team_member_duplicate"] = email
ctx["team_status"] = "info"
elif "remove_team_member" in request.POST:
if not is_owner:

View file

@ -228,6 +228,12 @@
</div>
{% endif %}
{% if team_member_duplicate %}
<div class="panel-footer">
{{ team_member_duplicate }} is already a member
</div>
{% endif %}
{% if team_member_removed %}
<div class="panel-footer">
{{ team_member_removed }} removed from team