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 django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import User
|
||||
@ -7,7 +11,6 @@ from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from hc.lib import emails
|
||||
import uuid
|
||||
|
||||
|
||||
class ProfileManager(models.Manager):
|
||||
@ -28,6 +31,7 @@ class Profile(models.Model):
|
||||
reports_allowed = models.BooleanField(default=True)
|
||||
ping_log_limit = models.IntegerField(default=100)
|
||||
token = models.CharField(max_length=128, blank=True)
|
||||
api_key = models.CharField(max_length=128, blank=True)
|
||||
|
||||
objects = ProfileManager()
|
||||
|
||||
@ -49,6 +53,10 @@ class Profile(models.Model):
|
||||
ctx = {"set_password_link": settings.SITE_ROOT + path}
|
||||
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):
|
||||
# reset next report date first:
|
||||
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):
|
||||
profile = Profile.objects.for_user(request.user)
|
||||
|
||||
show_api_key = False
|
||||
if request.method == "POST":
|
||||
if "set_password" in request.POST:
|
||||
profile.send_set_password_link()
|
||||
return redirect("hc-set-password-link-sent")
|
||||
|
||||
form = ReportSettingsForm(request.POST)
|
||||
if form.is_valid():
|
||||
profile.reports_allowed = form.cleaned_data["reports_allowed"]
|
||||
elif "create_api_key" in request.POST:
|
||||
profile.set_api_key()
|
||||
show_api_key = True
|
||||
messages.info(request, "The API key has been created!")
|
||||
elif "revoke_api_key" in request.POST:
|
||||
profile.api_key = ""
|
||||
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 = {
|
||||
"profile": profile
|
||||
"profile": profile,
|
||||
"show_api_key": show_api_key
|
||||
}
|
||||
|
||||
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
|
||||
</label>
|
||||
<button
|
||||
name="update_reports_allowed"
|
||||
type="submit"
|
||||
class="btn btn-default pull-right">Save</button>
|
||||
</form>
|
||||
@ -47,15 +48,84 @@
|
||||
{% csrf_token %}
|
||||
<h2>Set Password</h2>
|
||||
Attach a password to your healthchecks.io account
|
||||
<input type="hidden" name="set_password" value="1" />
|
||||
<button
|
||||
type="submit"
|
||||
value="set_password"
|
||||
class="btn btn-default pull-right">Set Password</button>
|
||||
</form>
|
||||
</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 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 %}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user