Closes #20917: Show example API usage for tokens (#20918)
Some checks are pending
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run

This commit is contained in:
Jeremy Stretch 2025-12-03 18:37:40 -05:00 committed by GitHub
parent 502b33b144
commit 7bca9f5d6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 64 additions and 69 deletions

View File

@ -25,10 +25,12 @@ from extras.models import Bookmark
from extras.tables import BookmarkTable, NotificationTable, SubscriptionTable from extras.tables import BookmarkTable, NotificationTable, SubscriptionTable
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 netbox.ui import layout
from netbox.views import generic from netbox.views import generic
from users import forms from users import forms
from users.models import UserConfig from users.models import UserConfig
from users.tables import TokenTable from users.tables import TokenTable
from users.ui.panels import TokenExamplePanel, TokenPanel
from utilities.request import safe_for_redirect from utilities.request import safe_for_redirect
from utilities.string import remove_linebreaks from utilities.string import remove_linebreaks
from utilities.views import register_model_view from utilities.views import register_model_view
@ -342,12 +344,21 @@ class UserTokenListView(LoginRequiredMixin, View):
@register_model_view(UserToken) @register_model_view(UserToken)
class UserTokenView(LoginRequiredMixin, View): class UserTokenView(LoginRequiredMixin, View):
layout = layout.SimpleLayout(
left_panels=[
TokenPanel(),
],
right_panels=[
TokenExamplePanel(),
],
)
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(UserToken.objects.filter(user=request.user), pk=pk)
return render(request, 'account/token.html', { return render(request, 'account/token.html', {
'object': token, 'object': token,
'layout': self.layout,
}) })

View File

@ -0,0 +1,9 @@
{% extends 'ui/panels/_base.html' %}
{% block panel_content %}
<div id="token-example" class="card-body font-monospace">curl -X GET \<br />
-H "Authorization: {{ object.get_auth_header_prefix }}<mark>&lt;TOKEN&gt;</mark>" \<br />
-H "Content-Type: application/json" \<br />
-H "Accept: application/json; indent=4" \<br />
{{ request.scheme }}://{{ request.get_host }}{% url "api-status" %}</div>
{% endblock panel_content %}

View File

@ -1,73 +1,4 @@
{% extends 'generic/object.html' %} {% extends 'generic/object.html' %}
{% load helpers %}
{% load i18n %} {% load i18n %}
{% load render_table from django_tables2 %}
{% block title %}{% trans "Token" %} {{ object }}{% endblock %} {% block title %}{% trans "Token" %} {{ object }}{% endblock %}
{% block subtitle %}{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Token" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Version" %}</th>
<td>{{ object.version }}</td>
</tr>
{% if object.version == 1 %}
<tr>
<th scope="row">{% trans "Token" %}</th>
<td>{{ object.partial }}</td>
</tr>
{% else %}
<tr>
<th scope="row">{% trans "Key" %}</th>
<td>{{ object }}</td>
</tr>
<tr>
<th scope="row">{% trans "Pepper ID" %}</th>
<td>{{ object.pepper_id }}</td>
</tr>
{% endif %}
<tr>
<th scope="row">{% trans "User" %}</th>
<td>
<a href="{% url 'users:user' pk=object.user.pk %}">{{ object.user }}</a>
</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Enabled" %}</th>
<td>{% checkmark object.enabled %}</td>
</tr>
<tr>
<th scope="row">{% trans "Write enabled" %}</th>
<td>{% checkmark object.write_enabled %}</td>
</tr>
<tr>
<th scope="row">{% trans "Created" %}</th>
<td>{{ object.created|isodatetime }}</td>
</tr>
<tr>
<th scope="row">{% trans "Expires" %}</th>
<td>{{ object.expires|isodatetime|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Last used" %}</th>
<td>{{ object.last_used|isodatetime|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Allowed IPs" %}</th>
<td>{{ object.allowed_ips|join:", "|placeholder }}</td>
</tr>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -201,6 +201,15 @@ class Token(models.Model):
""" """
return self.enabled and not self.is_expired return self.enabled and not self.is_expired
def get_auth_header_prefix(self):
"""
Return the HTTP Authorization header prefix for this token.
"""
if self.v1:
return 'Token '
if self.v2:
return f'Bearer {TOKEN_PREFIX}{self.key}.'
def clean(self): def clean(self):
super().clean() super().clean()

View File

25
netbox/users/ui/panels.py Normal file
View File

@ -0,0 +1,25 @@
from django.utils.translation import gettext_lazy as _
from netbox.ui import actions, attrs, panels
class TokenPanel(panels.ObjectAttributesPanel):
version = attrs.NumericAttr('version')
key = attrs.TextAttr('key')
token = attrs.TextAttr('partial')
pepper_id = attrs.NumericAttr('pepper_id')
user = attrs.RelatedObjectAttr('user', linkify=True)
description = attrs.TextAttr('description')
enabled = attrs.BooleanAttr('enabled')
write_enabled = attrs.BooleanAttr('write_enabled')
expires = attrs.TextAttr('expires')
last_used = attrs.TextAttr('last_used')
allowed_ips = attrs.TextAttr('allowed_ips')
class TokenExamplePanel(panels.Panel):
template_name = 'users/panels/token_example.html'
title = _('Example Usage')
actions = [
actions.CopyContent('token-example')
]

View File

@ -3,7 +3,9 @@ from django.db.models import Count
from core.models import ObjectChange from core.models import ObjectChange
from core.tables import ObjectChangeTable from core.tables import ObjectChangeTable
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
from netbox.ui import layout
from netbox.views import generic from netbox.views import generic
from users.ui import panels
from utilities.query import count_related from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables from . import filtersets, forms, tables
@ -26,6 +28,14 @@ class TokenListView(generic.ObjectListView):
@register_model_view(Token) @register_model_view(Token)
class TokenView(generic.ObjectView): class TokenView(generic.ObjectView):
queryset = Token.objects.all() queryset = Token.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.TokenPanel(),
],
right_panels=[
panels.TokenExamplePanel(),
],
)
@register_model_view(Token, 'add', detail=False) @register_model_view(Token, 'add', detail=False)