mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Clean up display of tokens in views
This commit is contained in:
parent
08b965b55e
commit
9f4135a23a
@ -72,6 +72,9 @@ ADMINS = [
|
|||||||
# ('John Doe', 'jdoe@example.com'),
|
# ('John Doe', 'jdoe@example.com'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Permit the retrieval of API tokens after their creation.
|
||||||
|
ALLOW_TOKEN_RETRIEVAL = False
|
||||||
|
|
||||||
# Enable any desired validators for local account passwords below. For a list of included validators, please see the
|
# Enable any desired validators for local account passwords below. For a list of included validators, please see the
|
||||||
# Django documentation at https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation.
|
# Django documentation at https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation.
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
@ -224,7 +227,3 @@ TIME_FORMAT = 'g:i a'
|
|||||||
SHORT_TIME_FORMAT = 'H:i:s'
|
SHORT_TIME_FORMAT = 'H:i:s'
|
||||||
DATETIME_FORMAT = 'N j, Y g:i a'
|
DATETIME_FORMAT = 'N j, Y g:i a'
|
||||||
SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
|
SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
|
||||||
|
|
||||||
# Allow API Tokens to be viewed after creation. Before NetBox 3.4 the default was to allow viewing of the tokens
|
|
||||||
# so this flag was created for backwards compatability.
|
|
||||||
ALLOW_TOKEN_RETRIEVAL = False
|
|
||||||
|
@ -109,11 +109,9 @@ Context:
|
|||||||
<button type="submit" name="_create" class="btn btn-primary">
|
<button type="submit" name="_create" class="btn btn-primary">
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
{% if not disable_addanother %}
|
|
||||||
<button type="submit" name="_addanother" class="btn btn-outline-primary">
|
<button type="submit" name="_addanother" class="btn btn-outline-primary">
|
||||||
Create & Add Another
|
Create & Add Another
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
@ -6,31 +6,55 @@
|
|||||||
{% 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 %}
|
||||||
|
<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-token" data-clipboard-target="#token_id" title="Copy to clipboard">copy the token value</a> below and store it securely.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">Token Created</h5>
|
<h5 class="card-header">Token</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>The token has been created, you will need to copy the key as it will no longer be displayed for security purposes.</p>
|
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Key</td>
|
<th scope="row">Key</th>
|
||||||
<td>{{ object.key }}</td>
|
<td>
|
||||||
|
<div class="float-end">
|
||||||
|
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_id" title="Copy to clipboard">
|
||||||
|
<i class="mdi mdi-content-copy"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="token_id">{{ key }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Description</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">User</th>
|
||||||
|
<td>{{ object.user }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Created</th>
|
||||||
|
<td>{{ object.created|annotated_date }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Expires</th>
|
||||||
|
<td>
|
||||||
|
{% if object.expires %}
|
||||||
|
{{ object.expires|annotated_date }}
|
||||||
|
{% else %}
|
||||||
|
<span>Never</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% render_form form %}
|
|
||||||
<div class="row my-3">
|
|
||||||
<div class="col col-md-12 text-center">
|
|
||||||
<button type="submit" name="_addanother" class="btn btn-outline-primary">
|
|
||||||
Add Another
|
|
||||||
</button>
|
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
<div class="col col-md-12 text-center">
|
||||||
|
<a href="{% url 'users:token_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 %}
|
||||||
|
@ -121,13 +121,7 @@ class TokenForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
instance = getattr(self, 'instance', None)
|
|
||||||
if instance and instance.id and not settings.ALLOW_TOKEN_RETRIEVAL:
|
|
||||||
keyfield = self.fields['key']
|
|
||||||
keyfield.disabled = True
|
|
||||||
keyfield.required = False
|
|
||||||
keyfield.widget = forms.HiddenInput()
|
|
||||||
|
|
||||||
|
# Omit the key field if token retrieval is not permitted
|
||||||
class TokenViewForm(BootstrapMixin, forms.Form):
|
if self.instance.pk and not settings.ALLOW_TOKEN_RETRIEVAL:
|
||||||
view_token = forms.BooleanField(widget=forms.HiddenInput(), required=False)
|
del self.fields['key']
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
@ -230,12 +231,12 @@ class Token(models.Model):
|
|||||||
'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"',
|
'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# Only display the last 24 bits of the token to avoid accidental exposure.
|
return self.key if settings.ALLOW_TOKEN_RETRIEVAL else self.partial
|
||||||
return f"{self.description or self.key[-6:]} ({self.user})"
|
|
||||||
|
@property
|
||||||
|
def partial(self):
|
||||||
|
return f'**********************************{self.key[-6:]}' if self.key else ''
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.key:
|
if not self.key:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from django.conf import settings
|
|
||||||
from .models import Token
|
from .models import Token
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
|
|
||||||
@ -7,14 +6,16 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
TOKEN = """<samp><span id="token_{{ record.pk }}">{{ value }}</span></samp>"""
|
TOKEN = """<samp><span id="token_{{ record.pk }}">{{ record }}</span></samp>"""
|
||||||
|
|
||||||
ALLOWED_IPS = """{{ value|join:", " }}"""
|
ALLOWED_IPS = """{{ value|join:", " }}"""
|
||||||
|
|
||||||
COPY_BUTTON = """
|
COPY_BUTTON = """
|
||||||
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_{{ record.pk }}" title="Copy to clipboard">
|
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
|
||||||
<i class="mdi mdi-content-copy"></i>
|
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_{{ record.pk }}" title="Copy to clipboard">
|
||||||
</a>
|
<i class="mdi mdi-content-copy"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -41,9 +42,3 @@ class TokenTable(NetBoxTable):
|
|||||||
fields = (
|
fields = (
|
||||||
'pk', 'description', 'key', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
|
'pk', 'description', 'key', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_key(self, value):
|
|
||||||
if settings.ALLOW_TOKEN_RETRIEVAL:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return "****************************************"
|
|
||||||
|
@ -20,7 +20,7 @@ from extras.tables import ObjectChangeTable
|
|||||||
from netbox.authentication import get_auth_backend_display, get_saml_idps
|
from netbox.authentication import get_auth_backend_display, get_saml_idps
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from .forms import LoginForm, PasswordChangeForm, TokenForm, TokenViewForm, UserConfigForm
|
from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm
|
||||||
from .models import Token, UserConfig
|
from .models import Token, UserConfig
|
||||||
from .tables import TokenTable
|
from .tables import TokenTable
|
||||||
|
|
||||||
@ -261,7 +261,6 @@ class TokenEditView(LoginRequiredMixin, View):
|
|||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:token_list'),
|
||||||
'disable_addanother': not settings.ALLOW_TOKEN_RETRIEVAL
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, pk=None):
|
def post(self, request, pk=None):
|
||||||
@ -288,10 +287,8 @@ class TokenEditView(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:
|
||||||
form = TokenViewForm(initial={'view_token': True})
|
|
||||||
return render(request, 'users/api_token.html', {
|
return render(request, 'users/api_token.html', {
|
||||||
'object': token,
|
'object': token,
|
||||||
'form': form,
|
|
||||||
'key': token.key,
|
'key': token.key,
|
||||||
'return_url': reverse('users:token_list'),
|
'return_url': reverse('users:token_list'),
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user