mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-05 06:46:25 -06:00
* Introduce the DEFAULT_PERMISSIONS config parameter * Establish default permissions for user token management
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
from django.urls import include, path
|
||||
from django.urls import path
|
||||
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
|
||||
app_name = 'account'
|
||||
@@ -13,6 +12,8 @@ urlpatterns = [
|
||||
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
|
||||
path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
|
||||
path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
|
||||
path('api-tokens/<int:pk>/', include(get_model_urls('users', 'usertoken'))),
|
||||
path('api-tokens/<int:pk>/', views.UserTokenView.as_view(), name='usertoken'),
|
||||
path('api-tokens/<int:pk>/edit/', views.UserTokenEditView.as_view(), name='usertoken_edit'),
|
||||
path('api-tokens/<int:pk>/delete/', views.UserTokenDeleteView.as_view(), name='usertoken_delete'),
|
||||
|
||||
]
|
||||
|
||||
@@ -49,21 +49,10 @@ class GroupViewSet(NetBoxModelViewSet):
|
||||
#
|
||||
|
||||
class TokenViewSet(NetBoxModelViewSet):
|
||||
queryset = RestrictedQuerySet(model=Token).prefetch_related('user')
|
||||
queryset = Token.objects.prefetch_related('user')
|
||||
serializer_class = serializers.TokenSerializer
|
||||
filterset_class = filtersets.TokenFilterSet
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Limit the non-superusers to their own Tokens.
|
||||
"""
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.is_authenticated:
|
||||
return queryset.none()
|
||||
if self.request.user.is_superuser:
|
||||
return queryset
|
||||
return queryset.filter(user=self.request.user)
|
||||
|
||||
|
||||
class TokenProvisionView(APIView):
|
||||
"""
|
||||
|
||||
@@ -23,14 +23,6 @@ COPY_BUTTON = """
|
||||
"""
|
||||
|
||||
|
||||
class TokenActionsColumn(columns.ActionsColumn):
|
||||
# Subclass ActionsColumn to disregard permissions for edit & delete buttons
|
||||
actions = {
|
||||
'edit': columns.ActionsItem('Edit', 'pencil', None, 'warning'),
|
||||
'delete': columns.ActionsItem('Delete', 'trash-can-outline', None, 'danger'),
|
||||
}
|
||||
|
||||
|
||||
class UserTokenTable(NetBoxTable):
|
||||
"""
|
||||
Table for users to manager their own API tokens under account views.
|
||||
@@ -55,7 +47,8 @@ class UserTokenTable(NetBoxTable):
|
||||
verbose_name=_('Allowed IPs'),
|
||||
template_code=ALLOWED_IPS
|
||||
)
|
||||
actions = TokenActionsColumn(
|
||||
# TODO: Fix permissions evaluation & viewname resolution
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete'),
|
||||
extra_buttons=COPY_BUTTON
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
|
||||
from users.models import ObjectPermission, Token
|
||||
from utilities.testing import APIViewTestCases, APITestCase
|
||||
from utilities.testing import APIViewTestCases, APITestCase, create_test_user
|
||||
from utilities.utils import deepmerge
|
||||
|
||||
|
||||
@@ -105,24 +105,35 @@ class TokenTest(
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Apply grant_token permission to enable the creation of Tokens for other Users
|
||||
self.add_permissions('users.grant_token')
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
users = (
|
||||
create_test_user('User 1'),
|
||||
create_test_user('User 2'),
|
||||
create_test_user('User 3'),
|
||||
)
|
||||
|
||||
tokens = (
|
||||
# We already start with one Token, created by the test class
|
||||
Token(user=self.user),
|
||||
Token(user=self.user),
|
||||
Token(user=users[0]),
|
||||
Token(user=users[1]),
|
||||
Token(user=users[2]),
|
||||
)
|
||||
# Use save() instead of bulk_create() to ensure keys get automatically generated
|
||||
for token in tokens:
|
||||
token.save()
|
||||
|
||||
self.create_data = [
|
||||
cls.create_data = [
|
||||
{
|
||||
'user': self.user.pk,
|
||||
'user': users[0].pk,
|
||||
},
|
||||
{
|
||||
'user': self.user.pk,
|
||||
'user': users[1].pk,
|
||||
},
|
||||
{
|
||||
'user': self.user.pk,
|
||||
'user': users[2].pk,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -161,6 +172,9 @@ class TokenTest(
|
||||
"""
|
||||
Test provisioning a Token for a different User with & without the grant_token permission.
|
||||
"""
|
||||
# Clear grant_token permission assigned by setUpTestData
|
||||
ObjectPermission.objects.filter(users=self.user).delete()
|
||||
|
||||
self.add_permissions('users.add_token')
|
||||
user2 = User.objects.create_user(username='testuser2')
|
||||
data = {
|
||||
|
||||
+11
-74
@@ -279,83 +279,20 @@ class UserTokenView(LoginRequiredMixin, View):
|
||||
})
|
||||
|
||||
|
||||
@register_model_view(UserToken, 'edit')
|
||||
class UserTokenEditView(LoginRequiredMixin, View):
|
||||
class UserTokenEditView(generic.ObjectEditView):
|
||||
queryset = UserToken.objects.all()
|
||||
form = forms.UserTokenForm
|
||||
default_return_url = 'account:usertoken_list'
|
||||
|
||||
def get(self, request, pk=None):
|
||||
if pk:
|
||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||
else:
|
||||
token = UserToken(user=request.user)
|
||||
form = forms.UserTokenForm(instance=token)
|
||||
|
||||
return render(request, 'generic/object_edit.html', {
|
||||
'object': token,
|
||||
'form': form,
|
||||
'return_url': reverse('account:usertoken_list'),
|
||||
})
|
||||
|
||||
def post(self, request, pk=None):
|
||||
if pk:
|
||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||
form = forms.UserTokenForm(request.POST, instance=token)
|
||||
else:
|
||||
token = UserToken(user=request.user)
|
||||
form = forms.UserTokenForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
token = form.save(commit=False)
|
||||
token.user = request.user
|
||||
token.save()
|
||||
|
||||
msg = f"Modified token {token}" if pk else f"Created token {token}"
|
||||
messages.success(request, msg)
|
||||
|
||||
if not pk and not settings.ALLOW_TOKEN_RETRIEVAL:
|
||||
return render(request, 'users/account/token.html', {
|
||||
'object': token,
|
||||
'key': token.key,
|
||||
'return_url': reverse('users:token_list'),
|
||||
})
|
||||
elif '_addanother' in request.POST:
|
||||
return redirect(request.path)
|
||||
else:
|
||||
return redirect('account:usertoken_list')
|
||||
|
||||
return render(request, 'generic/object_edit.html', {
|
||||
'object': token,
|
||||
'form': form,
|
||||
'return_url': reverse('account:usertoken_list'),
|
||||
'disable_addanother': not settings.ALLOW_TOKEN_RETRIEVAL
|
||||
})
|
||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||
if not obj.pk:
|
||||
obj.user = request.user
|
||||
return obj
|
||||
|
||||
|
||||
@register_model_view(UserToken, 'delete')
|
||||
class UserTokenDeleteView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request, pk):
|
||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||
|
||||
return render(request, 'generic/object_delete.html', {
|
||||
'object': token,
|
||||
'form': ConfirmationForm(),
|
||||
'return_url': reverse('account:usertoken_list'),
|
||||
})
|
||||
|
||||
def post(self, request, pk):
|
||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||
form = ConfirmationForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
token.delete()
|
||||
messages.success(request, "Token deleted")
|
||||
return redirect('account:usertoken_list')
|
||||
|
||||
return render(request, 'generic/object_delete.html', {
|
||||
'object': token,
|
||||
'form': form,
|
||||
'return_url': reverse('account:usertoken_list'),
|
||||
})
|
||||
class UserTokenDeleteView(generic.ObjectDeleteView):
|
||||
queryset = UserToken.objects.all()
|
||||
default_return_url = 'account:usertoken_list'
|
||||
|
||||
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user