Skip to content

Commit

Permalink
Merge 5c183d3 into bf44118
Browse files Browse the repository at this point in the history
  • Loading branch information
JoyyToo committed Jan 23, 2018
2 parents bf44118 + 5c183d3 commit f8e382e
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export API_KEY="your africas api key"
export USERNAME="your africastalking username"
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ hc.sqlite
static-collected
.DS_Store
static/.DS_Store
.vscode/
.vscode/
.env
.idea/
hc-venv
hc/api/migrations/0027_auto_20180117_1744.py
venv/
2 changes: 2 additions & 0 deletions hc/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ def formatted_kind(self, obj):
return "Slack"
elif obj.kind == "hipchat":
return "HipChat"
elif obj.kind == "sms":
return "SMS"
elif obj.kind == "email" and obj.email_verified:
return "Email"
elif obj.kind == "email" and not obj.email_verified:
Expand Down
3 changes: 1 addition & 2 deletions hc/api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2018-01-19 15:40
from __future__ import unicode_literals

import datetime
Expand All @@ -24,7 +23,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.UUIDField(default=uuid.uuid4, editable=False)),
('created', models.DateTimeField(auto_now_add=True)),
('kind', models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('po', 'Pushover'), ('victorops', 'VictorOps')], max_length=20)),
('kind', models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('po', 'Pushover'), ('victorops', 'VictorOps'), ('sms', 'SMS')], max_length=20)),
('value', models.TextField(blank=True)),
('email_verified', models.BooleanField(default=False)),
],
Expand Down
4 changes: 3 additions & 1 deletion hc/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
CHANNEL_KINDS = (("email", "Email"), ("webhook", "Webhook"),
("hipchat", "HipChat"),
("slack", "Slack"), ("pd", "PagerDuty"), ("po", "Pushover"),
("victorops", "VictorOps"))
("victorops", "VictorOps"), ("sms","SMS"))

PO_PRIORITIES = {
-2: "lowest",
Expand Down Expand Up @@ -194,6 +194,8 @@ def transport(self):
return transports.Pushbullet(self)
elif self.kind == "po":
return transports.Pushover(self)
elif self.kind == "sms":
return transports.SMS(self)
else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind)

Expand Down
27 changes: 27 additions & 0 deletions hc/api/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
from django.template.loader import render_to_string
from django.utils import timezone
import json
import os
import requests
from six.moves.urllib.parse import quote

from hc.front.aes import AESCipher
from hc.lib import emails
from africastalking.AfricasTalkingGateway import AfricasTalkingGateway, AfricasTalkingGatewayException


def tmpl(template_name, **ctx):
Expand Down Expand Up @@ -216,3 +219,27 @@ def notify(self, check):
}

return self.post(self.channel.value, payload)


class SMS(HttpTransport):
def notify(self, check):
username = os.getenv("USERNAME")
apikey = os.getenv("API_KEY")
new_cipher = AESCipher(key='mykey')
self.channel.value = new_cipher.decrypt(self.channel.value)
to = self.channel.value
message = """
Healthchecks Notification!\n
Name: %s \n
Status: %s \n
Last ping: %s\n
Total Pings: %s
""" % (check.name, check.status, check.last_ping.strftime('%x, %X'), check.n_pings)
# Create a new instance of our awesome gateway class
gateway = AfricasTalkingGateway(username, apikey)
try:
results = gateway.sendMessage(to, message)
print(results)

except AfricasTalkingGatewayException as e:
print('Encountered an error while sending: %s' % str(e))
40 changes: 40 additions & 0 deletions hc/front/aes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import base64
import hashlib

from Crypto import Random
from Crypto.Cipher import AES

class AESCipher(object):
"""
A classical AES Cipher. Can use any size of data and any size of password thanks to padding.
Also ensure the coherence and the type of the data with a unicode to byte converter.
"""
def __init__(self, key):
self.bs = 32
self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest()

@staticmethod
def str_to_bytes(data):
u_type = type(b''.decode('utf8'))
if isinstance(data, u_type):
return data.encode('utf8')
return data

def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes(chr(self.bs - len(s) % self.bs))

@staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]

