Browse Source

Allow simultaneous access to checks from different teams

pull/211/head
Pēteris Caune 6 years ago
parent
commit
c2f200fa02
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
12 changed files with 143 additions and 68 deletions
  1. +2
    -1
      CHANGELOG.md
  2. +9
    -1
      hc/front/tests/test_details.py
  3. +10
    -1
      hc/front/tests/test_log.py
  4. +10
    -0
      hc/front/tests/test_pause.py
  5. +14
    -1
      hc/front/tests/test_ping_details.py
  6. +20
    -18
      hc/front/tests/test_remove_check.py
  7. +8
    -0
      hc/front/tests/test_status_single.py
  8. +10
    -2
      hc/front/tests/test_switch_channel.py
  9. +12
    -4
      hc/front/tests/test_update_name.py
  10. +12
    -1
      hc/front/tests/test_update_timeout.py
  11. +30
    -38
      hc/front/views.py
  12. +6
    -1
      templates/base.html

+ 2
- 1
CHANGELOG.md View File

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- Set Pushover alert priorities for "down" and "up" events separately
- Additional python usage examples
- Allow simultaneous access to checks from different teams
### Bug Fixes
- Fix after-login redirects (the "?next=" query parameter)
@ -52,4 +53,4 @@ All notable changes to this project will be documented in this file.
- A new "Check Details" page.
- Updated django-compressor, psycopg2, pytz, requests package versions.
- C# usage example.
- Checks have a "Description" field.
- Checks have a "Description" field.

+ 9
- 1
hc/front/tests/test_details.py View File

@ -29,7 +29,7 @@ class DetailsTestCase(BaseTestCase):
def test_it_checks_ownership(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
assert r.status_code == 403
self.assertEqual(r.status_code, 404)
def test_it_shows_cron_expression(self):
self.check.kind = "cron"
@ -38,3 +38,11 @@ class DetailsTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Cron Expression", status_code=200)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)

+ 10
- 1
hc/front/tests/test_log.py View File

@ -52,7 +52,7 @@ class LogTestCase(BaseTestCase):
url = "/checks/%s/log/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
self.assertEqual(r.status_code, 404)
def test_it_shows_pushover_notifications(self):
ch = Channel(kind="po", user=self.alice)
@ -77,3 +77,12 @@ class LogTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertContains(r, "Called webhook foo/$NAME", status_code=200)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
url = "/checks/%s/log/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)

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

@ -24,3 +24,13 @@ class PauseTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 405)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
url = "/checks/%s/pause/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
self.assertRedirects(r, "/checks/")

+ 14
- 1
hc/front/tests/test_ping_details.py View File

@ -17,7 +17,7 @@ class LastPingTestCase(BaseTestCase):
def test_it_requires_user(self):
check = Check.objects.create()
r = self.client.get("/checks/%s/last_ping/" % check.code)
self.assertEqual(r.status_code, 403)
self.assertEqual(r.status_code, 404)
def test_it_accepts_n(self):
check = Check(user=self.alice)
@ -34,3 +34,16 @@ class LastPingTestCase(BaseTestCase):
r = self.client.get("/checks/%s/pings/2/" % check.code)
self.assertContains(r, "bar-456", status_code=200)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
check = Check(user=self.alice)
check.save()
Ping.objects.create(owner=check, body="this is body")
self.client.login(username="[email protected]", password="password")
r = self.client.get("/checks/%s/last_ping/" % check.code)
self.assertEqual(r.status_code, 200)

+ 20
- 18
hc/front/tests/test_remove_check.py View File

@ -9,37 +9,32 @@ class RemoveCheckTestCase(BaseTestCase):
self.check = Check(user=self.alice)
self.check.save()
def test_it_works(self):
url = "/checks/%s/remove/" % self.check.code
self.remove_url = "/checks/%s/remove/" % self.check.code
def test_it_works(self):
self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
r = self.client.post(self.remove_url)
self.assertRedirects(r, "/checks/")
assert Check.objects.count() == 0
self.assertEqual(Check.objects.count(), 0)
def test_team_access_works(self):
url = "/checks/%s/remove/" % self.check.code
# Logging in as bob, not alice. Bob has team access so this
# should work.
self.client.login(username="[email protected]", password="password")
self.client.post(url)
assert Check.objects.count() == 0
self.client.post(self.remove_url)
def test_it_handles_bad_uuid(self):
url = "/checks/not-uuid/remove/"
self.assertEqual(Check.objects.count(), 0)
def test_it_handles_bad_uuid(self):
self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
r = self.client.post("/checks/not-uuid/remove/")
self.assertEqual(r.status_code, 404)
def test_it_checks_owner(self):
url = "/checks/%s/remove/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
assert r.status_code == 403
r = self.client.post(self.remove_url)
self.assertEqual(r.status_code, 404)
def test_it_handles_missing_uuid(self):
# Valid UUID but there is no check for it:
@ -47,10 +42,17 @@ class RemoveCheckTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.post(url)
assert r.status_code == 404
self.assertEqual(r.status_code, 404)
def test_it_rejects_get(self):
url = "/checks/%s/remove/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
r = self.client.get(self.remove_url)
self.assertEqual(r.status_code, 405)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.remove_url)
self.assertRedirects(r, "/checks/")

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

@ -46,3 +46,11 @@ class StatusSingleTestCase(BaseTestCase):
doc = r.json()
self.assertFalse("events" in doc)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
self.client.login(username="[email protected]", password="password")
r = self.client.get("/checks/%s/status/" % self.check.code)
self.assertEqual(r.status_code, 200)

