diff --git a/src/backend/InvenTree/InvenTree/metadata.py b/src/backend/InvenTree/InvenTree/metadata.py
index c45c20e832..86cbc91229 100644
--- a/src/backend/InvenTree/InvenTree/metadata.py
+++ b/src/backend/InvenTree/InvenTree/metadata.py
@@ -5,7 +5,7 @@ from django.http import Http404
from django.urls import reverse
import structlog
-from rest_framework import exceptions, serializers
+from rest_framework import exceptions, permissions, serializers
from rest_framework.fields import empty
from rest_framework.metadata import SimpleMetadata
from rest_framework.request import clone_request
@@ -131,10 +131,26 @@ class InvenTreeMetadata(SimpleMetadata):
# Remove any HTTP methods that the user does not have permission for
for method, permission in rolemap.items():
+ # general model / role permission
result = check_user_permission(user, self.model, permission) or (
role_required and check_user_role(user, role_required, permission)
)
+ # check if simple IsAuthenticated permission class is used
+ if not result:
+ result = (
+ view.permission_classes
+ and len(view.permission_classes) == 1
+ and any(
+ perm
+ in [
+ permissions.IsAuthenticated,
+ InvenTree.permissions.IsAuthenticatedOrReadScope,
+ ]
+ for perm in view.permission_classes
+ )
+ )
+
if method in actions and not result:
del actions[method]
diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py
index 42201ed095..37434fbcff 100644
--- a/src/backend/InvenTree/users/api.py
+++ b/src/backend/InvenTree/users/api.py
@@ -367,49 +367,44 @@ class GetAuthToken(GenericAPIView):
- Existing tokens are *never* exposed again via the API
- Once the token is provided, it can be used for auth until it expires
"""
- if request.user.is_authenticated:
- user = request.user
- name = request.query_params.get('name', '')
+ if not request.user.is_authenticated:
+ raise exceptions.NotAuthenticated() # pragma: no cover
- name = ApiToken.sanitize_name(name)
+ user = request.user
+ name = request.query_params.get('name', '')
- today = datetime.date.today()
+ name = ApiToken.sanitize_name(name)
- # Find existing token, which has not expired
- token = ApiToken.objects.filter(
- user=user, name=name, revoked=False, expiry__gte=today
- ).first()
+ today = datetime.date.today()
- if not token:
- # User is authenticated, and requesting a token against the provided name.
- token = ApiToken.objects.create(user=request.user, name=name)
+ # Find existing token, which has not expired
+ token = ApiToken.objects.filter(
+ user=user, name=name, revoked=False, expiry__gte=today
+ ).first()
- logger.info(
- "Created new API token for user '%s' (name='%s')",
- user.username,
- name,
- )
+ if not token:
+ # User is authenticated, and requesting a token against the provided name.
+ token = ApiToken.objects.create(user=request.user, name=name)
+
+ logger.info(
+ "Created new API token for user '%s' (name='%s')", user.username, name
+ )
# Add some metadata about the request
- token.set_metadata('user_agent', request.headers.get('user-agent', ''))
- token.set_metadata('remote_addr', request.META.get('REMOTE_ADDR', ''))
- token.set_metadata('remote_host', request.META.get('REMOTE_HOST', ''))
- token.set_metadata('remote_user', request.META.get('REMOTE_USER', ''))
- token.set_metadata('server_name', request.META.get('SERVER_NAME', ''))
- token.set_metadata('server_port', request.META.get('SERVER_PORT', ''))
+ token.set_metadata('user_agent', request.headers.get('user-agent', ''))
+ token.set_metadata('remote_addr', request.META.get('REMOTE_ADDR', ''))
+ token.set_metadata('remote_host', request.META.get('REMOTE_HOST', ''))
+ token.set_metadata('remote_user', request.META.get('REMOTE_USER', ''))
+ token.set_metadata('server_name', request.META.get('SERVER_NAME', ''))
+ token.set_metadata('server_port', request.META.get('SERVER_PORT', ''))
- data = {'token': token.key, 'name': token.name, 'expiry': token.expiry}
+ data = {'token': token.key, 'name': token.name, 'expiry': token.expiry}
- # Ensure that the users session is logged in
- if not get_user(request).is_authenticated:
- login(
- request, user, backend='django.contrib.auth.backends.ModelBackend'
- )
+ # Ensure that the users session is logged in
+ if not get_user(request).is_authenticated:
+ login(request, user, backend='django.contrib.auth.backends.ModelBackend')
- return Response(data)
-
- else:
- raise exceptions.NotAuthenticated() # pragma: no cover
+ return Response(data)
class TokenMixin:
diff --git a/src/frontend/src/tables/settings/ApiTokenTable.tsx b/src/frontend/src/tables/settings/ApiTokenTable.tsx
index 9736dded12..5a70d1ec66 100644
--- a/src/frontend/src/tables/settings/ApiTokenTable.tsx
+++ b/src/frontend/src/tables/settings/ApiTokenTable.tsx
@@ -26,7 +26,8 @@ export function ApiTokenTable({
const [opened, { open, close }] = useDisclosure(false);
const generateToken = useCreateApiFormModal({
- url: ApiEndpoints.user_tokens,
+ url: ApiEndpoints.user_token,
+ method: 'GET',
title: t`Generate Token`,
fields: { name: {} },
successMessage: t`Token generated`,
@@ -178,6 +179,7 @@ export function ApiTokenTable({
onClose={close}
title={{t`Token`}}
centered
+ data-testid='generated-api-token'
>
diff --git a/src/frontend/tests/pui_settings.spec.ts b/src/frontend/tests/pui_settings.spec.ts
index f30415af2b..abc50732ce 100644
--- a/src/frontend/tests/pui_settings.spec.ts
+++ b/src/frontend/tests/pui_settings.spec.ts
@@ -543,3 +543,27 @@ async function testColorPicker(page, ref: string) {
await page.mouse.click(box.x + box.width / 2, box.y + box.height + 25);
await page.getByText('Color Mode').click();
}
+
+test('Settings - Auth - Tokens', async ({ browser }) => {
+ const page = await doCachedLogin(browser, {
+ username: 'allaccess',
+ password: 'nolimits',
+ url: 'settings/user/'
+ });
+
+ await page.getByRole('tab', { name: 'Security' }).click();
+ await page.getByRole('button', { name: 'Access Tokens' }).click();
+ await page
+ .getByRole('button', { name: 'action-button-generate-token' })
+ .click();
+ await page
+ .getByRole('textbox', { name: 'text-field-name' })
+ .fill('testtoken');
+ await page.getByRole('button', { name: 'Submit', exact: true }).click();
+ await page.getByText('Tokens are only shown once').waitFor();
+ await page
+ .getByTestId('generated-api-token')
+ .locator('.mantine-CloseButton-root')
+ .click();
+ await page.getByRole('cell', { name: 'testtoken' }).waitFor();
+});