forked from GithubBackups/healthchecks
THe LINE Notify integration uses OAuth2 flow.
This commit is contained in:
parent
25a8ec6bd9
commit
4f53325730
@ -136,6 +136,8 @@ Configurations settings loaded from environment variables:
|
||||
| MATRIX_ACCESS_TOKEN | `None`
|
||||
| APPRISE_ENABLED | `"False"`
|
||||
| SHELL_ENABLED | `"False"`
|
||||
| LINENOTIFY_CLIENT_ID | `None`
|
||||
| LINENOTIFY_CLIENT_SECRET | `None`
|
||||
|
||||
|
||||
Some useful settings keys to override are:
|
||||
|
@ -1,10 +1,9 @@
|
||||
from hc.api.models import Channel
|
||||
from django.test.utils import override_settings
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
@override_settings(LINENOTIFY_CLIENT_ID="t1", LINENOTIFY_CLIENT_SECRET="s1")
|
||||
class AddLineNotifyTestCase(BaseTestCase):
|
||||
url = "/integrations/add_linenotify/"
|
||||
|
||||
def setUp(self):
|
||||
super(AddLineNotifyTestCase, self).setUp()
|
||||
self.url = "/projects/%s/add_linenotify/" % self.project.code
|
||||
@ -12,32 +11,17 @@ class AddLineNotifyTestCase(BaseTestCase):
|
||||
def test_instructions_work(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertContains(r, "LineNotify")
|
||||
self.assertContains(r, "notify-bot.line.me/oauth/authorize", status_code=200)
|
||||
self.assertContains(r, "Connect LINE Notify")
|
||||
|
||||
def test_it_works(self):
|
||||
form = {"token": "helloworld"}
|
||||
# There should now be a key in session
|
||||
self.assertTrue("add_linenotify" in self.client.session)
|
||||
|
||||
@override_settings(LINENOTIFY_CLIENT_ID=None)
|
||||
def test_it_requires_client_id(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.post(self.url, form)
|
||||
self.assertRedirects(r, self.channels_url)
|
||||
|
||||
c = Channel.objects.get()
|
||||
self.assertEqual(c.kind, "linenotify")
|
||||
self.assertEqual(c.value, "helloworld")
|
||||
self.assertEqual(c.project, self.project)
|
||||
|
||||
def test_it_handles_json_linenotify_value(self):
|
||||
c = Channel(kind="linenotify", value="foo123")
|
||||
self.assertEqual(c.linenotify_token, "foo123")
|
||||
|
||||
def test_it_save_token(self):
|
||||
form = {"token": "foo123"}
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
self.client.post(self.url, form)
|
||||
|
||||
c = Channel.objects.get()
|
||||
self.assertEqual(c.value, "foo123")
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
def test_it_requires_rw_access(self):
|
||||
self.bobs_membership.rw = False
|
||||
|
81
hc/front/tests/test_add_linenotify_complete.py
Normal file
81
hc/front/tests/test_add_linenotify_complete.py
Normal file
@ -0,0 +1,81 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from hc.api.models import Channel
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
@override_settings(LINENOTIFY_CLIENT_ID="t1", LINENOTIFY_CLIENT_SECRET="s1")
|
||||
class AddLineNotifyCompleteTestCase(BaseTestCase):
|
||||
url = "/integrations/add_linenotify/"
|
||||
|
||||
@patch("hc.front.views.requests")
|
||||
def test_it_handles_oauth_response(self, mock_requests):
|
||||
session = self.client.session
|
||||
session["add_linenotify"] = ("foo", str(self.project.code))
|
||||
session.save()
|
||||
|
||||
mock_requests.post.return_value.json.return_value = {
|
||||
"status": 200,
|
||||
"access_token": "test-token",
|
||||
}
|
||||
|
||||
mock_requests.get.return_value.json.return_value = {"target": "Alice"}
|
||||
|
||||
url = self.url + "?code=12345678&state=foo"
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(url, follow=True)
|
||||
self.assertRedirects(r, self.channels_url)
|
||||
self.assertContains(r, "The LINE Notify integration has been added!")
|
||||
|
||||
ch = Channel.objects.get()
|
||||
self.assertEqual(ch.value, "test-token")
|
||||
self.assertEqual(ch.name, "Alice")
|
||||
self.assertEqual(ch.project, self.project)
|
||||
|
||||
# Session should now be clean
|
||||
self.assertFalse("add_linenotify" in self.client.session)
|
||||
|
||||
def test_it_avoids_csrf(self):
|
||||
session = self.client.session
|
||||
session["add_linenotify"] = ("foo", str(self.project.code))
|
||||
session.save()
|
||||
|
||||
url = self.url + "?code=12345678&state=bar"
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_it_handles_denial(self):
|
||||
session = self.client.session
|
||||
session["add_linenotify"] = ("foo", str(self.project.code))
|
||||
session.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url + "?error=access_denied&state=foo", follow=True)
|
||||
self.assertRedirects(r, self.channels_url)
|
||||
self.assertContains(r, "LINE Notify setup was cancelled")
|
||||
|
||||
self.assertEqual(Channel.objects.count(), 0)
|
||||
|
||||
# Session should now be clean
|
||||
self.assertFalse("add_linenotify" in self.client.session)
|
||||
|
||||
@override_settings(LINENOTIFY_CLIENT_ID=None)
|
||||
def test_it_requires_client_id(self):
|
||||
url = self.url + "?code=12345678&state=bar"
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
def test_it_requires_rw_access(self):
|
||||
self.bobs_membership.rw = False
|
||||
self.bobs_membership.save()
|
||||
|
||||
url = self.url + "?code=12345678&state=bar"
|
||||
self.client.login(username="bob@example.org", password="password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403)
|
@ -37,7 +37,7 @@ class AddPushbulletTestCase(BaseTestCase):
|
||||
|
||||
def test_it_avoids_csrf(self):
|
||||
session = self.client.session
|
||||
session["pushbullet"] = ("foo", str(self.project.code))
|
||||
session["add_pushbullet"] = ("foo", str(self.project.code))
|
||||
session.save()
|
||||
|
||||
url = self.url + "?code=12345678&state=bar&project=%s" % self.project.code
|
||||
|
@ -30,6 +30,7 @@ channel_urls = [
|
||||
name="hc-add-pushbullet-complete",
|
||||
),
|
||||
path("add_discord/", views.add_discord_complete, name="hc-add-discord-complete"),
|
||||
path("add_linenotify/", views.add_linenotify_complete),
|
||||
path("add_pushover/", views.pushover_help, name="hc-pushover-help"),
|
||||
path("telegram/", views.telegram_help, name="hc-telegram-help"),
|
||||
path("telegram/bot/", views.telegram_bot, name="hc-telegram-webhook"),
|
||||
|
@ -290,7 +290,9 @@ def index(request):
|
||||
"check": check,
|
||||
"ping_url": check.url(),
|
||||
"enable_apprise": settings.APPRISE_ENABLED is True,
|
||||
"enable_call": settings.TWILIO_AUTH is not None,
|
||||
"enable_discord": settings.DISCORD_CLIENT_ID is not None,
|
||||
"enable_linenotify": settings.LINENOTIFY_CLIENT_ID is not None,
|
||||
"enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None,
|
||||
"enable_pdc": settings.PD_VENDOR_KEY is not None,
|
||||
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
|
||||
@ -298,7 +300,6 @@ def index(request):
|
||||
"enable_shell": settings.SHELL_ENABLED is True,
|
||||
"enable_slack_btn": settings.SLACK_CLIENT_ID is not None,
|
||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||
"enable_call": settings.TWILIO_AUTH is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_trello": settings.TRELLO_APP_KEY is not None,
|
||||
"enable_whatsapp": settings.TWILIO_USE_WHATSAPP,
|
||||
@ -738,7 +739,9 @@ def channels(request, code):
|
||||
"profile": project.owner_profile,
|
||||
"channels": channels,
|
||||
"enable_apprise": settings.APPRISE_ENABLED is True,
|
||||
"enable_call": settings.TWILIO_AUTH is not None,
|
||||
"enable_discord": settings.DISCORD_CLIENT_ID is not None,
|
||||
"enable_linenotify": settings.LINENOTIFY_CLIENT_ID is not None,
|
||||
"enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None,
|
||||
"enable_pdc": settings.PD_VENDOR_KEY is not None,
|
||||
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
|
||||
@ -746,7 +749,6 @@ def channels(request, code):
|
||||
"enable_shell": settings.SHELL_ENABLED is True,
|
||||
"enable_slack_btn": settings.SLACK_CLIENT_ID is not None,
|
||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||
"enable_call": settings.TWILIO_AUTH is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_trello": settings.TRELLO_APP_KEY is not None,
|
||||
"enable_whatsapp": settings.TWILIO_USE_WHATSAPP,
|
||||
@ -1819,21 +1821,82 @@ def add_spike(request, code):
|
||||
return render(request, "integrations/add_spike.html", ctx)
|
||||
|
||||
|
||||
@require_setting("LINENOTIFY_CLIENT_ID")
|
||||
@login_required
|
||||
def add_linenotify(request, code):
|
||||
project = _get_rw_project_for_user(request, code)
|
||||
redirect_uri = settings.SITE_ROOT + reverse(add_linenotify_complete)
|
||||
|
||||
if request.method == "POST":
|
||||
form = forms.AddLineNotifyForm(request.POST)
|
||||
if form.is_valid():
|
||||
channel = Channel(project=project, kind="linenotify")
|
||||
channel.value = form.cleaned_data["token"]
|
||||
channel.save()
|
||||
state = token_urlsafe()
|
||||
authorize_url = " https://notify-bot.line.me/oauth/authorize?" + urlencode(
|
||||
{
|
||||
"client_id": settings.LINENOTIFY_CLIENT_ID,
|
||||
"redirect_uri": redirect_uri,
|
||||
"response_type": "code",
|
||||
"state": state,
|
||||
"scope": "notify",
|
||||
}
|
||||
)
|
||||
|
||||
channel.assign_all_checks()
|
||||
return redirect("hc-p-channels", project.code)
|
||||
else:
|
||||
form = forms.AddLineNotifyForm()
|
||||
ctx = {
|
||||
"page": "channels",
|
||||
"project": project,
|
||||
"authorize_url": authorize_url,
|
||||
}
|
||||
|
||||
ctx = {"page": "channels", "project": project, "form": form}
|
||||
request.session["add_linenotify"] = (state, str(project.code))
|
||||
return render(request, "integrations/add_linenotify.html", ctx)
|
||||
|
||||
|
||||
@require_setting("LINENOTIFY_CLIENT_ID")
|
||||
@login_required
|
||||
def add_linenotify_complete(request):
|
||||
if "add_linenotify" not in request.session:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
state, code = request.session.pop("add_linenotify")
|
||||
if request.GET.get("state") != state:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
project = _get_rw_project_for_user(request, code)
|
||||
if request.GET.get("error") == "access_denied":
|
||||
messages.warning(request, "LINE Notify setup was cancelled.")
|
||||
return redirect("hc-p-channels", project.code)
|
||||
|
||||
# Exchange code for access token
|
||||
redirect_uri = settings.SITE_ROOT + reverse(add_linenotify_complete)
|
||||
result = requests.post(
|
||||
"https://notify-bot.line.me/oauth/token",
|
||||
{
|
||||
"grant_type": "authorization_code",
|
||||
"code": request.GET.get("code"),
|
||||
"redirect_uri": redirect_uri,
|
||||
"client_id": settings.LINENOTIFY_CLIENT_ID,
|
||||
"client_secret": settings.LINENOTIFY_CLIENT_SECRET,
|
||||
},
|
||||
)
|
||||
|
||||
doc = result.json()
|
||||
if doc.get("status") != 200:
|
||||
messages.warning(request, "Something went wrong.")
|
||||
return redirect("hc-p-channels", project.code)
|
||||
|
||||
# Fetch notification target's name, will use it as channel name:
|
||||
token = doc["access_token"]
|
||||
result = requests.get(
|
||||
"https://notify-api.line.me/api/status",
|
||||
headers={"Authorization": "Bearer %s" % token},
|
||||
)
|
||||
doc = result.json()
|
||||
|
||||
channel = Channel(kind="linenotify", project=project)
|
||||
channel.name = doc.get("target")
|
||||
channel.value = token
|
||||
channel.save()
|
||||
channel.assign_all_checks()
|
||||
messages.success(request, "The LINE Notify integration has been added!")
|
||||
|
||||
return redirect("hc-p-channels", project.code)
|
||||
|
||||
|
||||
# Forks: add custom views after this line
|
||||
|
@ -219,6 +219,9 @@ APPRISE_ENABLED = envbool("APPRISE_ENABLED", "False")
|
||||
# Local shell commands
|
||||
SHELL_ENABLED = envbool("SHELL_ENABLED", "False")
|
||||
|
||||
# LINE Notify
|
||||
LINENOTIFY_CLIENT_ID = os.getenv("LINENOTIFY_CLIENT_ID")
|
||||
LINENOTIFY_CLIENT_SECRET = os.getenv("LINENOTIFY_CLIENT_SECRET")
|
||||
|
||||
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
|
||||
from .local_settings import *
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 253 KiB |
Binary file not shown.
Before Width: | Height: | Size: 121 KiB |
Binary file not shown.
Before Width: | Height: | Size: 79 KiB |
@ -230,6 +230,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_linenotify %}
|
||||
<li>
|
||||
<img src="{% static 'img/integrations/linenotify.png' %}"
|
||||
class="icon" alt="LINE Notify icon" />
|
||||
@ -238,6 +239,7 @@
|
||||
<p>Receive a notification on LINE when a check goes up or down.</p>
|
||||
<a href="{% url 'hc-add-linenotify' project.code %}" class="btn btn-primary">Add Integration</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_matrix %}
|
||||
<li>
|
||||
|
@ -435,12 +435,14 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_linenotify %}
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
|
||||
<div class="integration">
|
||||
<img src="{% static 'img/integrations/linenotify.png' %}" class="icon" alt="" />
|
||||
<h3>LINE Notify<br><small>Chat</small></h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_matrix %}
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
|
||||
|
@ -1,104 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
{% load humanize static hc_extras %}
|
||||
|
||||
{% block title %}LineNotify Integration for {% site_name %}{% endblock %}
|
||||
{% block title %}LINE Notify Integration for {{ site_name }}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>LINE Notify</h1>
|
||||
<div class="col-sm-12">
|
||||
<h1>LINE Notify</h1>
|
||||
|
||||
<p><a href="https://notify-bot.line.me/en/">LINE Notify</a>
|
||||
allows you to send notifications directly to your LINE chats.
|
||||
You can set up {% site_name %} to post status updates directly to an
|
||||
appropriate LINE chat. </p>
|
||||
<div class="jumbotron">
|
||||
<p>
|
||||
With the <a href="https://notify-bot.line.me/en/">LINE Notify</a>
|
||||
integration, {{ site_name }} will send
|
||||
a notification to your selected LINE chat when a check
|
||||
goes <strong>up</strong> or <strong>down</strong>.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Setup Guide</h2>
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
Log into your LINE account,
|
||||
go to <strong>My Page</strong> in the upper right corner.
|
||||
In the <strong>My Page</strong>, scroll to the bottom of the page,
|
||||
and click on <strong>Generate token</strong> button.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="marker-wrap">
|
||||
<span style="left: 77%; top: 14%;" class="marker"></span>
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_linenotify_1.png' %}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
It will pop up the <strong>Generate token</strong> Form.
|
||||
Fill out the details, and click the button.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_linenotify_2.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
Copy the displayed <strong>Token</strong> and paste it
|
||||
in the form below.
|
||||
Save the integration, and it's done!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_linenotify_3.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Integration Settings</h2>
|
||||
|
||||
<form method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<div class="form-group {{ form.value.css_classes }}">
|
||||
<label for="api-token" class="col-sm-2 control-label">API Token</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
id="api-token"
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="token"
|
||||
placeholder=""
|
||||
value="{{ form.token.value|default:"" }}">
|
||||
|
||||
{% if form.token.errors %}
|
||||
<div class="help-block">
|
||||
{{ form.token.errors|join:"" }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Save Integration</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post" class="text-center">
|
||||
{% csrf_token %}
|
||||
<a href="{{ authorize_url }}" class="btn btn-lg btn-default">
|
||||
<img class="ai-icon" src="{% static 'img/integrations/linenotify.png' %}" alt="LINE Notify" />
|
||||
Connect LINE Notify
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user