Browse Source

Read-only users cannot edit or remove channels.

pull/419/head
Pēteris Caune 4 years ago
parent
commit
39198c827a
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 92 additions and 30 deletions
  1. +13
    -0
      hc/front/tests/test_channels.py
  2. +8
    -0
      hc/front/tests/test_edit_webhook.py
  3. +14
    -11
      hc/front/tests/test_remove_channel.py
  4. +10
    -0
      hc/front/tests/test_update_channel.py
  5. +10
    -0
      hc/front/tests/test_update_channel_name.py
  6. +25
    -13
      hc/front/views.py
  7. +3
    -3
      static/css/channels.css
  8. +1
    -1
      static/js/channels.js
  9. +8
    -2
      templates/front/channels.html

+ 13
- 0
hc/front/tests/test_channels.py View File

@ -108,3 +108,16 @@ class ChannelsTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get(self.channels_url) r = self.client.get(self.channels_url)
self.assertContains(r, "broken-channels", status_code=200) self.assertContains(r, "broken-channels", status_code=200)
def test_it_hides_actions_from_readonly_users(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
Channel.objects.create(project=self.project, kind="webhook", value="{}")
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.channels_url)
self.assertNotContains(r, "Add Integration", status_code=200)
self.assertNotContains(r, "icon-delete")
self.assertNotContains(r, "edit_webhook")

+ 8
- 0
hc/front/tests/test_edit_webhook.py View File

@ -82,3 +82,11 @@ class EditWebhookTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url) r = self.client.get(self.url)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def test_it_requires_rw_access(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, {})
self.assertEqual(r.status_code, 403)

+ 14
- 11
hc/front/tests/test_remove_channel.py View File

@ -9,20 +9,18 @@ class RemoveChannelTestCase(BaseTestCase):
self.channel.value = "[email protected]" self.channel.value = "[email protected]"
self.channel.save() self.channel.save()
def test_it_works(self):
url = "/integrations/%s/remove/" % self.channel.code
self.url = "/integrations/%s/remove/" % self.channel.code
def test_it_works(self):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
r = self.client.post(self.url)
self.assertRedirects(r, self.channels_url) self.assertRedirects(r, self.channels_url)
assert Channel.objects.count() == 0 assert Channel.objects.count() == 0
def test_team_access_works(self): def test_team_access_works(self):
url = "/integrations/%s/remove/" % self.channel.code
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
self.client.post(url)
self.client.post(self.url)
assert Channel.objects.count() == 0 assert Channel.objects.count() == 0
def test_it_handles_bad_uuid(self): def test_it_handles_bad_uuid(self):
@ -33,10 +31,8 @@ class RemoveChannelTestCase(BaseTestCase):
self.assertEqual(r.status_code, 404) self.assertEqual(r.status_code, 404)
def test_it_checks_owner(self): def test_it_checks_owner(self):
url = "/integrations/%s/remove/" % self.channel.code
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
r = self.client.post(self.url)
self.assertEqual(r.status_code, 404) self.assertEqual(r.status_code, 404)
def test_it_handles_missing_uuid(self): def test_it_handles_missing_uuid(self):
@ -48,7 +44,14 @@ class RemoveChannelTestCase(BaseTestCase):
self.assertEqual(r.status_code, 404) self.assertEqual(r.status_code, 404)
def test_it_rejects_get(self): def test_it_rejects_get(self):
url = "/integrations/%s/remove/" % self.channel.code
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
r = self.client.get(self.url)
self.assertEqual(r.status_code, 405) self.assertEqual(r.status_code, 405)
def test_it_requires_rw_access(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url)
self.assertEqual(r.status_code, 403)

+ 10
- 0
hc/front/tests/test_update_channel.py View File

@ -70,3 +70,13 @@ class UpdateChannelTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(self.channels_url, data=payload) r = self.client.post(self.channels_url, data=payload)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def test_it_requires_rw_access(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
payload = {"channel": self.channel.code}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.channels_url, data=payload)
self.assertEqual(r.status_code, 403)

