diff --git a/docs/docs/settings/error_codes.md b/docs/docs/settings/error_codes.md index 59874a9402..bf388e7f2b 100644 --- a/docs/docs/settings/error_codes.md +++ b/docs/docs/settings/error_codes.md @@ -210,6 +210,12 @@ The environment in which the backup was taken does not match the current environ This warning will not prevent you from restoring the backup but it is recommended to ensure the mentioned issues are resolved before restoring the backup to prevent issues with the restored instance. +#### INVE-W14 +**Elevated privileges - Backend** + +A user is logged in with elevated privileges. This might be a superuser or a administrator user. These types of users have elevated permissions and should not be used for regular usage. +Use separate accounts for administrative tasks and regular usage to reduce risk. Make sure to review the [permission documentation](../settings/permissions.md#dangerous-user-flags). + ### INVE-I (InvenTree Information) Information — These are not errors but information messages. They might point out potential issues or just provide information. diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index 37688941f0..b25dfc1a98 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -535,6 +535,7 @@ Set the `INVENTREE_FRONTEND_SETTINGS` Environment variable to a JSON object or u | `url_compatibility` | Support compatibility with "legacy" URLs? | `true` | | `sentry_dsn` | Set a Sentry DSN url | *Not specified* | | `mobile_mode` | Controls if InvenTree web UI can be used by mobile devices. There are 3 options: `default` - does not allow mobile devices; `allow-ignore` - shows a mobile device detected banner with a button to ignore this warning AT THE USERS OWN RISK; `allow-always` - skips the mobile check and allows mobile devices always (of course at the server admins OWN RISK) | `default` | +| `dangerous_hide_evelevated_alert` | Hides the elevated permissions alert in the UI. This is a dangerous option as using the UI with elevated permissions is against the threat model. | `false` | E.g. to allow mobile devices to ignore the mobile check, use the following Environment variable: diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 182c008bd8..354c46c20c 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -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 diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py index 0fdd481137..284b4611d3 100644 --- a/src/backend/InvenTree/users/serializers.py +++ b/src/backend/InvenTree/users/serializers.py @@ -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, ) diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index 473b70946b..54c4ddfa3a 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -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): diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index 2e61e7377a..9222814fa4 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -127,7 +127,7 @@ function HoverNameBadge(data: any, type: BadgeType) { data?.image, <> {data.is_superuser && {t`Superuser`}} - {data.is_staff && {t`Staff`}} + {data.is_staff && {t`Administrator`}} {data.email && t`Email: ` + data.email} ]; diff --git a/src/frontend/src/components/items/OnlyStaff.tsx b/src/frontend/src/components/items/OnlyStaff.tsx index 192e748d53..709d75c69d 100644 --- a/src/frontend/src/components/items/OnlyStaff.tsx +++ b/src/frontend/src/components/items/OnlyStaff.tsx @@ -7,5 +7,7 @@ export const OnlyStaff = ({ children }: { children: any }) => { const [user] = useUserState(useShallow((state) => [state.user])); if (user?.is_staff) return children; - return This information is only available for staff users; + return ( + This information is only available for administrative users + ); }; diff --git a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx index 56feea6e2c..dc478d56b7 100644 --- a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx +++ b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx @@ -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 ( - - This information is only available for staff users - - ); - return ; + return ( + + + + ); } const AboutContent = ({ diff --git a/src/frontend/src/components/nav/Header.tsx b/src/frontend/src/components/nav/Header.tsx index 14725a6002..a70b87819b 100644 --- a/src/frontend/src/components/nav/Header.tsx +++ b/src/frontend/src/components/nav/Header.tsx @@ -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(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(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() { + {showElevated && user && ( + setElevatedAlertClosed(true)} + > + + {t`The current user has elevated privileges and should not be used for regular usage.`} + {errorCodeLink('INVE-W14')} + + + )} ); } diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx index d5fc90bd52..54b2ffc7da 100644 --- a/src/frontend/src/main.tsx +++ b/src/frontend/src/main.tsx @@ -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; diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx index f879e77906..7d0ad44770 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx @@ -69,7 +69,7 @@ export function AccountDetailPanel() { value: }, { - label: t`Staff Access`, + label: t`Administrator`, value: }, { diff --git a/src/frontend/src/pages/core/UserDetail.tsx b/src/frontend/src/pages/core/UserDetail.tsx index e7dd1940ca..457f19e5d6 100644 --- a/src/frontend/src/pages/core/UserDetail.tsx +++ b/src/frontend/src/pages/core/UserDetail.tsx @@ -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 && ( - {t`Staff`} + {t`Administrator`} ), instance.is_superuser && ( {t`Superuser`} ), !instance.is_staff && !instance.is_superuser && ( - {t`Basic user`} + {t`Normal user`} ), instance.is_active ? ( {t`Active`} diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx index 66bd277547..e70f106afe 100644 --- a/src/frontend/src/tables/settings/UserTable.tsx +++ b/src/frontend/src/tables/settings/UserTable.tsx @@ -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',