def encrypt(self, raw):
raw = self._pad(AESCipher.str_to_bytes(raw))
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8')

def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
12 changes: 11 additions & 1 deletion hc/front/tests/test_add_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@ def test_team_access_channel_works(self):
q = Channel.objects.filter(user= alice, value="bob@example.org")
self.assertEqual(q.count(), 1)
self.assertEqual(r.status_code, 302)


def test_sms_works(self):
""" test sms access"""
alice_channel = User.objects.get(email="alice@example.org")
alice_before = Channel.objects.filter(user=alice_channel).count()
self.client.login(username="bob@example.org", password="password")
url = "/integrations/add/"
form = {"kind": "sms"}
self.client.post(url, form)
alice_after = Channel.objects.filter(user=alice_channel).count()
self.assertEqual(alice_after, (alice_before + 1))

### Test that bad kinds don't work
def test_bad_kinds_dont_work(self):
Expand Down
11 changes: 11 additions & 0 deletions hc/front/tests/test_add_sms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import json
from hc.api.models import Channel
from hc.test import BaseTestCase


class AddSMSTestCase(BaseTestCase):

def test_sms_works(self):
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/integrations/add_sms/")
self.assertContains(r, "Integration Settings", status_code=200)
1 change: 1 addition & 0 deletions hc/front/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
url(r'^add_slack/$', views.add_slack, name="hc-add-slack"),
url(r'^add_slack_btn/$', views.add_slack_btn, name="hc-add-slack-btn"),
url(r'^add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"),
url(r'^add_sms/$', views.add_sms, name="hc-add-sms"),
url(r'^add_pushbullet/$', views.add_pushbullet, name="hc-add-pushbullet"),
url(r'^add_pushover/$', views.add_pushover, name="hc-add-pushover"),
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
Expand Down
18 changes: 15 additions & 3 deletions hc/front/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import Counter
from datetime import timedelta as td
from itertools import tee
from .aes import AESCipher

import requests
from django.conf import settings
Expand Down Expand Up @@ -277,10 +278,14 @@ def channels(request):

channel.checks = new_checks
return redirect("hc-channels")

channels = Channel.objects.filter(user=request.team.user).order_by("created")
channels = channels.annotate(n_checks=Count("checks"))
for channel in channels:
if channel.kind == "sms":
new_cipher = AESCipher(key='mykey')
channel.value = new_cipher.decrypt(channel.value)


num_checks = Check.objects.filter(user=request.team.user).count()

ctx = {
Expand All @@ -297,6 +302,10 @@ def do_add_channel(request, data):
form = AddChannelForm(data)
if form.is_valid():
channel = form.save(commit=False)
if channel.kind == "sms":
cipher = AESCipher(key='mykey')
channel.value = cipher.encrypt(channel.value)

channel.user = request.team.user
channel.save()

Expand All @@ -307,15 +316,14 @@ def do_add_channel(request, data):

return redirect("hc-channels")
else:
print(form.errors)
return HttpResponseBadRequest()


@login_required
def add_channel(request):
assert request.method == "POST"
return do_add_channel(request, request.POST)


@login_required
@uuid_or_400
def channel_checks(request, code):
Expand Down Expand Up @@ -435,6 +443,10 @@ def add_hipchat(request):
ctx = {"page": "channels"}
return render(request, "integrations/add_hipchat.html", ctx)

@login_required
def add_sms(request):
ctx = {"page": "channels"}
return render(request, "integrations/add_sms.html", ctx)

@login_required
def add_pushbullet(request):
Expand Down
10 changes: 10 additions & 0 deletions hc/lib/slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf import settings
from djmail.template_mail import InlineCSSTemplateMail

def send(name, to, ctx):
o = InlineCSSTemplateMail(name)
ctx["SITE_ROOT"] = settings.SITE_ROOT
o.send(to, ctx)

def alert(to, ctx):
send("alert", to, ctx)
25 changes: 13 additions & 12 deletions hc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@
# install requirements.txt and do manage.py runserver and it works
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': './hc.sqlite',
}
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'hc',
'USER': 'postgres',
'TEST': {'CHARSET': 'UTF8'}
}
}

