forked from GithubBackups/healthchecks
Fix a 403 when transferring a project to a read-only team member
This commit is contained in:
parent
9640d2242f
commit
4f83f8c06b
@ -7,9 +7,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Use multicolor channel icons for better appearance in the dark mode
|
- Use multicolor channel icons for better appearance in the dark mode
|
||||||
- Add SITE_LOGO_URL setting (#323)
|
- Add SITE_LOGO_URL setting (#323)
|
||||||
- Add admin action to log in as any user
|
- Add admin action to log in as any user
|
||||||
|
- Add a "Manager" role (#484)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Fix dark mode styling issues in Cron Syntax Cheatsheet
|
- Fix dark mode styling issues in Cron Syntax Cheatsheet
|
||||||
|
- Fix a 403 when transferring a project to a read-only team member
|
||||||
|
|
||||||
## v1.21.0 - 2020-07-02
|
## v1.21.0 - 2020-07-02
|
||||||
|
|
||||||
|
@ -49,20 +49,34 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
self.assertTrue(len(api_key) > 10)
|
self.assertTrue(len(api_key) > 10)
|
||||||
self.assertFalse("b'" in api_key)
|
self.assertFalse("b'" in api_key)
|
||||||
|
|
||||||
|
def test_it_requires_rw_access_to_create_api_key(self):
|
||||||
|
self.bobs_membership.role = "r"
|
||||||
|
self.bobs_membership.save()
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"create_api_keys": "1"})
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
def test_it_revokes_api_key(self):
|
def test_it_revokes_api_key(self):
|
||||||
self.project.api_key_readonly = "R" * 32
|
self.project.api_key_readonly = "R" * 32
|
||||||
self.project.save()
|
self.project.save()
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"revoke_api_keys": "1"})
|
||||||
form = {"revoke_api_keys": "1"}
|
|
||||||
r = self.client.post(self.url, form)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
self.project.refresh_from_db()
|
self.project.refresh_from_db()
|
||||||
self.assertEqual(self.project.api_key, "")
|
self.assertEqual(self.project.api_key, "")
|
||||||
self.assertEqual(self.project.api_key_readonly, "")
|
self.assertEqual(self.project.api_key_readonly, "")
|
||||||
|
|
||||||
|
def test_it_requires_rw_access_to_revoke_api_key(self):
|
||||||
|
self.bobs_membership.role = "r"
|
||||||
|
self.bobs_membership.save()
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"revoke_api_keys": "1"})
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
def test_it_adds_team_member(self):
|
def test_it_adds_team_member(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
@ -160,7 +174,11 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
aaa = "a" * 300
|
aaa = "a" * 300
|
||||||
form = {"invite_team_member": "1", "email": f"frank+{aaa}@example.org", "role": "r"}
|
form = {
|
||||||
|
"invite_team_member": "1",
|
||||||
|
"email": f"frank+{aaa}@example.org",
|
||||||
|
"role": "r",
|
||||||
|
}
|
||||||
r = self.client.post(self.url, form)
|
r = self.client.post(self.url, form)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
@ -245,6 +263,15 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
self.project.refresh_from_db()
|
self.project.refresh_from_db()
|
||||||
self.assertEqual(self.project.name, "Alpha Team")
|
self.assertEqual(self.project.name, "Alpha Team")
|
||||||
|
|
||||||
|
def test_it_requires_rw_access_to_set_project_name(self):
|
||||||
|
self.bobs_membership.role = "r"
|
||||||
|
self.bobs_membership.save()
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
form = {"set_project_name": "1", "name": "Alpha Team"}
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
def test_it_shows_invite_suggestions(self):
|
def test_it_shows_invite_suggestions(self):
|
||||||
p2 = Project.objects.create(owner=self.alice)
|
p2 = Project.objects.create(owner=self.alice)
|
||||||
|
|
||||||
@ -254,7 +281,7 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
self.assertContains(r, "Add Users from Other Teams")
|
self.assertContains(r, "Add Users from Other Teams")
|
||||||
self.assertContains(r, "bob@example.org")
|
self.assertContains(r, "bob@example.org")
|
||||||
|
|
||||||
def test_it_checks_rw_access_when_updating_project_name(self):
|
def test_it_requires_rw_access_to_update_project_name(self):
|
||||||
self.bobs_membership.role = "r"
|
self.bobs_membership.role = "r"
|
||||||
self.bobs_membership.save()
|
self.bobs_membership.save()
|
||||||
|
|
||||||
@ -280,9 +307,15 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
self.project.save()
|
self.project.save()
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"show_api_keys": "1"})
|
||||||
form = {"show_api_keys": "1"}
|
|
||||||
r = self.client.post(self.url, form)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
self.assertNotContains(r, "Prometheus metrics endpoint")
|
self.assertNotContains(r, "Prometheus metrics endpoint")
|
||||||
|
|
||||||
|
def test_it_requires_rw_access_to_show_api_key(self):
|
||||||
|
self.bobs_membership.role = "r"
|
||||||
|
self.bobs_membership.save()
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"show_api_keys": "1"})
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from hc.accounts.models import Member
|
||||||
from hc.api.models import Check
|
from hc.api.models import Check
|
||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
@ -149,3 +150,19 @@ class TransferProjectTestCase(BaseTestCase):
|
|||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.post(self.url, {"reject_transfer": "1"})
|
r = self.client.post(self.url, {"reject_transfer": "1"})
|
||||||
self.assertEqual(r.status_code, 403)
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
def test_readonly_user_can_accept(self):
|
||||||
|
self.bobs_membership.transfer_request_date = now()
|
||||||
|
self.bobs_membership.role = "r"
|
||||||
|
self.bobs_membership.save()
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
self.client.post(self.url, {"accept_transfer": "1"})
|
||||||
|
|
||||||
|
self.project.refresh_from_db()
|
||||||
|
# Bob should now be the owner
|
||||||
|
self.assertEqual(self.project.owner, self.bob)
|
||||||
|
|
||||||
|
# Alice, the previous owner, should now be a *regular* member
|
||||||
|
m = Member.objects.get(user=self.alice, project=self.project)
|
||||||
|
self.assertEqual(m.role, "w")
|
||||||
|
@ -302,10 +302,10 @@ def project(request, code):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if not rw:
|
|
||||||
return HttpResponseForbidden()
|
|
||||||
|
|
||||||
if "create_api_keys" in request.POST:
|
if "create_api_keys" in request.POST:
|
||||||
|
if not rw:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
project.set_api_keys()
|
project.set_api_keys()
|
||||||
project.save()
|
project.save()
|
||||||
|
|
||||||
@ -313,6 +313,9 @@ def project(request, code):
|
|||||||
ctx["api_keys_created"] = True
|
ctx["api_keys_created"] = True
|
||||||
ctx["api_status"] = "success"
|
ctx["api_status"] = "success"
|
||||||
elif "revoke_api_keys" in request.POST:
|
elif "revoke_api_keys" in request.POST:
|
||||||
|
if not rw:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
project.api_key = ""
|
project.api_key = ""
|
||||||
project.api_key_readonly = ""
|
project.api_key_readonly = ""
|
||||||
project.save()
|
project.save()
|
||||||
@ -320,6 +323,9 @@ def project(request, code):
|
|||||||
ctx["api_keys_revoked"] = True
|
ctx["api_keys_revoked"] = True
|
||||||
ctx["api_status"] = "info"
|
ctx["api_status"] = "info"
|
||||||
elif "show_api_keys" in request.POST:
|
elif "show_api_keys" in request.POST:
|
||||||
|
if not rw:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
ctx["show_api_keys"] = True
|
ctx["show_api_keys"] = True
|
||||||
elif "invite_team_member" in request.POST:
|
elif "invite_team_member" in request.POST:
|
||||||
if not is_manager:
|
if not is_manager:
|
||||||
@ -372,6 +378,9 @@ def project(request, code):
|
|||||||
ctx["team_member_removed"] = form.cleaned_data["email"]
|
ctx["team_member_removed"] = form.cleaned_data["email"]
|
||||||
ctx["team_status"] = "info"
|
ctx["team_status"] = "info"
|
||||||
elif "set_project_name" in request.POST:
|
elif "set_project_name" in request.POST:
|
||||||
|
if not rw:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
form = forms.ProjectNameForm(request.POST)
|
form = forms.ProjectNameForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
project.name = form.cleaned_data["name"]
|
project.name = form.cleaned_data["name"]
|
||||||
@ -427,6 +436,9 @@ def project(request, code):
|
|||||||
# 1. Reuse the existing membership, and change its user
|
# 1. Reuse the existing membership, and change its user
|
||||||
tr.user = project.owner
|
tr.user = project.owner
|
||||||
tr.transfer_request_date = None
|
tr.transfer_request_date = None
|
||||||
|
# The previous owner becomes a regular member
|
||||||
|
# (not readonly, not manager):
|
||||||
|
tr.role = Member.Role.REGULAR
|
||||||
tr.save()
|
tr.save()
|
||||||
|
|
||||||
# 2. Change project's owner
|
# 2. Change project's owner
|
||||||
|
@ -167,7 +167,7 @@
|
|||||||
{% for m in project.member_set.all %}
|
{% for m in project.member_set.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="email">{{ m.user.email }}</td>
|
<td class="email">{{ m.user.email }}</td>
|
||||||
<td>{{ m.get_role_display}}</td>
|
<td>{{ m.get_role_display }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if is_manager and m.user != request.user %}
|
{% if is_manager and m.user != request.user %}
|
||||||
<a
|
<a
|
||||||
|
Loading…
x
Reference in New Issue
Block a user