mirror of
https://github.com/inventree/InvenTree.git
synced 2026-03-21 11:44:42 +00:00
fix: low-privilege user token creation (#11492)
* [bug] Users cannot create their own API tokens Fixes #11486 * fix detection of metadata * make easier to read * add handler for IsAuthenticated * use correct method * fix style see #11487 * add frontend test * make test more reliable?
This commit is contained in:
@@ -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]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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={<StylishText size='xl'>{t`Token`}</StylishText>}
|
||||
centered
|
||||
data-testid='generated-api-token'
|
||||
>
|
||||
<Text c='dimmed'>
|
||||
<Trans>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user