+ 10
- 2
hc/front/tests/test_switch_channel.py View File

@ -32,7 +32,7 @@ class SwitchChannelTestCase(BaseTestCase):
def test_it_checks_ownership(self):
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, {"state": "on"})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.status_code, 404)
def test_it_checks_channels_ownership(self):
cc = Check(user=self.charlie)
@ -43,4 +43,12 @@ class SwitchChannelTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, {"state": "on"})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.status_code, 400)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, {"state": "on"})
self.assertEqual(r.status_code, 200)

+ 12
- 4
hc/front/tests/test_update_name.py View File

@ -12,10 +12,8 @@ class UpdateNameTestCase(BaseTestCase):
self.url = "/checks/%s/name/" % self.check.code
def test_it_works(self):
payload = {"name": "Alice Was Here"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data=payload)
r = self.client.post(self.url, data={"name": "Alice Was Here"})
self.assertRedirects(r, "/checks/")
self.check.refresh_from_db()
@ -32,12 +30,22 @@ class UpdateNameTestCase(BaseTestCase):
self.check.refresh_from_db()
self.assertEqual(self.check.name, "Bob Was Here")
def test_it_allows_cross_team_access(self):
# Bob's current team is not set
self.bobs_profile.current_team = None
self.bobs_profile.save()
# But this should still work:
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data={"name": "Bob Was Here"})
self.assertRedirects(r, "/checks/")
def test_it_checks_ownership(self):
payload = {"name": "Charlie Sent This"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data=payload)
self.assertEqual(r.status_code, 403)
self.assertEqual(r.status_code, 404)
def test_it_handles_bad_uuid(self):
url = "/checks/not-uuid/name/"


+ 12
- 1
hc/front/tests/test_update_timeout.py View File

@ -145,10 +145,21 @@ class UpdateTimeoutTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 403
self.assertEqual(r.status_code, 404)
def test_it_rejects_get(self):
url = "/checks/%s/timeout/" % self.check.code
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 405)
def test_it_allows_cross_team_access(self):
self.bobs_profile.current_team = None
self.bobs_profile.save()
url = "/checks/%s/timeout/" % self.check.code
payload = {"kind": "simple", "timeout": 3600, "grace": 60}
self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload)
self.assertRedirects(r, "/checks/")

+ 30
- 38
hc/front/views.py View File

@ -60,6 +60,23 @@ def _tags_statuses(checks):
return tags, num_down
def _get_check_for_user(request, code):
""" Return specified check if current user has access to it. """
if not request.user.is_authenticated:
raise Http404("not found")
if request.user.is_superuser:
q = Check.objects
else:
q = request.profile.checks_from_all_teams()
try:
return q.get(code=code)
except Check.DoesNotExist:
raise Http404("not found")
@login_required
def my_checks(request):
if request.GET.get("sort") in VALID_SORT_VALUES:
@ -136,13 +153,11 @@ def status(request):
@login_required
@require_POST
def switch_channel(request, code, channel_code):
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
channel = get_object_or_404(Channel, code=channel_code)
if channel.user_id != request.team.user.id:
return HttpResponseForbidden()
if channel.user_id != check.user_id:
return HttpResponseBadRequest()
if request.POST.get("state") == "on":
channel.checks.add(check)
@ -228,10 +243,7 @@ def add_check(request):
@require_POST
@login_required
def update_name(request, code):
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
form = NameTagsForm(request.POST)
if form.is_valid():
check.name = form.cleaned_data["name"]
@ -248,9 +260,7 @@ def update_name(request, code):
@require_POST
@login_required
def update_timeout(request, code):
check = get_object_or_404(Check, code=code)
if check.user != request.team.user:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
kind = request.POST.get("kind")
if kind == "simple":
@ -304,13 +314,7 @@ def cron_preview(request):
def ping_details(request, code, n=None):
if not request.user.is_authenticated:
return HttpResponseForbidden()
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
q = Ping.objects.filter(owner=check)
if n:
q = q.filter(n=n)
@ -328,9 +332,7 @@ def ping_details(request, code, n=None):
@require_POST
@login_required
def pause(request, code):
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
check.status = "paused"
check.save()
@ -344,12 +346,8 @@ def pause(request, code):
@require_POST
@login_required
def remove_check(request, code):
check = get_object_or_404(Check, code=code)
if check.user != request.team.user:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
check.delete()
return redirect("hc-checks")
@ -371,11 +369,9 @@ def _get_events(check, limit):
@login_required
def log(request, code):
check = get_object_or_404(Check, code=code)
if check.user != request.team.user:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
limit = request.team.ping_log_limit
limit = check.user.profile.ping_log_limit
ctx = {
"check": check,
"events": _get_events(check, limit),
@ -388,11 +384,9 @@ def log(request, code):
@login_required
def details(request, code):
check = get_object_or_404(Check, code=code)
if check.user != request.team.user:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
channels = Channel.objects.filter(user=request.team.user)
channels = Channel.objects.filter(user=check.user)
channels = list(channels.order_by("created"))
ctx = {
@ -407,9 +401,7 @@ def details(request, code):
@login_required
def status_single(request, code):
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check = _get_check_for_user(request, code)
status = check.get_status()
events = _get_events(check, 20)


+ 6
- 1
templates/base.html View File

@ -115,7 +115,12 @@
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="nav-email" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">
{{ request.team }} <span class="caret"></span>
{% if check %}
{{ check.user.profile }}
{% else %}
{{ request.team }}
{% endif %}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{% with teams=request.get_teams %}


Loading…
Cancel
Save