Fix a 403 when transferring a project to a read-only team member

This commit is contained in:
Pēteris Caune 2021-07-26 12:50:43 +03:00
parent 9640d2242f
commit 4f83f8c06b
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
5 changed files with 76 additions and 12 deletions

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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