Browse Source

Project code in URL for the "Add Slack" page. cc: #336

pull/340/head
Pēteris Caune 5 years ago
parent
commit
acce0808ce
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
12 changed files with 279 additions and 212 deletions
  1. +2
    -2
      hc/accounts/tests/test_check_token.py
  2. +3
    -3
      hc/accounts/tests/test_login.py
  3. +1
    -0
      hc/accounts/views.py
  4. +8
    -9
      hc/front/tests/test_add_slack.py
  5. +10
    -72
      hc/front/tests/test_add_slack_btn.py
  6. +69
    -0
      hc/front/tests/test_add_slack_complete.py
  7. +9
    -0
      hc/front/tests/test_add_slack_help.py
  8. +4
    -2
      hc/front/urls.py
  9. +70
    -32
      hc/front/views.py
  10. +5
    -1
      templates/front/channels.html
  11. +1
    -91
      templates/integrations/add_slack.html
  12. +97
    -0
      templates/integrations/add_slack_btn.html

+ 2
- 2
hc/accounts/tests/test_check_token.py View File

@ -40,9 +40,9 @@ class CheckTokenTestCase(BaseTestCase):
self.assertContains(r, "incorrect or expired")
def test_it_handles_next_parameter(self):
url = "/accounts/check_token/alice/secret-token/?next=/integrations/add_slack/"
url = "/accounts/check_token/alice/secret-token/?next=" + self.channels_url
r = self.client.post(url)
self.assertRedirects(r, "/integrations/add_slack/")
self.assertRedirects(r, self.channels_url)
def test_it_ignores_bad_next_parameter(self):
url = "/accounts/check_token/alice/secret-token/?next=/evil/"


+ 3
- 3
hc/accounts/tests/test_login.py View File

@ -24,14 +24,14 @@ class LoginTestCase(BaseTestCase):
def test_it_sends_link_with_next(self):
form = {"identity": "[email protected]"}
r = self.client.post("/accounts/login/?next=/integrations/add_slack/", form)
r = self.client.post("/accounts/login/?next=" + self.channels_url, form)
self.assertRedirects(r, "/accounts/login_link_sent/")
self.assertIn("auto-login", r.cookies)
# The check_token link should have a ?next= query parameter:
self.assertEqual(len(mail.outbox), 1)
body = mail.outbox[0].body
self.assertTrue("/?next=/integrations/add_slack/" in body)
self.assertTrue("/?next=" + self.channels_url in body)
@override_settings(SECRET_KEY="test-secret")
def test_it_rate_limits_emails(self):
@ -85,7 +85,7 @@ class LoginTestCase(BaseTestCase):
form = {"action": "login", "email": "[email protected]", "password": "password"}
samples = ["/integrations/add_slack/", "/checks/%s/details/" % check.code]
samples = [self.channels_url, "/checks/%s/details/" % check.code]
for s in samples:
r = self.client.post("/accounts/login/?next=%s" % s, form)


+ 1
- 0
hc/accounts/views.py View File

@ -40,6 +40,7 @@ NEXT_WHITELIST = (
"hc-details",
"hc-log",
"hc-channels",
"hc-p-channels",
"hc-add-slack",
"hc-add-pushover",
)


+ 8
- 9
hc/front/tests/test_add_slack.py View File