+ 10
- 0
hc/front/tests/test_update_channel_name.py View File

@ -51,3 +51,13 @@ class UpdateChannelNameTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url) r = self.client.get(self.url)
self.assertEqual(r.status_code, 405) self.assertEqual(r.status_code, 405)
def test_it_requires_rw_access(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
payload = {"name": "My work email"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data=payload)
self.assertEqual(r.status_code, 403)

+ 25
- 13
hc/front/views.py View File

@ -110,15 +110,23 @@ def _get_channel_for_user(request, code):
assert request.user.is_authenticated assert request.user.is_authenticated
q = Channel.objects
if not request.user.is_superuser:
project_ids = request.profile.projects().values("id")
q = q.filter(project_id__in=project_ids)
channel = get_object_or_404(Channel.objects.select_related("project"), code=code)
if request.user.is_superuser:
return channel, True
try:
return q.get(code=code)
except Channel.DoesNotExist:
raise Http404("not found")
if request.user.id == channel.project.owner_id:
return channel, True
membership = get_object_or_404(Member, project=channel.project, user=request.user)
return channel, membership.rw
def _get_rw_channel_for_user(request, code):
channel, rw = _get_channel_for_user(request, code)
if not rw:
raise PermissionDenied
return channel
def _get_project_for_user(request, project_code): def _get_project_for_user(request, project_code):
@ -693,6 +701,9 @@ def channels(request, code):
project, rw = _get_project_for_user(request, code) project, rw = _get_project_for_user(request, code)
if request.method == "POST": if request.method == "POST":
if not rw:
return HttpResponseForbidden()
code = request.POST["channel"] code = request.POST["channel"]
try: try:
channel = Channel.objects.get(code=code) channel = Channel.objects.get(code=code)
@ -722,6 +733,7 @@ def channels(request, code):
ctx = { ctx = {
"page": "channels", "page": "channels",
"rw": rw,
"project": project, "project": project,
"profile": project.owner_profile, "profile": project.owner_profile,
"channels": channels, "channels": channels,
@ -746,7 +758,7 @@ def channels(request, code):
@login_required @login_required
def channel_checks(request, code): def channel_checks(request, code):
channel = _get_channel_for_user(request, code)
channel = _get_rw_channel_for_user(request, code)
assigned = set(channel.checks.values_list("code", flat=True).distinct()) assigned = set(channel.checks.values_list("code", flat=True).distinct())
checks = Check.objects.filter(project=channel.project).order_by("created") checks = Check.objects.filter(project=channel.project).order_by("created")
@ -759,7 +771,7 @@ def channel_checks(request, code):
@require_POST @require_POST
@login_required @login_required
def update_channel_name(request, code): def update_channel_name(request, code):
channel = _get_channel_for_user(request, code)
channel = _get_rw_channel_for_user(request, code)
form = forms.ChannelNameForm(request.POST) form = forms.ChannelNameForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -817,7 +829,7 @@ def unsubscribe_email(request, code, signed_token):
@require_POST @require_POST
@login_required @login_required
def send_test_notification(request, code): def send_test_notification(request, code):
channel = _get_channel_for_user(request, code)
channel, rw = _get_channel_for_user(request, code)
dummy = Check(name="TEST", status="down") dummy = Check(name="TEST", status="down")
dummy.last_ping = timezone.now() - td(days=1) dummy.last_ping = timezone.now() - td(days=1)
@ -846,7 +858,7 @@ def send_test_notification(request, code):
@require_POST @require_POST
@login_required @login_required
def remove_channel(request, code): def remove_channel(request, code):
channel = _get_channel_for_user(request, code)
channel = _get_rw_channel_for_user(request, code)
project = channel.project project = channel.project
channel.delete() channel.delete()
@ -926,7 +938,7 @@ def add_webhook(request, code):
@login_required @login_required
def edit_webhook(request, code): def edit_webhook(request, code):
channel = _get_channel_for_user(request, code)
channel = _get_rw_channel_for_user(request, code)
if channel.kind != "webhook": if channel.kind != "webhook":
return HttpResponseBadRequest() return HttpResponseBadRequest()


+ 3
- 3
static/css/channels.css View File

@ -54,7 +54,6 @@ table.channels-table > tbody > tr > th {
.edit-name, .edit-checks { .edit-name, .edit-checks {
padding: 12px 6px; padding: 12px 6px;
border: 1px solid transparent; border: 1px solid transparent;
cursor: pointer;
} }
.edit-name .channel-details-mini { .edit-name .channel-details-mini {
@ -70,9 +69,10 @@ table.channels-table > tbody > tr > th {
color: #111; color: #111;
} }
.channel-row:hover .edit-name,
.channel-row:hover .edit-checks {
.rw .channel-row:hover .edit-name,
.rw .channel-row:hover .edit-checks {
border: 1px dotted #AAA; border: 1px dotted #AAA;
cursor: pointer;
} }
.edit-checks { .edit-checks {


+ 1
- 1
static/js/channels.js View File

@ -1,6 +1,6 @@
$(function() { $(function() {
$(".edit-checks").click(function() {
$(".rw .edit-checks").click(function() {
$("#checks-modal").modal("show"); $("#checks-modal").modal("show");
$.ajax(this.dataset.url).done(function(data) { $.ajax(this.dataset.url).done(function(data) {
$("#checks-modal .modal-content").html(data); $("#checks-modal .modal-content").html(data);


+ 8
- 2
templates/front/channels.html View File

@ -16,7 +16,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
{% if channels %} {% if channels %}
<table class="table channels-table">
<table class="table channels-table {% if rw %}rw{% endif %}">
<tr> <tr>
<th></th> <th></th>
<th class="th-name">Name, Details</th> <th class="th-name">Name, Details</th>
@ -132,7 +132,7 @@
{% endif %} {% endif %}
</td> </td>
<td class="actions"> <td class="actions">
{% if ch.kind == "webhook" %}
{% if ch.kind == "webhook" and rw %}
<a class="btn btn-sm btn-default" href="{% url 'hc-edit-webhook' ch.code %}">Edit</a> <a class="btn btn-sm btn-default" href="{% url 'hc-edit-webhook' ch.code %}">Edit</a>
{% endif %} {% endif %}
<form action="{% url 'hc-channel-test' ch.code %}" method="post"> <form action="{% url 'hc-channel-test' ch.code %}" method="post">
@ -145,6 +145,7 @@
Test! Test!
</button> </button>
</form> </form>
{% if rw %}
<button <button
data-kind="{{ ch.get_kind_display }}" data-kind="{{ ch.get_kind_display }}"
data-url="{% url 'hc-remove-channel' ch.code %}" data-url="{% url 'hc-remove-channel' ch.code %}"
@ -152,6 +153,7 @@
type="button"> type="button">
<span class="icon-delete"></span> <span class="icon-delete"></span>
</button> </button>
{% endif %}
</td> </td>
<td> <td>
@ -173,6 +175,7 @@
{% endif %} {% endif %}
{% if rw %}
<h1 class="ai-title">Add More</h1> <h1 class="ai-title">Add More</h1>
<ul class="add-integration"> <ul class="add-integration">
<li> <li>
@ -431,6 +434,7 @@
</li> </li>
</ul> </ul>
{% endif %}
</div> </div>
</div> </div>
@ -472,6 +476,7 @@
</div> </div>
</div> </div>
{% if rw %}
{% for ch in channels %} {% for ch in channels %}
<div id="name-{{ ch.code }}" class="modal channel-modal"> <div id="name-{{ ch.code }}" class="modal channel-modal">
<div class="modal-dialog"> <div class="modal-dialog">
@ -566,6 +571,7 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% endif %}
{% endblock %} {% endblock %}


Loading…
Cancel
Save