mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Reintroduce UserToken proxy model for account views
This commit is contained in:
parent
5064bd4574
commit
b57c101ee4
@ -4,13 +4,13 @@
|
|||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'users:usertoken_list' %}">Tokens</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'users:usertoken_list' %}">My API Tokens</a></li>
|
||||||
{% endblock breadcrumbs %}
|
{% endblock breadcrumbs %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% if not settings.ALLOW_TOKEN_RETRIEVAL %}
|
{% if key and not settings.ALLOW_TOKEN_RETRIEVAL %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
<i class="mdi mdi-alert"></i> Tokens cannot be retrieved at a later time. You must <a href="#" class="copy-content" data-clipboard-target="#token_id" title="Copy to clipboard">copy the token value</a> below and store it securely.
|
<i class="mdi mdi-alert"></i> Tokens cannot be retrieved at a later time. You must <a href="#" class="copy-content" data-clipboard-target="#token_id" title="Copy to clipboard">copy the token value</a> below and store it securely.
|
||||||
</div>
|
</div>
|
||||||
@ -19,15 +19,17 @@
|
|||||||
<h5 class="card-header">Token</h5>
|
<h5 class="card-header">Token</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
{% if key %}
|
||||||
<th scope="row">Key</th>
|
<tr>
|
||||||
<td>
|
<th scope="row">Key</th>
|
||||||
<div class="float-end">
|
<td>
|
||||||
{% copy_content "token_id" %}
|
<div class="float-end">
|
||||||
</div>
|
{% copy_content "token_id" %}
|
||||||
<div id="token_id">{{ key }}</div>
|
</div>
|
||||||
</td>
|
<div id="token_id">{{ key }}</div>
|
||||||
</tr>
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Description</th>
|
<th scope="row">Description</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
@ -53,10 +55,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-12 text-center">
|
|
||||||
<a href="{% url 'users:usertoken_add' %}" class="btn btn-outline-primary">Add Another</a>
|
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block title %}API Tokens{% endblock %}
|
{% block title %}My API Tokens{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
24
netbox/users/migrations/0005_usertoken.py
Normal file
24
netbox/users/migrations/0005_usertoken.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-07-25 15:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0004_netboxgroup_netboxuser'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserToken',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('users.token',),
|
||||||
|
),
|
||||||
|
]
|
@ -321,6 +321,17 @@ class Token(models.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class UserToken(Token):
|
||||||
|
"""
|
||||||
|
Proxy model for users to manage their own API tokens.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('users:usertoken', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Permissions
|
# Permissions
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from users.models import NetBoxGroup, NetBoxUser, ObjectPermission, Token
|
from users.models import NetBoxGroup, NetBoxUser, ObjectPermission, Token, UserToken
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupTable',
|
'GroupTable',
|
||||||
@ -30,7 +30,7 @@ class TokenActionsColumn(columns.ActionsColumn):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TokenTable(NetBoxTable):
|
class UserTokenTable(NetBoxTable):
|
||||||
key = columns.TemplateColumn(
|
key = columns.TemplateColumn(
|
||||||
verbose_name='Key',
|
verbose_name='Key',
|
||||||
template_code=TOKEN,
|
template_code=TOKEN,
|
||||||
@ -57,6 +57,15 @@ class TokenTable(NetBoxTable):
|
|||||||
extra_buttons=COPY_BUTTON
|
extra_buttons=COPY_BUTTON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = UserToken
|
||||||
|
fields = [
|
||||||
|
'pk', 'id', 'key', 'description', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TokenTable(UserTokenTable):
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Token
|
model = Token
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -13,8 +13,7 @@ urlpatterns = [
|
|||||||
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
|
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
|
||||||
path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
|
path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
|
||||||
path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
|
path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
|
||||||
path('api-tokens/<int:pk>/edit/', views.UserTokenEditView.as_view(), name='usertoken_edit'),
|
path('api-tokens/<int:pk>/', include(get_model_urls('users', 'usertoken'))),
|
||||||
path('api-tokens/<int:pk>/delete/', views.UserTokenDeleteView.as_view(), name='usertoken_delete'),
|
|
||||||
|
|
||||||
# Tokens
|
# Tokens
|
||||||
path('tokens/', views.TokenListView.as_view(), name='token_list'),
|
path('tokens/', views.TokenListView.as_view(), name='token_list'),
|
||||||
|
@ -24,7 +24,7 @@ from netbox.views import generic
|
|||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.views import register_model_view
|
from utilities.views import register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import Token, UserConfig, NetBoxGroup, NetBoxUser, ObjectPermission
|
from .models import NetBoxGroup, NetBoxUser, ObjectPermission, Token, UserConfig, UserToken
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -249,52 +249,61 @@ class BookmarkListView(LoginRequiredMixin, generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# API tokens
|
# User views for token management
|
||||||
#
|
#
|
||||||
|
|
||||||
class UserTokenListView(LoginRequiredMixin, View):
|
class UserTokenListView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
tokens = UserToken.objects.filter(user=request.user)
|
||||||
tokens = Token.objects.filter(user=request.user)
|
table = tables.UserTokenTable(tokens)
|
||||||
table = tables.TokenTable(tokens)
|
|
||||||
table.configure(request)
|
table.configure(request)
|
||||||
|
|
||||||
return render(request, 'users/account/api_tokens.html', {
|
return render(request, 'users/account/usertoken_list.html', {
|
||||||
'tokens': tokens,
|
'tokens': tokens,
|
||||||
'active_tab': 'api-tokens',
|
'active_tab': 'api-tokens',
|
||||||
'table': table,
|
'table': table,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(UserToken)
|
||||||
|
class UserTokenView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
token = get_object_or_404(UserToken, pk=pk)
|
||||||
|
key = token.key if settings.ALLOW_TOKEN_RETRIEVAL else None
|
||||||
|
|
||||||
|
return render(request, 'users/account/usertoken.html', {
|
||||||
|
'object': token,
|
||||||
|
'key': key,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(UserToken, 'edit')
|
||||||
class UserTokenEditView(LoginRequiredMixin, View):
|
class UserTokenEditView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
def get(self, request, pk=None):
|
def get(self, request, pk=None):
|
||||||
|
|
||||||
if pk:
|
if pk:
|
||||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||||
else:
|
else:
|
||||||
token = Token(user=request.user)
|
token = UserToken(user=request.user)
|
||||||
|
|
||||||
form = forms.UserTokenForm(instance=token)
|
form = forms.UserTokenForm(instance=token)
|
||||||
|
|
||||||
return render(request, 'generic/object_edit.html', {
|
return render(request, 'generic/object_edit.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:usertoken_list'),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, pk=None):
|
def post(self, request, pk=None):
|
||||||
|
|
||||||
if pk:
|
if pk:
|
||||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||||
form = forms.UserTokenForm(request.POST, instance=token)
|
form = forms.UserTokenForm(request.POST, instance=token)
|
||||||
else:
|
else:
|
||||||
token = Token(user=request.user)
|
token = UserToken(user=request.user)
|
||||||
form = forms.UserTokenForm(request.POST)
|
form = forms.UserTokenForm(request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
token = form.save(commit=False)
|
token = form.save(commit=False)
|
||||||
token.user = request.user
|
token.user = request.user
|
||||||
token.save()
|
token.save()
|
||||||
@ -303,7 +312,7 @@ class UserTokenEditView(LoginRequiredMixin, View):
|
|||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|
||||||
if not pk and not settings.ALLOW_TOKEN_RETRIEVAL:
|
if not pk and not settings.ALLOW_TOKEN_RETRIEVAL:
|
||||||
return render(request, 'users/account/api_token.html', {
|
return render(request, 'users/account/usertoken.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'key': token.key,
|
'key': token.key,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:token_list'),
|
||||||
@ -311,45 +320,41 @@ class UserTokenEditView(LoginRequiredMixin, View):
|
|||||||
elif '_addanother' in request.POST:
|
elif '_addanother' in request.POST:
|
||||||
return redirect(request.path)
|
return redirect(request.path)
|
||||||
else:
|
else:
|
||||||
return redirect('users:token_list')
|
return redirect('users:usertoken_list')
|
||||||
|
|
||||||
return render(request, 'generic/object_edit.html', {
|
return render(request, 'generic/object_edit.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:usertoken_list'),
|
||||||
'disable_addanother': not settings.ALLOW_TOKEN_RETRIEVAL
|
'disable_addanother': not settings.ALLOW_TOKEN_RETRIEVAL
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(UserToken, 'delete')
|
||||||
class UserTokenDeleteView(LoginRequiredMixin, View):
|
class UserTokenDeleteView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=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, 'generic/object_delete.html', {
|
return render(request, 'generic/object_delete.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
'form': ConfirmationForm(),
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:usertoken_list'),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
|
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
|
||||||
form = ConfirmationForm(request.POST)
|
form = ConfirmationForm(request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
token.delete()
|
token.delete()
|
||||||
messages.success(request, "Token deleted")
|
messages.success(request, "Token deleted")
|
||||||
return redirect('users:token_list')
|
return redirect('users:usertoken_list')
|
||||||
|
|
||||||
return render(request, 'generic/object_delete.html', {
|
return render(request, 'generic/object_delete.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:usertoken_list'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -368,9 +373,6 @@ class TokenListView(generic.ObjectListView):
|
|||||||
class TokenView(generic.ObjectView):
|
class TokenView(generic.ObjectView):
|
||||||
queryset = Token.objects.all()
|
queryset = Token.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Token, 'edit')
|
@register_model_view(Token, 'edit')
|
||||||
class TokenEditView(generic.ObjectEditView):
|
class TokenEditView(generic.ObjectEditView):
|
||||||
|
Loading…
Reference in New Issue
Block a user