@ -1,33 +1,32 @@
from django.test.utils import override_settings
from hc.api.models import Channel
from hc.test import BaseTestCase
class AddSlackTestCase(BaseTestCase):
@override_settings(SLACK_CLIENT_ID=None)
def setUp(self):
super(AddSlackTestCase, self).setUp()
self.url = "/projects/%s/add_slack/" % self.project.code
def test_instructions_work(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/add_slack/")
r = self.client.get(self.url)
self.assertContains(r, "Integration Settings", status_code=200)
@override_settings(SLACK_CLIENT_ID=None)
def test_it_works(self):
form = {"value": "http://example.org"}
self.client.login(username="[email protected]", password="password")
r = self.client.post("/integrations/add_slack/", form)
self.assertRedirects(r, "/integrations/")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
c = Channel.objects.get()
self.assertEqual(c.kind, "slack")
self.assertEqual(c.value, "http://example.org")
self.assertEqual(c.project, self.project)
@override_settings(SLACK_CLIENT_ID=None)
def test_it_rejects_bad_url(self):
form = {"value": "not an URL"}
self.client.login(username="[email protected]", password="password")
r = self.client.post("/integrations/add_slack/", form)
r = self.client.post(self.url, form)
self.assertContains(r, "Enter a valid URL")

+ 10
- 72
hc/front/tests/test_add_slack_btn.py View File

@ -1,84 +1,22 @@
import json
from django.test.utils import override_settings
from hc.api.models import Channel
from hc.test import BaseTestCase
from mock import patch
@override_settings(SLACK_CLIENT_ID="fake-client-id")
class AddSlackBtnTestCase(BaseTestCase):
@override_settings(SLACK_CLIENT_ID="foo")
def test_it_prepares_login_link(self):
r = self.client.get("/integrations/add_slack/")
self.assertContains(r, "Before adding Slack integration", status_code=200)
def setUp(self):
super(AddSlackBtnTestCase, self).setUp()
self.url = "/projects/%s/add_slack_btn/" % self.project.code
self.assertContains(r, "?next=/integrations/add_slack/")
def test_instructions_work(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Setup Guide", status_code=200)
@override_settings(SLACK_CLIENT_ID="foo")
def test_slack_button(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/add_slack/")
r = self.client.get(self.url)
self.assertContains(r, "slack.com/oauth/authorize", status_code=200)
# There should now be a key in session
self.assertTrue("slack" in self.client.session)
@patch("hc.front.views.requests.post")
def test_it_handles_oauth_response(self, mock_post):
session = self.client.session
session["slack"] = "foo"
session.save()
oauth_response = {
"ok": True,
"team_name": "foo",
"incoming_webhook": {"url": "http://example.org", "channel": "bar"},
}
mock_post.return_value.text = json.dumps(oauth_response)
mock_post.return_value.json.return_value = oauth_response
url = "/integrations/add_slack_btn/?code=12345678&state=foo"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url, follow=True)
self.assertRedirects(r, "/integrations/")
self.assertContains(r, "The Slack integration has been added!")
ch = Channel.objects.get()
self.assertEqual(ch.slack_team, "foo")
self.assertEqual(ch.slack_channel, "bar")
self.assertEqual(ch.slack_webhook_url, "http://example.org")
self.assertEqual(ch.project, self.project)
# Session should now be clean
self.assertFalse("slack" in self.client.session)
def test_it_avoids_csrf(self):
session = self.client.session
session["slack"] = "foo"
session.save()
url = "/integrations/add_slack_btn/?code=12345678&state=bar"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 400)
@patch("hc.front.views.requests.post")
def test_it_handles_oauth_error(self, mock_post):
session = self.client.session
session["slack"] = "foo"
session.save()
oauth_response = {"ok": False, "error": "something went wrong"}
mock_post.return_value.text = json.dumps(oauth_response)
mock_post.return_value.json.return_value = oauth_response
url = "/integrations/add_slack_btn/?code=12345678&state=foo"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url, follow=True)
self.assertRedirects(r, "/integrations/")
self.assertContains(r, "something went wrong")
self.assertTrue("add_slack" in self.client.session)

+ 69
- 0
hc/front/tests/test_add_slack_complete.py View File

