From b98f069aaba554e727fab39b2251c9caef86dc62 Mon Sep 17 00:00:00 2001 From: Wojciech Mista Date: Fri, 24 Sep 2021 14:01:54 +0200 Subject: [PATCH] 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 --- cypress/Data/permissions.js | 7 +++++ cypress/Data/permissionsUsers.js | 7 +++-- src/auth/components/SectionRoute.tsx | 27 +++++++++++++----- src/auth/misc.ts | 6 ++++ src/components/AppLayout/menuStructure.ts | 16 ++--------- src/configuration/ConfigurationPage.tsx | 11 +++++--- src/configuration/index.tsx | 32 ++++++++++++--------- src/configuration/utils.ts | 17 ++++++++++++ src/index.tsx | 34 +++++++++++++---------- 9 files changed, 102 insertions(+), 55 deletions(-) create mode 100644 src/configuration/utils.ts diff --git a/cypress/Data/permissions.js b/cypress/Data/permissions.js index 0da978465..bdf2051cb 100644 --- a/cypress/Data/permissions.js +++ b/cypress/Data/permissions.js @@ -66,6 +66,13 @@ export const PERMISSIONS = { CONFIGURATION_SELECTORS.productTypes ] }, + pageTypeAndAttribute: { + parent: configurationAsParent, + permissionSelectors: [ + CONFIGURATION_SELECTORS.pageTypes, + CONFIGURATION_SELECTORS.attributes + ] + }, settings: { parent: configurationAsParent, permissionSelectors: [ diff --git a/cypress/Data/permissionsUsers.js b/cypress/Data/permissionsUsers.js index b7b8d1cc4..22105318b 100644 --- a/cypress/Data/permissionsUsers.js +++ b/cypress/Data/permissionsUsers.js @@ -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] diff --git a/src/auth/components/SectionRoute.tsx b/src/auth/components/SectionRoute.tsx index 332909a62..f7e93c31a 100644 --- a/src/auth/components/SectionRoute.tsx +++ b/src/auth/components/SectionRoute.tsx @@ -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 = ({ permissions, + matchPermission = "all", ...props }) => { const { user } = useUser(); - const hasPermissions = - !permissions || - permissions - .map(permission => hasPermission(permission, user)) - .reduce((prev, curr) => prev && curr); - return hasPermissions ? : ; + const hasSectionPermissions = () => { + if (!permissions) { + return true; + } + + if (matchAll(matchPermission)) { + return hasAllPermissions(permissions, user); + } + + return hasAnyPermissions(permissions, user); + }; + + return hasSectionPermissions() ? : ; }; SectionRoute.displayName = "Route"; export default SectionRoute; diff --git a/src/auth/misc.ts b/src/auth/misc.ts index a9e1392f6..dd964afec 100644 --- a/src/auth/misc.ts +++ b/src/auth/misc.ts @@ -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; diff --git a/src/components/AppLayout/menuStructure.ts b/src/components/AppLayout/menuStructure.ts index 879caba09..80802447d 100644 --- a/src/components/AppLayout/menuStructure.ts +++ b/src/components/AppLayout/menuStructure.ts @@ -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 { } 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 } diff --git a/src/configuration/ConfigurationPage.tsx b/src/configuration/ConfigurationPage.tsx index d8107cad6..33e67a2d2 100644 --- a/src/configuration/ConfigurationPage.tsx +++ b/src/configuration/ConfigurationPage.tsx @@ -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; - permission: PermissionEnum; + permissions: PermissionEnum[]; title: string; url?: string; testId?: string; @@ -123,6 +123,7 @@ export const ConfigurationPage: React.FC = props => { ); const intl = useIntl(); + return ( {!isSmUp && renderVersionInfo} @@ -135,7 +136,7 @@ export const ConfigurationPage: React.FC = 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 = props => {
{menu.menuItems - .filter(menuItem => hasPermission(menuItem.permission, user)) + .filter(menuItem => + hasAnyPermissions(menuItem.permissions, user) + ) .map((item, itemIndex) => ( , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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: , - 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" diff --git a/src/configuration/utils.ts b/src/configuration/utils.ts new file mode 100644 index 000000000..67b19cea2 --- /dev/null +++ b/src/configuration/utils.ts @@ -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(); diff --git a/src/index.tsx b/src/index.tsx index d35e58b69..ee4168d56 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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} /> { /> { path={channelsSection} component={ChannelsSection} /> - {createConfigurationMenu(intl).filter(menu => - menu.menuItems.map(item => hasPermission(item.permission, user)) - ).length > 0 && ( - - )} +