Add page type and attributes permissions handling (#1425)
* WIP add new configuration menu * Add missing permissions * Changed filtering to one based on arrays of permissions * Add hasAnyPermissions and hasAllPermissions functions * Simplify getConfigMenuItemsPermissions function * Simplify routing logic
This commit is contained in:
parent
d4ffd88752
commit
b98f069aab
9 changed files with 102 additions and 55 deletions
|
@ -66,6 +66,13 @@ export const PERMISSIONS = {
|
|||
CONFIGURATION_SELECTORS.productTypes
|
||||
]
|
||||
},
|
||||
pageTypeAndAttribute: {
|
||||
parent: configurationAsParent,
|
||||
permissionSelectors: [
|
||||
CONFIGURATION_SELECTORS.pageTypes,
|
||||
CONFIGURATION_SELECTORS.attributes
|
||||
]
|
||||
},
|
||||
settings: {
|
||||
parent: configurationAsParent,
|
||||
permissionSelectors: [
|
||||
|
|
|
@ -33,9 +33,6 @@ export const PERMISSIONS_OPTIONS = {
|
|||
user: ONE_PERMISSION_USERS.page,
|
||||
permissions: [PERMISSIONS.page]
|
||||
},
|
||||
pageTypeAndAttribute: {
|
||||
user: ONE_PERMISSION_USERS.pageTypeAndAttribute
|
||||
},
|
||||
plugin: {
|
||||
user: ONE_PERMISSION_USERS.plugin,
|
||||
permissions: [PERMISSIONS.plugin]
|
||||
|
@ -48,6 +45,10 @@ export const PERMISSIONS_OPTIONS = {
|
|||
user: ONE_PERMISSION_USERS.productTypeAndAttribute,
|
||||
permissions: [PERMISSIONS.productTypeAndAttribute]
|
||||
},
|
||||
pageTypeAndAttribute: {
|
||||
user: ONE_PERMISSION_USERS.pageTypeAndAttribute,
|
||||
permissions: [PERMISSIONS.pageTypeAndAttribute]
|
||||
},
|
||||
settings: {
|
||||
user: ONE_PERMISSION_USERS.settings,
|
||||
permissions: [PERMISSIONS.settings]
|
||||
|
|
|
@ -4,24 +4,37 @@ import { Route, RouteProps } from "react-router-dom";
|
|||
|
||||
import NotFound from "../../NotFound";
|
||||
import { PermissionEnum } from "../../types/globalTypes";
|
||||
import { hasPermission } from "../misc";
|
||||
import { hasAllPermissions, hasAnyPermissions } from "../misc";
|
||||
|
||||
type MatchPermissionType = "all" | "any";
|
||||
|
||||
interface SectionRouteProps extends RouteProps {
|
||||
permissions?: PermissionEnum[];
|
||||
matchPermission?: MatchPermissionType;
|
||||
}
|
||||
|
||||
const matchAll = (match: MatchPermissionType) => match === "all";
|
||||
|
||||
export const SectionRoute: React.FC<SectionRouteProps> = ({
|
||||
permissions,
|
||||
matchPermission = "all",
|
||||
...props
|
||||
}) => {
|
||||
const { user } = useUser();
|
||||
|
||||
const hasPermissions =
|
||||
!permissions ||
|
||||
permissions
|
||||
.map(permission => hasPermission(permission, user))
|
||||
.reduce((prev, curr) => prev && curr);
|
||||
return hasPermissions ? <Route {...props} /> : <NotFound />;
|
||||
const hasSectionPermissions = () => {
|
||||
if (!permissions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchAll(matchPermission)) {
|
||||
return hasAllPermissions(permissions, user);
|
||||
}
|
||||
|
||||
return hasAnyPermissions(permissions, user);
|
||||
};
|
||||
|
||||
return hasSectionPermissions() ? <Route {...props} /> : <NotFound />;
|
||||
};
|
||||
SectionRoute.displayName = "Route";
|
||||
export default SectionRoute;
|
||||
|
|
|
@ -4,3 +4,9 @@ import { PermissionEnum } from "../types/globalTypes";
|
|||
|
||||
export const hasPermission = (permission: PermissionEnum, user: User) =>
|
||||
user.userPermissions.map(perm => perm.code).includes(permission);
|
||||
|
||||
export const hasAnyPermissions = (permissions: PermissionEnum[], user: User) =>
|
||||
permissions?.some(permission => hasPermission(permission, user)) || false;
|
||||
|
||||
export const hasAllPermissions = (permissions: PermissionEnum[], user: User) =>
|
||||
permissions?.every(permission => hasPermission(permission, user)) || false;
|
||||
|
|
|
@ -6,11 +6,8 @@ import discountsIcon from "@assets/images/menu-discounts-icon.svg";
|
|||
import homeIcon from "@assets/images/menu-home-icon.svg";
|
||||
import ordersIcon from "@assets/images/menu-orders-icon.svg";
|
||||
import translationIcon from "@assets/images/menu-translation-icon.svg";
|
||||
import {
|
||||
configurationMenuUrl,
|
||||
createConfigurationMenu
|
||||
} from "@saleor/configuration";
|
||||
import { MenuItem } from "@saleor/configuration/ConfigurationPage";
|
||||
import { configurationMenuUrl } from "@saleor/configuration";
|
||||
import { getConfigMenuItemsPermissions } from "@saleor/configuration/utils";
|
||||
import { User } from "@saleor/fragments/types/User";
|
||||
import { giftCardsListUrl } from "@saleor/giftCards/urls";
|
||||
import { commonMessages, sectionNames } from "@saleor/intl";
|
||||
|
@ -33,8 +30,6 @@ interface FilterableMenuItem extends Omit<SidebarMenuItem, "children"> {
|
|||
}
|
||||
|
||||
function createMenuStructure(intl: IntlShape, user: User): SidebarMenuItem[] {
|
||||
const configurationMenu = createConfigurationMenu(intl);
|
||||
|
||||
const menuItems: FilterableMenuItem[] = [
|
||||
{
|
||||
ariaLabel: "home",
|
||||
|
@ -152,12 +147,7 @@ function createMenuStructure(intl: IntlShape, user: User): SidebarMenuItem[] {
|
|||
ariaLabel: "configure",
|
||||
iconSrc: configurationIcon,
|
||||
label: intl.formatMessage(sectionNames.configuration),
|
||||
permissions: configurationMenu
|
||||
.reduce(
|
||||
(sections, section) => [...sections, ...section.menuItems],
|
||||
[] as MenuItem[]
|
||||
)
|
||||
.map(section => section.permission),
|
||||
permissions: getConfigMenuItemsPermissions(intl),
|
||||
id: "configure",
|
||||
url: configurationMenuUrl
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { makeStyles } from "@saleor/macaw-ui";
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { hasPermission } from "../auth/misc";
|
||||
import { hasAnyPermissions } from "../auth/misc";
|
||||
import Container from "../components/Container";
|
||||
import PageHeader from "../components/PageHeader";
|
||||
import VersionInfo from "../components/VersionInfo";
|
||||
|
@ -17,7 +17,7 @@ import { PermissionEnum } from "../types/globalTypes";
|
|||
export interface MenuItem {
|
||||
description: string;
|
||||
icon: React.ReactElement<IconProps>;
|
||||
permission: PermissionEnum;
|
||||
permissions: PermissionEnum[];
|
||||
title: string;
|
||||
url?: string;
|
||||
testId?: string;
|
||||
|
@ -123,6 +123,7 @@ export const ConfigurationPage: React.FC<ConfigurationPageProps> = props => {
|
|||
);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{!isSmUp && renderVersionInfo}
|
||||
|
@ -135,7 +136,7 @@ export const ConfigurationPage: React.FC<ConfigurationPageProps> = props => {
|
|||
{menus
|
||||
.filter(menu =>
|
||||
menu.menuItems.some(menuItem =>
|
||||
hasPermission(menuItem.permission, user)
|
||||
hasAnyPermissions(menuItem.permissions, user)
|
||||
)
|
||||
)
|
||||
.map((menu, menuIndex) => (
|
||||
|
@ -145,7 +146,9 @@ export const ConfigurationPage: React.FC<ConfigurationPageProps> = props => {
|
|||
</div>
|
||||
<div className={classes.configurationItem}>
|
||||
{menu.menuItems
|
||||
.filter(menuItem => hasPermission(menuItem.permission, user))
|
||||
.filter(menuItem =>
|
||||
hasAnyPermissions(menuItem.permissions, user)
|
||||
)
|
||||
.map((item, itemIndex) => (
|
||||
<Card
|
||||
className={item.url ? classes.card : classes.cardDisabled}
|
||||
|
|
|
@ -50,7 +50,10 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuAttributes"
|
||||
}),
|
||||
icon: <Attributes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES,
|
||||
permissions: [
|
||||
PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES,
|
||||
PermissionEnum.MANAGE_PAGE_TYPES_AND_ATTRIBUTES
|
||||
],
|
||||
title: intl.formatMessage(sectionNames.attributes),
|
||||
url: attributeListUrl(),
|
||||
testId: "configurationMenuAttributes"
|
||||
|
@ -61,7 +64,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuProductTypes"
|
||||
}),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES,
|
||||
permissions: [PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES],
|
||||
title: intl.formatMessage(sectionNames.productTypes),
|
||||
url: productTypeListUrl(),
|
||||
testId: "configurationMenuProductTypes"
|
||||
|
@ -79,7 +82,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuTaxes"
|
||||
}),
|
||||
icon: <Taxes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SETTINGS,
|
||||
permissions: [PermissionEnum.MANAGE_SETTINGS],
|
||||
title: intl.formatMessage(sectionNames.taxes),
|
||||
url: taxSection,
|
||||
testId: "configurationMenuTaxes"
|
||||
|
@ -97,7 +100,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuStaff"
|
||||
}),
|
||||
icon: <StaffMembers fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_STAFF,
|
||||
permissions: [PermissionEnum.MANAGE_STAFF],
|
||||
title: intl.formatMessage(sectionNames.staff),
|
||||
url: staffListUrl(),
|
||||
testId: "configurationMenuStaff"
|
||||
|
@ -109,7 +112,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuPermissionGroups"
|
||||
}),
|
||||
icon: <PermissionGroups fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_STAFF,
|
||||
permissions: [PermissionEnum.MANAGE_STAFF],
|
||||
title: intl.formatMessage(sectionNames.permissionGroups),
|
||||
url: permissionGroupListUrl(),
|
||||
testId: "configurationMenuPermissionGroups"
|
||||
|
@ -127,7 +130,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuShipping"
|
||||
}),
|
||||
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SHIPPING,
|
||||
permissions: [PermissionEnum.MANAGE_SHIPPING],
|
||||
title: intl.formatMessage(sectionNames.shipping),
|
||||
url: shippingZonesListUrl(),
|
||||
testId: "configurationMenuShipping"
|
||||
|
@ -138,7 +141,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuWarehouses"
|
||||
}),
|
||||
icon: <Warehouses fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
permissions: [PermissionEnum.MANAGE_PRODUCTS],
|
||||
title: intl.formatMessage(sectionNames.warehouses),
|
||||
url: warehouseSection,
|
||||
testId: "configurationMenuWarehouses"
|
||||
|
@ -156,7 +159,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuChannels"
|
||||
}),
|
||||
icon: <Channels fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_CHANNELS,
|
||||
permissions: [PermissionEnum.MANAGE_CHANNELS],
|
||||
title: intl.formatMessage(sectionNames.channels),
|
||||
url: channelsListUrl(),
|
||||
testId: "configurationMenuChannels"
|
||||
|
@ -174,7 +177,10 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuPageTypes"
|
||||
}),
|
||||
icon: <PageTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PAGES,
|
||||
permissions: [
|
||||
PermissionEnum.MANAGE_PAGES,
|
||||
PermissionEnum.MANAGE_PAGE_TYPES_AND_ATTRIBUTES
|
||||
],
|
||||
title: intl.formatMessage(sectionNames.pageTypes),
|
||||
url: pageTypeListUrl(),
|
||||
testId: "configurationMenuPageTypes"
|
||||
|
@ -185,7 +191,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuPages"
|
||||
}),
|
||||
icon: <Pages fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PAGES,
|
||||
permissions: [PermissionEnum.MANAGE_PAGES],
|
||||
title: intl.formatMessage(sectionNames.pages),
|
||||
url: pageListUrl(),
|
||||
testId: "configurationMenuPages"
|
||||
|
@ -203,7 +209,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuNavigation"
|
||||
}),
|
||||
icon: <Navigation fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_MENUS,
|
||||
permissions: [PermissionEnum.MANAGE_MENUS],
|
||||
title: intl.formatMessage(sectionNames.navigation),
|
||||
url: menuListUrl(),
|
||||
testId: "configurationMenuNavigation"
|
||||
|
@ -214,7 +220,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
id: "configurationMenuSiteSettings"
|
||||
}),
|
||||
icon: <SiteSettings fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SETTINGS,
|
||||
permissions: [PermissionEnum.MANAGE_SETTINGS],
|
||||
title: intl.formatMessage(sectionNames.siteSettings),
|
||||
url: siteSettingsUrl(),
|
||||
testId: "configurationMenuSiteSettings"
|
||||
|
@ -231,7 +237,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
preserveAspectRatio="xMinYMin meet"
|
||||
/>
|
||||
),
|
||||
permission: PermissionEnum.MANAGE_PLUGINS,
|
||||
permissions: [PermissionEnum.MANAGE_PLUGINS],
|
||||
title: intl.formatMessage(sectionNames.plugins),
|
||||
url: pluginListUrl(),
|
||||
testId: "configurationPluginsPages"
|
||||
|
|
17
src/configuration/utils.ts
Normal file
17
src/configuration/utils.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import { IntlShape } from "react-intl";
|
||||
|
||||
import { createConfigurationMenu } from ".";
|
||||
|
||||
export const getConfigMenuItemsPermissions = (
|
||||
intl: IntlShape
|
||||
): PermissionEnum[] =>
|
||||
createConfigurationMenu(intl)
|
||||
.reduce(
|
||||
(prev, { menuItems }) => [
|
||||
...prev,
|
||||
...menuItems.map(({ permissions }) => permissions)
|
||||
],
|
||||
[]
|
||||
)
|
||||
.flat();
|
|
@ -26,7 +26,6 @@ import AuthProvider, { useAuth } from "./auth/AuthProvider";
|
|||
import LoginLoading from "./auth/components/LoginLoading/LoginLoading";
|
||||
import SectionRoute from "./auth/components/SectionRoute";
|
||||
import authLink from "./auth/link";
|
||||
import { hasPermission } from "./auth/misc";
|
||||
import CategorySection from "./categories";
|
||||
import ChannelsSection from "./channels";
|
||||
import { channelsSection } from "./channels/urls";
|
||||
|
@ -41,7 +40,8 @@ import MessageManagerProvider from "./components/messages";
|
|||
import { ShopProvider } from "./components/Shop";
|
||||
import { WindowTitle } from "./components/WindowTitle";
|
||||
import { API_URI, APP_MOUNT_URI, DEMO_MODE, GTM_ID } from "./config";
|
||||
import ConfigurationSection, { createConfigurationMenu } from "./configuration";
|
||||
import ConfigurationSection from "./configuration";
|
||||
import { getConfigMenuItemsPermissions } from "./configuration/utils";
|
||||
import AppStateProvider from "./containers/AppState";
|
||||
import BackgroundTasksProvider from "./containers/BackgroundTasks";
|
||||
import ServiceWorker from "./containers/ServiceWorker/ServiceWorker";
|
||||
|
@ -71,6 +71,7 @@ import TranslationsSection from "./translations";
|
|||
import { PermissionEnum } from "./types/globalTypes";
|
||||
import WarehouseSection from "./warehouses";
|
||||
import { warehouseSection } from "./warehouses/urls";
|
||||
|
||||
if (process.env.GTM_ID) {
|
||||
TagManager.initialize({ gtmId: GTM_ID });
|
||||
}
|
||||
|
@ -151,8 +152,7 @@ const Routes: React.FC = () => {
|
|||
hasToken,
|
||||
isAuthenticated,
|
||||
tokenAuthLoading,
|
||||
tokenVerifyLoading,
|
||||
user
|
||||
tokenVerifyLoading
|
||||
} = useAuth();
|
||||
|
||||
const { channel } = useAppChannel(false);
|
||||
|
@ -220,9 +220,13 @@ const Routes: React.FC = () => {
|
|||
component={PageSection}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_PAGES]}
|
||||
permissions={[
|
||||
PermissionEnum.MANAGE_PAGES,
|
||||
PermissionEnum.MANAGE_PAGE_TYPES_AND_ATTRIBUTES
|
||||
]}
|
||||
path="/page-types"
|
||||
component={PageTypesSection}
|
||||
matchPermission="any"
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_PLUGINS]}
|
||||
|
@ -283,10 +287,12 @@ const Routes: React.FC = () => {
|
|||
/>
|
||||
<SectionRoute
|
||||
permissions={[
|
||||
PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES
|
||||
PermissionEnum.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES,
|
||||
PermissionEnum.MANAGE_PAGE_TYPES_AND_ATTRIBUTES
|
||||
]}
|
||||
path={attributeSection}
|
||||
component={AttributeSection}
|
||||
matchPermission="any"
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_APPS]}
|
||||
|
@ -303,15 +309,13 @@ const Routes: React.FC = () => {
|
|||
path={channelsSection}
|
||||
component={ChannelsSection}
|
||||
/>
|
||||
{createConfigurationMenu(intl).filter(menu =>
|
||||
menu.menuItems.map(item => hasPermission(item.permission, user))
|
||||
).length > 0 && (
|
||||
<SectionRoute
|
||||
exact
|
||||
path="/configuration"
|
||||
component={ConfigurationSection}
|
||||
/>
|
||||
)}
|
||||
<SectionRoute
|
||||
matchPermission="any"
|
||||
permissions={getConfigMenuItemsPermissions(intl)}
|
||||
exact
|
||||
path="/configuration"
|
||||
component={ConfigurationSection}
|
||||
/>
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</ErrorBoundary>
|
||||
|
|
Loading…
Reference in a new issue