mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-15 07:48:51 +00:00
feat(frontend): improve comms around danger of staff users (#11659)
* docs: add more details around staff / superuser roles and their dangers * make clear that staff users are dangerous * make distinction clearer in API * add error code and frontend warning about running with staff / admin user * fix test * bump api * adapt banner warning * make banner locally disableable * add global option to disable elevated user alert
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 469
|
||||
INVENTREE_API_VERSION = 470
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v470 -> 2026-04-01 : https://github.com/inventree/InvenTree/pull/11659
|
||||
- Renames "is_staff" field to "is_admin" and updates help texts accordingly to highlight current security boundaries
|
||||
|
||||
v469 -> 2026-03-31 : https://github.com/inventree/InvenTree/pull/11641
|
||||
- Adds parameter support to the SalesOrderShipment model and API endpoints
|
||||
|
||||
|
||||
@@ -306,8 +306,8 @@ class ExtendedUserSerializer(UserSerializer):
|
||||
)
|
||||
|
||||
is_staff = serializers.BooleanField(
|
||||
label=_('Staff'),
|
||||
help_text=_('Does this user have staff permissions'),
|
||||
label=_('Administrator'),
|
||||
help_text=_('Does this user have administrative permissions'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -37,9 +37,10 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
fields['is_active']['help_text'], 'Is this user account active'
|
||||
)
|
||||
|
||||
self.assertEqual(fields['is_staff']['label'], 'Staff')
|
||||
self.assertEqual(fields['is_staff']['label'], 'Administrator')
|
||||
self.assertEqual(
|
||||
fields['is_staff']['help_text'], 'Does this user have staff permissions'
|
||||
fields['is_staff']['help_text'],
|
||||
'Does this user have administrative permissions',
|
||||
)
|
||||
|
||||
def test_api_url(self):
|
||||
|
||||
@@ -127,7 +127,7 @@ function HoverNameBadge(data: any, type: BadgeType) {
|
||||
data?.image,
|
||||
<>
|
||||
{data.is_superuser && <Badge color='red'>{t`Superuser`}</Badge>}
|
||||
{data.is_staff && <Badge color='blue'>{t`Staff`}</Badge>}
|
||||
{data.is_staff && <Badge color='orange'>{t`Administrator`}</Badge>}
|
||||
{data.email && t`Email: ` + data.email}
|
||||
</>
|
||||
];
|
||||
|
||||
@@ -7,5 +7,7 @@ export const OnlyStaff = ({ children }: { children: any }) => {
|
||||
const [user] = useUserState(useShallow((state) => [state.user]));
|
||||
|
||||
if (user?.is_staff) return children;
|
||||
return <Trans>This information is only available for staff users</Trans>;
|
||||
return (
|
||||
<Trans>This information is only available for administrative users</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
Group,
|
||||
Space,
|
||||
Stack,
|
||||
Table,
|
||||
Text
|
||||
Table
|
||||
} from '@mantine/core';
|
||||
import type { ContextModalProps } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -20,11 +19,11 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { api } from '../../App';
|
||||
import { generateUrl } from '../../functions/urls';
|
||||
import { useServerApiState } from '../../states/ServerApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
import type { JSX } from 'react';
|
||||
import { OnlyStaff } from '../items/OnlyStaff';
|
||||
|
||||
type AboutLookupRef = {
|
||||
ref: string;
|
||||
@@ -42,15 +41,11 @@ export function AboutInvenTreeModal({
|
||||
modalBody: string;
|
||||
}>
|
||||
>) {
|
||||
const [user] = useUserState(useShallow((state) => [state.user]));
|
||||
|
||||
if (!user?.is_staff)
|
||||
return (
|
||||
<Text>
|
||||
<Trans>This information is only available for staff users</Trans>
|
||||
</Text>
|
||||
);
|
||||
return <AboutContent context={context} id={id} innerProps={innerProps} />;
|
||||
return (
|
||||
<OnlyStaff>
|
||||
<AboutContent context={context} id={id} innerProps={innerProps} />
|
||||
</OnlyStaff>
|
||||
);
|
||||
}
|
||||
|
||||
const AboutContent = ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Container,
|
||||
Group,
|
||||
Indicator,
|
||||
@@ -39,7 +40,7 @@ import {
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { ScanButton } from '../buttons/ScanButton';
|
||||
import { SpotlightButton } from '../buttons/SpotlightButton';
|
||||
import { Alerts } from './Alerts';
|
||||
import { Alerts, errorCodeLink } from './Alerts';
|
||||
import { MainMenu } from './MainMenu';
|
||||
import { NavHoverMenu } from './NavHoverMenu';
|
||||
import { NavigationDrawer } from './NavigationDrawer';
|
||||
@@ -53,11 +54,12 @@ export function Header() {
|
||||
const [server] = useServerApiState(useShallow((state) => [state.server]));
|
||||
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
|
||||
useDisclosure(navigationOpen);
|
||||
|
||||
const [
|
||||
searchDrawerOpened,
|
||||
{ open: openSearchDrawer, close: closeSearchDrawer }
|
||||
] = useDisclosure(false);
|
||||
const [elevatedAlertClosed, setElevatedAlertClosed] =
|
||||
useState<boolean>(false);
|
||||
|
||||
useHotkeys([
|
||||
[
|
||||
@@ -79,7 +81,7 @@ export function Header() {
|
||||
{ open: openNotificationDrawer, close: closeNotificationDrawer }
|
||||
] = useDisclosure(false);
|
||||
|
||||
const { isLoggedIn } = useUserState();
|
||||
const { isLoggedIn, user } = useUserState();
|
||||
const [notificationCount, setNotificationCount] = useState<number>(0);
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const userSettings = useUserSettingsState();
|
||||
@@ -128,6 +130,14 @@ export function Header() {
|
||||
else closeNavDrawer();
|
||||
}, [navigationOpen]);
|
||||
|
||||
const showElevated = useMemo(
|
||||
() =>
|
||||
(user?.is_staff || user?.is_superuser || false) &&
|
||||
!elevatedAlertClosed &&
|
||||
!window.INVENTREE_SETTINGS.dangerous_hide_evelevated_alert,
|
||||
[user, elevatedAlertClosed]
|
||||
);
|
||||
|
||||
const headerStyle: any = useMemo(() => {
|
||||
const sticky: boolean = userSettings.isSet('STICKY_HEADER', true);
|
||||
|
||||
@@ -200,6 +210,19 @@ export function Header() {
|
||||
</Group>
|
||||
</Group>
|
||||
</Container>
|
||||
{showElevated && user && (
|
||||
<Alert
|
||||
color={user.is_superuser ? 'red' : 'orange'}
|
||||
title={user.is_superuser ? t`Superuser Mode` : t`Administrator Mode`}
|
||||
withCloseButton
|
||||
onClose={() => setElevatedAlertClosed(true)}
|
||||
>
|
||||
<Text>
|
||||
{t`The current user has elevated privileges and should not be used for regular usage.`}
|
||||
{errorCodeLink('INVE-W14')}
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ declare global {
|
||||
sentry_dsn?: string;
|
||||
environment?: string;
|
||||
mobile_mode?: 'default' | 'allow-ignore' | 'allow-always';
|
||||
dangerous_hide_evelevated_alert?: boolean;
|
||||
};
|
||||
react: typeof React;
|
||||
React: typeof React;
|
||||
|
||||
@@ -69,7 +69,7 @@ export function AccountDetailPanel() {
|
||||
value: <YesNoUndefinedButton value={user?.profile?.active} />
|
||||
},
|
||||
{
|
||||
label: t`Staff Access`,
|
||||
label: t`Administrator`,
|
||||
value: <YesNoUndefinedButton value={user?.is_staff} />
|
||||
},
|
||||
{
|
||||
|
||||
@@ -84,14 +84,14 @@ export default function UserDetail() {
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'is_staff',
|
||||
label: t`Staff`,
|
||||
icon: 'info'
|
||||
label: t`Administrator`,
|
||||
icon: 'warning'
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'is_superuser',
|
||||
label: t`Superuser`,
|
||||
icon: 'info'
|
||||
icon: 'warning'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -197,13 +197,13 @@ export default function UserDetail() {
|
||||
? []
|
||||
: [
|
||||
instance.is_staff && (
|
||||
<Badge key='is_staff' color='blue'>{t`Staff`}</Badge>
|
||||
<Badge key='is_staff' color='orange'>{t`Administrator`}</Badge>
|
||||
),
|
||||
instance.is_superuser && (
|
||||
<Badge key='is_superuser' color='red'>{t`Superuser`}</Badge>
|
||||
),
|
||||
!instance.is_staff && !instance.is_superuser && (
|
||||
<Badge key='is_normal' color='yellow'>{t`Basic user`}</Badge>
|
||||
<Badge key='is_normal' color='yellow'>{t`Normal user`}</Badge>
|
||||
),
|
||||
instance.is_active ? (
|
||||
<Badge key='is_active' color='green'>{t`Active`}</Badge>
|
||||
|
||||
@@ -180,7 +180,7 @@ export function UserDrawer({
|
||||
disabled: isCurrentUser
|
||||
},
|
||||
is_staff: {
|
||||
label: t`Is Staff`,
|
||||
label: t`Is Administrator`,
|
||||
description: t`Designates whether the user can log into the django admin site.`,
|
||||
disabled: isCurrentUser
|
||||
},
|
||||
@@ -281,7 +281,8 @@ export function UserTable({
|
||||
}
|
||||
},
|
||||
BooleanColumn({
|
||||
accessor: 'is_staff'
|
||||
accessor: 'is_staff',
|
||||
title: t`Administrator`
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'is_superuser'
|
||||
@@ -402,8 +403,8 @@ export function UserTable({
|
||||
},
|
||||
{
|
||||
name: 'is_staff',
|
||||
label: t`Staff`,
|
||||
description: t`Show staff users`
|
||||
label: t`Administrator`,
|
||||
description: t`Show administrators`
|
||||
},
|
||||
{
|
||||
name: 'is_superuser',
|
||||
|
||||
Reference in New Issue
Block a user