Browse Source

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

pull/551/head
Pēteris Caune 3 years ago
parent
commit
4f83f8c06b
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
5 changed files with 76 additions and 12 deletions
  1. +2
    -0
      CHANGELOG.md
  2. +41
    -8
      hc/accounts/tests/test_project.py
  3. +17
    -0
      hc/accounts/tests/test_transfer_project.py
  4. +15
    -3
      hc/accounts/views.py
  5. +1
    -1
      templates/accounts/project.html

+ 2
- 0
CHANGELOG.md 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


+ 41
- 8
hc/accounts/tests/test_project.py 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="[email protected]", 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="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
form = {"revoke_api_keys": "1"}
r = self.client.post(self.url, form)
r = self.client.post(self.url, {"revoke_api_keys": "1"})
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="[email protected]", 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="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
@ -160,7 +174,11 @@ class ProjectTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", 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="[email protected]", 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, "[email protected]") self.assertContains(r, "[email protected]")
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="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
form = {"show_api_keys": "1"}
r = self.client.post(self.url, form)
r = self.client.post(self.url, {"show_api_keys": "1"})
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="[email protected]", password="password")
r = self.client.post(self.url, {"show_api_keys": "1"})
self.assertEqual(r.status_code, 403)

+ 17
- 0
hc/accounts/tests/test_transfer_project.py 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="[email protected]", password="password") self.client.login(username="[email protected]", 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="[email protected]", 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")

+ 15
- 3
hc/accounts/views.py 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


+ 1
- 1
templates/accounts/project.html 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


Loading…
Cancel
Save