# You can switch database engine to postgres or mysql using environment
Expand All @@ -111,7 +113,7 @@
}
}

if os.environ.get("HEROKU") == 'TRUE':
if os.environ.get("DATABASE_URL") == 'TRUE':
db_from_env = dj_database_url.config()
DATABASES['default'].update(db_from_env)

Expand All @@ -126,8 +128,7 @@

USE_TZ = True

# SITE_ROOT = "http://localhost:8000"
SITE_ROOT = "https://hc-logros.herokuapp.com"
SITE_ROOT = "http://localhost:8000"
SITE_NAME = "Health Checks"

PING_ENDPOINT = SITE_ROOT + "/ping/"
Expand All @@ -145,15 +146,15 @@
EMAIL_BACKEND = "djmail.backends.default.EmailBackend"
DJMAIL_REAL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# Email
EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = os.environ.get('EMAIL_PORT')
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
EMAIL_HOST = os.getenv('EMAIL_HOST')
EMAIL_PORT = os.getenv('EMAIL_PORT')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = True

# Slack integration -- override these in local_settings
SLACK_CLIENT_ID = None
SLACK_CLIENT_SECRET = None
SLACK_CLIENT_ID = "300370717905.301980969415"
SLACK_CLIENT_SECRET = "b52538015be1993899301ce5cc50cbc6"

# Pushover integration -- override these in local_settings
PUSHOVER_API_TOKEN = None
Expand Down
21 changes: 20 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
AfricastalkingGateway==2.0
asn1crypto==0.24.0
boto==2.48.0
cffi==1.11.4
cryptography==2.1.4
amqp==2.2.2
billiard==3.5.0.3
celery==4.1.0
certifi==2017.11.5
chardet==3.0.4
cssselect==1.0.3
cssutils==1.0.2
dj-database-url==0.4.2
Expand All @@ -12,15 +21,25 @@ future==0.16.0
futures==3.0.3
gunicorn==19.7.1
heroku==0.1.4
idna==2.6
kombu==4.1.0
lxml==4.1.1
mock==2.0.0
Naked==0.1.31
pbr==3.1.1
premailer==2.9.6
psycopg2==2.7.3.2
pycparser==2.18
pycrypto==2.6.1
PySocks==1.6.8
python-dateutil==1.5
pytz==2017.3
PyYAML==3.12
rcssmin==1.0.6
requests==2.9.1
requests==2.18.4
rjsmin==1.0.12
shellescape==3.4.1
six==1.11.0
urllib3==1.22
vine==1.1.4
whitenoise==3.3.1
Binary file added static/img/integrations/sms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion templates/front/channels.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
{% if ch.kind == "webhook" %} Webhook {% endif %}
{% if ch.kind == "slack" %} Slack {% endif %}
{% if ch.kind == "hipchat" %} HipChat {% endif %}
{% if ch.kind == "sms" %} SMS {% endif %}
{% if ch.kind == "pd" %} PagerDuty {% endif %}
{% if ch.kind == "po" %} Pushover {% endif %}
{% if ch.kind == "victorops" %} VictorOps {% endif %}
Expand Down Expand Up @@ -80,8 +81,10 @@
{% elif ch.kind == "pushbullet" %}
<span class="preposition">API key</span>
{{ ch.value }}
{% else %}
{% elif ch.kind == "sms" %}
{{ ch.value }}
{% else %}
{{ ch.value }}
{% endif %}
</td>
<td class="channels-num-checks">
Expand Down Expand Up @@ -170,6 +173,15 @@ <h2>HipChat</h2>

<a href="{% url 'hc-add-hipchat' %}" class="btn btn-primary">Add Integration</a>
</li>
<li>
<img src="{% static 'img/integrations/sms.png' %}"
class="icon" alt="SMS icon" />

<h2>SMS</h2>
<p>SMS to your phone.</p>

<a href="{% url 'hc-add-sms' %}" class="btn btn-primary">Add Integration</a>
</li>
<li>
<img src="{% static 'img/integrations/victorops.png' %}"
class="icon" alt="VictorOps icon" />
Expand Down
Loading

0 comments on commit f8e382e

Please sign in to comment.