mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Finished user control panel for tokens
This commit is contained in:
parent
d58a8ebba0
commit
4f6d2a8b71
@ -9,11 +9,21 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2 col-md-offset-2">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li{% ifequal active_tab "profile" %} class="active"{% endifequal %}><a href="{% url 'users:profile' %}">Profile</a></li>
|
||||
<li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}><a href="{% url 'users:change_password' %}">Change Password</a></li>
|
||||
<li{% ifequal active_tab "api_tokens" %} class="active"{% endifequal %}><a href="{% url 'users:api_tokens' %}">API Tokens</a></li>
|
||||
<li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}><a href="{% url 'users:userkey' %}">User Key</a></li>
|
||||
<li{% ifequal active_tab "recent_activity" %} class="active"{% endifequal %}><a href="{% url 'users:recent_activity' %}">Recent Activity</a></li>
|
||||
<li{% ifequal active_tab "profile" %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'users:profile' %}">Profile</a>
|
||||
</li>
|
||||
<li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'users:change_password' %}">Change Password</a>
|
||||
</li>
|
||||
<li{% ifequal active_tab "api_tokens" %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'users:token_list' %}">API Tokens</a>
|
||||
</li>
|
||||
<li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'users:userkey' %}">User Key</a>
|
||||
</li>
|
||||
<li{% ifequal active_tab "recent_activity" %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'users:recent_activity' %}">Recent Activity</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-6">
|
||||
|
@ -9,28 +9,36 @@
|
||||
{% for token in tokens %}
|
||||
<div class="panel panel-{% if token.is_expired %}danger{% else %}default{% endif %}">
|
||||
<div class="panel-heading">
|
||||
{% if token.is_expired %}
|
||||
<div class="pull-right">
|
||||
<span class="label label-danger">Expired</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'users:token_edit' pk=token.pk %}" class="btn btn-xs btn-warning">Edit</a>
|
||||
<a href="{% url 'users:token_delete' pk=token.pk %}" class="btn btn-xs btn-danger">Delete</a>
|
||||
</div>
|
||||
<i class="fa fa-key"></i> {{ token.key }}
|
||||
{% if token.is_expired %}
|
||||
<span class="label label-danger">Expired</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Created: {{ token.created|date }}
|
||||
<span title="{{ token.created }}">{{ token.created|date }}</span><br />
|
||||
<small class="text-muted">Created</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
Expires: {{ token.expires|default:"Never" }}
|
||||
{% if token.expires %}
|
||||
<span title="{{ token.expires }}">{{ token.expires|date }}</span><br />
|
||||
{% else %}
|
||||
<span>Never</span><br />
|
||||
{% endif %}
|
||||
<small class="text-muted">Expires</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
Write operations:
|
||||
{% if token.write_enabled %}
|
||||
<span class="label label-success">Enabled</span>
|
||||
{% else %}
|
||||
<span class="label label-danger">Disabled</span>
|
||||
{% endif %}
|
||||
{% endif %}<br />
|
||||
<small class="text-muted">Create/edit/delete operations</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if token.description %}
|
||||
@ -38,7 +46,13 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>You do not have any API tokens.</p>
|
||||
{% endfor %}
|
||||
<a href="{% url 'users:token_add' %}" class="btn btn-primary">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a token
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,6 +1,8 @@
|
||||
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
|
||||
from django import forms
|
||||
|
||||
from utilities.forms import BootstrapMixin
|
||||
from .models import Token
|
||||
|
||||
|
||||
class LoginForm(BootstrapMixin, AuthenticationForm):
|
||||
@ -14,3 +16,14 @@ class LoginForm(BootstrapMixin, AuthenticationForm):
|
||||
|
||||
class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
|
||||
pass
|
||||
|
||||
|
||||
class TokenForm(BootstrapMixin, forms.ModelForm):
|
||||
key = forms.CharField(required=False, help_text="If no key is provided, one will be generated automatically.")
|
||||
|
||||
class Meta:
|
||||
model = Token
|
||||
fields = ['key', 'write_enabled', 'expires', 'description']
|
||||
help_texts = {
|
||||
'expires': 'YYYY-MM-DD [HH:MM:SS]'
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-03-08 03:52
|
||||
# Generated by Django 1.10.6 on 2017-03-08 15:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -22,7 +23,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('expires', models.DateTimeField(blank=True, null=True)),
|
||||
('key', models.CharField(max_length=40, unique=True)),
|
||||
('key', models.CharField(max_length=40, unique=True, validators=[django.core.validators.MinLengthValidator(40)])),
|
||||
('write_enabled', models.BooleanField(default=True, help_text=b'Permit create/update/delete operations using this key')),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to=settings.AUTH_USER_MODEL)),
|
||||
|
@ -2,6 +2,7 @@ import binascii
|
||||
import os
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import MinLengthValidator
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils import timezone
|
||||
@ -16,7 +17,7 @@ class Token(models.Model):
|
||||
user = models.ForeignKey(User, related_name='tokens', on_delete=models.CASCADE)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
expires = models.DateTimeField(blank=True, null=True)
|
||||
key = models.CharField(max_length=40, unique=True)
|
||||
key = models.CharField(max_length=40, unique=True, validators=[MinLengthValidator(40)])
|
||||
write_enabled = models.BooleanField(default=True, help_text="Permit create/update/delete operations using this key")
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
|
||||
@ -24,7 +25,8 @@ class Token(models.Model):
|
||||
default_permissions = []
|
||||
|
||||
def __str__(self):
|
||||
return u"API key for {}".format(self.user)
|
||||
# Only display the last 24 bits of the token to avoid accidental exposure.
|
||||
return u"{} ({})".format(self.key[-6:], self.user)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.key:
|
||||
|
@ -8,7 +8,10 @@ urlpatterns = [
|
||||
# User profiles
|
||||
url(r'^profile/$', views.profile, name='profile'),
|
||||
url(r'^profile/password/$', views.change_password, name='change_password'),
|
||||
url(r'^profile/api-tokens/$', views.TokenList.as_view(), name='api_tokens'),
|
||||
url(r'^profile/api-tokens/$', views.TokenListView.as_view(), name='token_list'),
|
||||
url(r'^profile/api-tokens/add/$', views.TokenEditView.as_view(), name='token_add'),
|
||||
url(r'^profile/api-tokens/(?P<pk>\d+)/edit/$', views.TokenEditView.as_view(), name='token_edit'),
|
||||
url(r'^profile/api-tokens/(?P<pk>\d+)/delete/$', views.TokenDeleteView.as_view(), name='token_delete'),
|
||||
url(r'^profile/user-key/$', views.userkey, name='userkey'),
|
||||
url(r'^profile/user-key/edit/$', views.userkey_edit, name='userkey_edit'),
|
||||
url(r'^profile/recent-activity/$', views.recent_activity, name='recent_activity'),
|
||||
|
@ -4,13 +4,14 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.http import is_safe_url
|
||||
from django.views.generic import View
|
||||
|
||||
from secrets.forms import UserKeyForm
|
||||
from secrets.models import UserKey
|
||||
from .forms import LoginForm, PasswordChangeForm
|
||||
from utilities.forms import ConfirmationForm
|
||||
from .forms import LoginForm, PasswordChangeForm, TokenForm
|
||||
from .models import Token
|
||||
|
||||
|
||||
@ -136,7 +137,7 @@ def recent_activity(request):
|
||||
# API tokens
|
||||
#
|
||||
|
||||
class TokenList(LoginRequiredMixin, View):
|
||||
class TokenListView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request):
|
||||
|
||||
@ -146,3 +147,74 @@ class TokenList(LoginRequiredMixin, View):
|
||||
'tokens': tokens,
|
||||
'active_tab': 'api_tokens',
|
||||
})
|
||||
|
||||
|
||||
class TokenEditView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request, pk=None):
|
||||
|
||||
if pk is not None:
|
||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
||||
else:
|
||||
token = Token(user=request.user)
|
||||
|
||||
form = TokenForm(instance=token)
|
||||
|
||||
return render(request, 'utilities/obj_edit.html', {
|
||||
'obj': token,
|
||||
'obj_type': token._meta.verbose_name,
|
||||
'form': form,
|
||||
'return_url': reverse('users:token_list'),
|
||||
})
|
||||
|
||||
def post(self, request, pk=None):
|
||||
|
||||
if pk is not None:
|
||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
||||
form = TokenForm(request.POST, instance=token)
|
||||
else:
|
||||
form = TokenForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
token = form.save(commit=False)
|
||||
token.user = request.user
|
||||
token.save()
|
||||
|
||||
msg = "Token updated" if pk else "New token created"
|
||||
messages.success(request, msg)
|
||||
|
||||
return redirect('users:token_list')
|
||||
|
||||
|
||||
class TokenDeleteView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
||||
initial_data = {
|
||||
'return_url': reverse('users:token_list'),
|
||||
}
|
||||
form = ConfirmationForm(initial=initial_data)
|
||||
|
||||
return render(request, 'utilities/obj_delete.html', {
|
||||
'obj': token,
|
||||
'obj_type': token._meta.verbose_name,
|
||||
'form': form,
|
||||
'return_url': reverse('users:token_list'),
|
||||
})
|
||||
|
||||
def post(self, request, pk):
|
||||
|
||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
||||
form = ConfirmationForm(request.POST)
|
||||
if form.is_valid():
|
||||
token.delete()
|
||||
messages.success(request, "Token deleted")
|
||||
return redirect('users:token_list')
|
||||
|
||||
return render(request, 'utilities/obj_delete.html', {
|
||||
'obj': token,
|
||||
'obj_type': token._meta.verbose_name,
|
||||
'form': form,
|
||||
'return_url': reverse('users:token_list'),
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user