@ -0,0 +1,69 @@
import json
from django.test.utils import override_settings
from hc.api.models import Channel
from hc.test import BaseTestCase
from mock import patch
@override_settings(SLACK_CLIENT_ID="fake-client-id")
class AddSlackCompleteTestCase(BaseTestCase):
@patch("hc.front.views.requests.post")
def test_it_handles_oauth_response(self, mock_post):
session = self.client.session
session["add_slack"] = ("foo", str(self.project.code))
session.save()
oauth_response = {
"ok": True,
"team_name": "foo",
"incoming_webhook": {"url": "http://example.org", "channel": "bar"},
}
mock_post.return_value.text = json.dumps(oauth_response)
mock_post.return_value.json.return_value = oauth_response
url = "/integrations/add_slack_btn/?code=12345678&state=foo"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url, follow=True)
self.assertRedirects(r, self.channels_url)
self.assertContains(r, "The Slack integration has been added!")
ch = Channel.objects.get()
self.assertEqual(ch.slack_team, "foo")
self.assertEqual(ch.slack_channel, "bar")
self.assertEqual(ch.slack_webhook_url, "http://example.org")
self.assertEqual(ch.project, self.project)
# Session should now be clean
self.assertFalse("add_slack" in self.client.session)
def test_it_avoids_csrf(self):
session = self.client.session
session["add_slack"] = ("foo", str(self.project.code))
session.save()
url = "/integrations/add_slack_btn/?code=12345678&state=bar"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
@patch("hc.front.views.requests.post")
def test_it_handles_oauth_error(self, mock_post):
session = self.client.session
session["add_slack"] = ("foo", str(self.project.code))
session.save()
oauth_response = {"ok": False, "error": "something went wrong"}
mock_post.return_value.text = json.dumps(oauth_response)
mock_post.return_value.json.return_value = oauth_response
url = "/integrations/add_slack_btn/?code=12345678&state=foo"
self.client.login(username="[email protected]", password="password")
r = self.client.get(url, follow=True)
self.assertRedirects(r, self.channels_url)
self.assertContains(r, "something went wrong")

+ 9
- 0
hc/front/tests/test_add_slack_help.py View File

@ -0,0 +1,9 @@
from django.test.utils import override_settings
from hc.test import BaseTestCase
@override_settings(SLACK_CLIENT_ID="fake-client-id")
class AddSlackHelpTestCase(BaseTestCase):
def test_instructions_work(self):
r = self.client.get("/integrations/add_slack/")
self.assertContains(r, "Setup Guide", status_code=200)

+ 4
- 2
hc/front/urls.py View File

