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:
Wojciech Mista 2021-09-24 14:01:54 +02:00 committed by GitHub
parent d4ffd88752
commit b98f069aab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 55 deletions

View file

@ -66,6 +66,13 @@ export const PERMISSIONS = {
CONFIGURATION_SELECTORS.productTypes
]
},
pageTypeAndAttribute: {
parent: configurationAsParent,
permissionSelectors: [
CONFIGURATION_SELECTORS.pageTypes,
CONFIGURATION_SELECTORS.attributes
]
},
settings: {
parent: configurationAsParent,
permissionSelectors: [

View file

@ -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]

View file

@ -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;

View file

@ -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;

View file

@ -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
}

View file

@ -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}

View file

@ -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"

View 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();

View file

@ -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>