forked from GithubBackups/healthchecks
Set and revoke API key in Settings page.
This commit is contained in:
parent
6e621703fb
commit
5d2cc0b0fc
20
hc/accounts/migrations/0004_profile_api_key.py
Normal file
20
hc/accounts/migrations/0004_profile_api_key.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-02-16 12:14
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0003_profile_token'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='api_key',
|
||||||
|
field=models.CharField(blank=True, max_length=128),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,8 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@ -7,7 +11,6 @@ from django.core.urlresolvers import reverse
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from hc.lib import emails
|
from hc.lib import emails
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileManager(models.Manager):
|
class ProfileManager(models.Manager):
|
||||||
@ -28,6 +31,7 @@ class Profile(models.Model):
|
|||||||
reports_allowed = models.BooleanField(default=True)
|
reports_allowed = models.BooleanField(default=True)
|
||||||
ping_log_limit = models.IntegerField(default=100)
|
ping_log_limit = models.IntegerField(default=100)
|
||||||
token = models.CharField(max_length=128, blank=True)
|
token = models.CharField(max_length=128, blank=True)
|
||||||
|
api_key = models.CharField(max_length=128, blank=True)
|
||||||
|
|
||||||
objects = ProfileManager()
|
objects = ProfileManager()
|
||||||
|
|
||||||
@ -49,6 +53,10 @@ class Profile(models.Model):
|
|||||||
ctx = {"set_password_link": settings.SITE_ROOT + path}
|
ctx = {"set_password_link": settings.SITE_ROOT + path}
|
||||||
emails.set_password(self.user.email, ctx)
|
emails.set_password(self.user.email, ctx)
|
||||||
|
|
||||||
|
def set_api_key(self):
|
||||||
|
self.api_key = base64.urlsafe_b64encode(os.urandom(24))
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_report(self):
|
def send_report(self):
|
||||||
# reset next report date first:
|
# reset next report date first:
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
43
hc/accounts/tests/test_profile.py
Normal file
43
hc/accounts/tests/test_profile.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from django.core import mail
|
||||||
|
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from hc.accounts.models import Profile
|
||||||
|
|
||||||
|
|
||||||
|
class LoginTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def test_it_sends_set_password_link(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {"set_password": "1"}
|
||||||
|
r = self.client.post("/accounts/profile/", form)
|
||||||
|
assert r.status_code == 302
|
||||||
|
|
||||||
|
# profile.token should be set now
|
||||||
|
profile = Profile.objects.for_user(self.alice)
|
||||||
|
self.assertTrue(len(profile.token) > 10)
|
||||||
|
|
||||||
|
# And an email should have been sent
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
expected_subject = 'Set password on healthchecks.io'
|
||||||
|
self.assertEqual(mail.outbox[0].subject, expected_subject)
|
||||||
|
|
||||||
|
def test_it_creates_api_key(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {"create_api_key": "1"}
|
||||||
|
r = self.client.post("/accounts/profile/", form)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
profile = Profile.objects.for_user(self.alice)
|
||||||
|
self.assertTrue(len(profile.api_key) > 10)
|
||||||
|
|
||||||
|
def test_it_revokes_api_key(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {"revoke_api_key": "1"}
|
||||||
|
r = self.client.post("/accounts/profile/", form)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
profile = Profile.objects.for_user(self.alice)
|
||||||
|
self.assertEqual(profile.api_key, "")
|
@ -120,19 +120,31 @@ def check_token(request, username, token):
|
|||||||
def profile(request):
|
def profile(request):
|
||||||
profile = Profile.objects.for_user(request.user)
|
profile = Profile.objects.for_user(request.user)
|
||||||
|
|
||||||
|
show_api_key = False
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if "set_password" in request.POST:
|
if "set_password" in request.POST:
|
||||||
profile.send_set_password_link()
|
profile.send_set_password_link()
|
||||||
return redirect("hc-set-password-link-sent")
|
return redirect("hc-set-password-link-sent")
|
||||||
|
elif "create_api_key" in request.POST:
|
||||||
form = ReportSettingsForm(request.POST)
|
profile.set_api_key()
|
||||||
if form.is_valid():
|
show_api_key = True
|
||||||
profile.reports_allowed = form.cleaned_data["reports_allowed"]
|
messages.info(request, "The API key has been created!")
|
||||||
|
elif "revoke_api_key" in request.POST:
|
||||||
|
profile.api_key = ""
|
||||||
profile.save()
|
profile.save()
|
||||||
messages.info(request, "Your settings have been updated!")
|
messages.info(request, "The API key has been revoked!")
|
||||||
|
elif "show_api_key" in request.POST:
|
||||||
|
show_api_key = True
|
||||||
|
elif "update_reports_allowed" in request.POST:
|
||||||
|
form = ReportSettingsForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
profile.reports_allowed = form.cleaned_data["reports_allowed"]
|
||||||
|
profile.save()
|
||||||
|
messages.info(request, "Your settings have been updated!")
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
"profile": profile
|
"profile": profile,
|
||||||
|
"show_api_key": show_api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "accounts/profile.html", ctx)
|
return render(request, "accounts/profile.html", ctx)
|
||||||
|
20
hc/api/migrations/0025_auto_20160216_1214.py
Normal file
20
hc/api/migrations/0025_auto_20160216_1214.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-02-16 12:14
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0024_auto_20160203_2227'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channel',
|
||||||
|
name='kind',
|
||||||
|
field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('po', 'Pushover'), ('victorops', 'VictorOps')], max_length=20),
|
||||||
|
),
|
||||||
|
]
|
@ -165,8 +165,4 @@ $(function () {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
@ -33,6 +33,7 @@
|
|||||||
Each month send me a summary of my checks
|
Each month send me a summary of my checks
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
|
name="update_reports_allowed"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-default pull-right">Save</button>
|
class="btn btn-default pull-right">Save</button>
|
||||||
</form>
|
</form>
|
||||||
@ -47,15 +48,84 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<h2>Set Password</h2>
|
<h2>Set Password</h2>
|
||||||
Attach a password to your healthchecks.io account
|
Attach a password to your healthchecks.io account
|
||||||
<input type="hidden" name="set_password" value="1" />
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
value="set_password"
|
||||||
class="btn btn-default pull-right">Set Password</button>
|
class="btn btn-default pull-right">Set Password</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body settings-block">
|
||||||
|
<h2>API Access</h2>
|
||||||
|
{% if profile.api_key %}
|
||||||
|
{% if show_api_key %}
|
||||||
|
API key: <code>{{ profile.api_key }}</code>
|
||||||
|
<button
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#revoke-api-key-modal"
|
||||||
|
class="btn btn-danger pull-right">Revoke</button>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<span class="text-success glyphicon glyphicon-ok"></span>
|
||||||
|
API access is enabled.
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="show_api_key"
|
||||||
|
class="btn btn-default pull-right">Show API key</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
API access is disabled.
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="create_api_key"
|
||||||
|
class="btn btn-default pull-right">Create API key</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="revoke-api-key-modal" class="modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form id="revoke-api-key-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</span></button>
|
||||||
|
<h4 class="remove-check-title">Revoke API Key</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>You are about to revoke the current API key.</p>
|
||||||
|
<p>Afterwards, you can create a new API key, but there will
|
||||||
|
be <strong>no way of getting the current API
|
||||||
|
key back</strong>.
|
||||||
|
</p>
|
||||||
|
<p>Are you sure?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="revoke_api_key"
|
||||||
|
class="btn btn-danger">Revoke API Key</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user