@ -24,8 +24,6 @@ check_urls = [
channel_urls = [
path("", views.channels, name="hc-channels"),
path("add_slack/", views.add_slack, name="hc-add-slack"),
path("add_slack_btn/", views.add_slack_btn, name="hc-add-slack-btn"),
path(
"add_pushbullet/",
views.add_pushbullet_complete,
@ -34,6 +32,8 @@ channel_urls = [
path("add_discord/", views.add_discord_complete, name="hc-add-discord-complete"),
path("add_pushover/", views.add_pushover_help),
path("telegram/bot/", views.telegram_bot, name="hc-telegram-webhook"),
path("add_slack/", views.add_slack_help),
path("add_slack_btn/", views.add_slack_complete),
path("add_telegram/", views.add_telegram, name="hc-add-telegram"),
path("add_trello/settings/", views.trello_settings, name="hc-trello-settings"),
path("<uuid:code>/checks/", views.channel_checks, name="hc-channel-checks"),
@ -67,6 +67,8 @@ project_urls = [
path("add_pushbullet/", views.add_pushbullet, name="hc-add-pushbullet"),
path("add_pushover/", views.add_pushover, name="hc-add-pushover"),
path("add_shell/", views.add_shell, name="hc-add-shell"),
path("add_slack/", views.add_slack, name="hc-add-slack"),
path("add_slack_btn/", views.add_slack_btn, name="hc-add-slack-btn"),
path("add_sms/", views.add_sms, name="hc-add-sms"),
path("add_trello/", views.add_trello, name="hc-add-trello"),
path("add_victorops/", views.add_victorops, name="hc-add-victorops"),


+ 70
- 32
hc/front/views.py View File

@ -684,6 +684,7 @@ def channels(request, code=None):
"enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None,
"enable_apprise": settings.APPRISE_ENABLED is True,
"enable_shell": settings.SHELL_ENABLED is True,
"enable_slack_btn": settings.SLACK_CLIENT_ID is not None,
"use_payments": settings.USE_PAYMENTS,
}
@ -1025,76 +1026,93 @@ def add_pagerteam(request, code):
return render(request, "integrations/add_pagerteam.html", ctx)
def add_slack(request):
if not settings.SLACK_CLIENT_ID and not request.user.is_authenticated:
return redirect("hc-login")
@login_required
def add_slack(request, code):
project = _get_project_for_user(request, code)
if request.method == "POST":
form = AddUrlForm(request.POST)
if form.is_valid():
channel = Channel(project=request.project, kind="slack")
channel = Channel(project=project, kind="slack")
channel.value = form.cleaned_data["value"]
channel.save()
channel.assign_all_checks()
return redirect("hc-channels")
return redirect("hc-p-channels", project.code)
else:
form = AddUrlForm()
ctx = {
"page": "channels",
"form": form,
"slack_client_id": settings.SLACK_CLIENT_ID,
}
if request.user.is_authenticated:
ctx["project"] = request.project
return render(request, "integrations/add_slack.html", ctx)
if settings.SLACK_CLIENT_ID and request.user.is_authenticated:
ctx["state"] = _prepare_state(request, "slack")
return render(request, "integrations/add_slack.html", ctx)
def add_slack_help(request):
if not settings.SLACK_CLIENT_ID:
raise Http404("slack integration is not available")
ctx = {"page": "channels"}
return render(request, "integrations/add_slack_btn.html", ctx)
@login_required
def add_mattermost(request, code):
def add_slack_btn(request, code):
if not settings.SLACK_CLIENT_ID:
raise Http404("slack integration is not available")
project = _get_project_for_user(request, code)
if request.method == "POST":
form = AddUrlForm(request.POST)
if form.is_valid():
channel = Channel(project=project, kind="mattermost")
channel.value = form.cleaned_data["value"]
channel.save()
state = token_urlsafe()
authorize_url = "https://slack.com/oauth/authorize?" + urlencode(
{
"scope": "incoming-webhook",
"client_id": settings.SLACK_CLIENT_ID,
"state": state,
}
)
channel.assign_all_checks()
return redirect("hc-p-channels", project.code)
else:
form = AddUrlForm()
ctx = {
"project": project,
"page": "channels",
"authorize_url": authorize_url,
}
ctx = {"page": "channels", "form": form, "project": project}
return render(request, "integrations/add_mattermost.html", ctx)
request.session["add_slack"] = (state, str(project.code))
return render(request, "integrations/add_slack_btn.html", ctx)
@login_required
def add_slack_btn(request):
code = _get_validated_code(request, "slack")
if code is None:
return HttpResponseBadRequest()
def add_slack_complete(request):
if not settings.SLACK_CLIENT_ID:
raise Http404("slack integration is not available")
if "add_slack" not in request.session:
return HttpResponseForbidden()
state, code = request.session.pop("add_slack")
project = _get_project_for_user(request, code)
if request.GET.get("error") == "access_denied":
messages.warning(request, "Slack setup was cancelled.")
return redirect("hc-p-channels", project.code)
if request.GET.get("state") != state:
return HttpResponseForbidden()
result = requests.post(
"https://slack.com/api/oauth.access",
{
"client_id": settings.SLACK_CLIENT_ID,
"client_secret": settings.SLACK_CLIENT_SECRET,
"code": code,
"code": request.GET.get("code"),
},
)
doc = result.json()
if doc.get("ok"):
channel = Channel(kind="slack", project=request.project)
channel.user = request.project.owner
channel = Channel(kind="slack", project=project)
channel.value = result.text
channel.save()
channel.assign_all_checks()
@ -1103,7 +1121,27 @@ def add_slack_btn(request):
s = doc.get("error")
messages.warning(request, "Error message from slack: %s" % s)
return redirect("hc-channels")
return redirect("hc-p-channels", project.code)
@login_required
def add_mattermost(request, code):
project = _get_project_for_user(request, code)
if request.method == "POST":
form = AddUrlForm(request.POST)
if form.is_valid():
channel = Channel(project=project, kind="mattermost")
channel.value = form.cleaned_data["value"]
channel.save()
channel.assign_all_checks()
return redirect("hc-p-channels", project.code)
else:
form = AddUrlForm()
ctx = {"page": "channels", "form": form, "project": project}
return render(request, "integrations/add_mattermost.html", ctx)
@login_required


+ 5
- 1
templates/front/channels.html View File

@ -170,7 +170,11 @@
<h2>Slack</h2>
<p>A messaging app for teams.</p>
<a href="{% url 'hc-add-slack' %}" class="btn btn-primary">Add Integration</a>
{% if enable_slack_btn %}
<a href="{% url 'hc-add-slack-btn' project.code %}" class="btn btn-primary">Add Integration</a>
{% else %}
<a href="{% url 'hc-add-slack' project.code %}" class="btn btn-primary">Add Integration</a>
{% endif %}
</li>
<li>
<img src="{% static 'img/integrations/email.png' %}"


+ 1
- 91
templates/integrations/add_slack.html View File

@ -3,97 +3,9 @@
{% block title %}Add Slack - {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
{% if slack_client_id %}
<div class="jumbotron">
{% if request.user.is_authenticated %}
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set
up {% site_name %} to post status updates directly to an appropriate
Slack channel.</p>
<div class="text-center">
<a href="https://slack.com/oauth/authorize?scope=incoming-webhook&client_id={{ slack_client_id }}&state={{ state }}">
<img
alt="Add to Slack"
src="{% static 'img/integrations/add_to_slack.png' %}"
srcset="{% static 'img/integrations/add_to_slack.png' %} 1x, {% static 'img/integrations/[email protected]' %} 2x" />
</a>
</div>
{% else %}
<p>
{% site_name %} is a <strong>free</strong> and
<a href="https://github.com/healthchecks/healthchecks">open source</a>
service for monitoring your cron jobs, background processes and
scheduled tasks. Before adding Slack integration, please log into
{% site_name %}:</p>
<div class="text-center">
<a href="{% url 'hc-login' %}?next={% url 'hc-add-slack' %}"
class="btn btn-primary btn-lg">Sign In</a>
</div>
{% endif %}
</div>
<h2>Setup Guide</h2>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">1</span>
<p>
After {% if request.user.is_authenticated %}{% else %}signing in and{% endif %}
clicking on "Add to Slack", you should
be on a page that says "{% site_name %} would like access to
your Slack team". Select the team you want to add the
{% site_name %} integration app to.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_1.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">2</span>
<p>
You should now be on a page that says "{% site_name %} would
like access to <i>TEAM NAME</i>". Select the channel you want to
post {% site_name %} notifications to.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_2.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">3</span>
<p>
That is all! You will now be redirected back to
"Integrations" page on {% site_name %} and see
the new integration!
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_3.png' %}">
</div>
</div>
{% else %}
<h1>Slack</h1>
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set
@ -141,7 +53,7 @@
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal" action="{% url 'hc-add-slack' %}">
<form method="post" class="form-horizontal">
{% csrf_token %}
<div class="form-group {{ form.value.css_classes }}">
<label for="callback-url" class="col-sm-2 control-label">
@ -169,8 +81,6 @@
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% endblock %}

+ 97
- 0
templates/integrations/add_slack_btn.html View File

@ -0,0 +1,97 @@
{% extends "base.html" %}
{% load humanize static hc_extras %}
{% block title %}Add Slack - {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="jumbotron">
{% if request.user.is_authenticated %}
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set
up {% site_name %} to post status updates directly to an appropriate
Slack channel.</p>
<div class="text-center">
<a href="{{ authorize_url }}">
<img
alt="Add to Slack"
src="{% static 'img/integrations/add_to_slack.png' %}"
srcset="{% static 'img/integrations/add_to_slack.png' %} 1x, {% static 'img/integrations/[email protected]' %} 2x" />
</a>
</div>
{% else %}
<p>
{% site_name %} is a <strong>free</strong> and
<a href="https://github.com/healthchecks/healthchecks">open source</a>
service for monitoring your cron jobs, background processes and
scheduled tasks. Before adding Slack integration, please log into
{% site_name %}:</p>
<div class="text-center">
<a href="{% url 'hc-login' %}"
class="btn btn-primary btn-lg">Sign In</a>
</div>
{% endif %}
</div>
<h2>Setup Guide</h2>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">1</span>
<p>
After {% if request.user.is_authenticated %}{% else %}signing in and{% endif %}
clicking on "Add to Slack", you should
be on a page that says "{% site_name %} would like access to
your Slack team". Select the team you want to add the
{% site_name %} integration app to.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_1.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">2</span>
<p>
You should now be on a page that says "{% site_name %} would
like access to <i>TEAM NAME</i>". Select the channel you want to
post {% site_name %} notifications to.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_2.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">3</span>
<p>
That is all! You will now be redirected back to
"Integrations" page on {% site_name %} and see
the new integration!
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_slack_btn_3.png' %}">
</div>
</div>
</div>
</div>
{% endblock %}

Loading…
Cancel
Save