Skip to content

Commit

Permalink
Show a UI warning when user does not have permission to update/edit a…
Browse files Browse the repository at this point in the history
…n existing Navigation block (#37286)

* Show warning when user has insufficient permision to edit the given Nav

* Move warning underneath block but allow viewing

* Sync warning display with entity loading

* Include ref as effect dependency

* Revert unintentional edit to comment placement

* Show permisisons warning using global notices system

* Hide delete and rename inspector items based on perms

* Switch to snackbar notice

* Add e2e test covering update permission notice

* Try removing forward slash to see if test passes

* Remove usage of uniqueId

Resolves #37286 (comment)

* Update error message to use clearer terminology

Addressses #37286 (comment)

* Fix test to match code error message string change

* Move permissions selectors to existing hook

* Add explaination of requirement for 403 expect for Nav Areas

* Attempt to fix flaky test on CI

* Remove ref as a dependency to useSelect

Co-authored-by: Daniel Richards <daniel.richards@automattic.com>
  • Loading branch information
getdave and talldan authored Dec 16, 2021
1 parent 3044e2f commit 131df0e
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 13 deletions.
90 changes: 77 additions & 13 deletions packages/block-library/src/navigation/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import noop from 'lodash';
import { noop } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -27,6 +27,7 @@ import {
Warning,
} from '@wordpress/block-editor';
import { EntityProvider, useEntityProp } from '@wordpress/core-data';

import { useDispatch, useSelect } from '@wordpress/data';
import {
PanelBody,
Expand All @@ -38,6 +39,7 @@ import {
Button,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { store as noticeStore } from '@wordpress/notices';

/**
* Internal dependencies
Expand Down Expand Up @@ -103,6 +105,8 @@ function Navigation( {
customPlaceholder: CustomPlaceholder = null,
customAppender: CustomAppender = null,
} ) {
const noticeRef = useRef();

const {
openSubmenusOnClick,
overlayMenu,
Expand Down Expand Up @@ -167,6 +171,8 @@ function Navigation( {
__unstableMarkNextChangeAsNotPersistent,
} = useDispatch( blockEditorStore );

const { createWarningNotice, removeNotice } = useDispatch( noticeStore );

const [
hasSavedUnsavedInnerBlocks,
setHasSavedUnsavedInnerBlocks,
Expand All @@ -189,6 +195,10 @@ function Navigation( {
hasResolvedNavigationMenus,
navigationMenus,
navigationMenu,
canUserUpdateNavigationEntity,
hasResolvedCanUserUpdateNavigationEntity,
canUserDeleteNavigationEntity,
hasResolvedCanUserDeleteNavigationEntity,
} = useNavigationMenu( ref );

const navRef = useRef();
Expand Down Expand Up @@ -303,6 +313,54 @@ function Navigation( {
// with the snapshot from the time when ref became undefined.
}, [ clientId, ref, innerBlocks ] );

useEffect( () => {
const setPermissionsNotice = () => {
if ( noticeRef.current ) {
return;
}

noticeRef.current =
'block-library/core/navigation/permissions/update';

createWarningNotice(
__(
'You do not have permission to edit this Menu. Any changes made will not be saved.'
),
{
id: noticeRef.current,
type: 'snackbar',
}
);
};

const removePermissionsNotice = () => {
if ( ! noticeRef.current ) {
return;
}
removeNotice( noticeRef.current );
noticeRef.current = null;
};

if ( ! isSelected && ! isInnerBlockSelected ) {
removePermissionsNotice();
}

if (
( isSelected || isInnerBlockSelected ) &&
hasResolvedCanUserUpdateNavigationEntity &&
! canUserUpdateNavigationEntity
) {
setPermissionsNotice();
}
}, [
ref,
isEntityAvailable,
hasResolvedCanUserUpdateNavigationEntity,
canUserUpdateNavigationEntity,
isSelected,
isInnerBlockSelected,
] );

const startWithEmptyMenu = useCallback( () => {
if ( navigationArea ) {
setAreaMenu( 0 );
Expand Down Expand Up @@ -507,18 +565,24 @@ function Navigation( {
</InspectorControls>
{ isEntityAvailable && (
<InspectorControls __experimentalGroup="advanced">
<NavigationMenuNameControl />
<NavigationMenuDeleteControl
onDelete={ () => {
if ( navigationArea ) {
setAreaMenu( 0 );
}
setAttributes( {
ref: undefined,
} );
setIsPlaceholderShown( true );
} }
/>
{ hasResolvedCanUserUpdateNavigationEntity &&
canUserUpdateNavigationEntity && (
<NavigationMenuNameControl />
) }
{ hasResolvedCanUserDeleteNavigationEntity &&
canUserDeleteNavigationEntity && (
<NavigationMenuDeleteControl
onDelete={ () => {
if ( navigationArea ) {
setAreaMenu( 0 );
}
setAttributes( {
ref: undefined,
} );
setIsPlaceholderShown( true );
} }
/>
) }
</InspectorControls>
) }
<nav { ...blockProps }>
Expand Down
15 changes: 15 additions & 0 deletions packages/block-library/src/navigation/use-navigation-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function useNavigationMenu( ref ) {
getEditedEntityRecord,
getEntityRecords,
hasFinishedResolution,
canUser,
} = select( coreStore );

const navigationMenuSingleArgs = [
Expand Down Expand Up @@ -64,6 +65,20 @@ export default function useNavigationMenu( ref ) {
),
navigationMenu,
navigationMenus,
canUserUpdateNavigationEntity: ref
? canUser( 'update', 'navigation', ref )
: undefined,
hasResolvedCanUserUpdateNavigationEntity: hasFinishedResolution(
'canUser',
[ 'update', 'navigation', ref ]
),
canUserDeleteNavigationEntity: ref
? canUser( 'delete', 'navigation', ref )
: undefined,
hasResolvedCanUserDeleteNavigationEntity: hasFinishedResolution(
'canUser',
[ 'delete', 'navigation', ref ]
),
};
},
[ ref ]
Expand Down
65 changes: 65 additions & 0 deletions packages/e2e-tests/specs/editor/blocks/navigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
ensureSidebarOpened,
__experimentalRest as rest,
publishPost,
createUser,
loginUser,
deleteUser,
} from '@wordpress/e2e-test-utils';

/**
Expand Down Expand Up @@ -111,6 +114,7 @@ const PLACEHOLDER_ACTIONS_CLASS = 'wp-block-navigation-placeholder__actions';
const PLACEHOLDER_ACTIONS_XPATH = `//*[contains(@class, '${ PLACEHOLDER_ACTIONS_CLASS }')]`;
const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start empty']`;
const ADD_ALL_PAGES_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Add all pages']`;
const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select menu']`;

async function turnResponsivenessOn() {
const blocks = await getAllBlocks();
Expand Down Expand Up @@ -203,6 +207,17 @@ async function getNavigationMenuRawContent() {
// Disable reason - these tests are to be re-written.
// eslint-disable-next-line jest/no-disabled-tests
describe( 'Navigation', () => {
let username;
let contribUserPassword;

beforeAll( async () => {
username = 'contributoruser';

contribUserPassword = await createUser( username, {
role: 'contributor',
} );
} );

beforeEach( async () => {
await deleteAll( [
POSTS_ENDPOINT,
Expand All @@ -223,6 +238,8 @@ describe( 'Navigation', () => {
NAVIGATION_MENUS_ENDPOINT,
] );
await deleteAllClassicMenus();

await deleteUser( username );
} );

describe( 'placeholder', () => {
Expand Down Expand Up @@ -766,4 +783,52 @@ describe( 'Navigation', () => {
expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 );
} );
} );

describe( 'Permission based restrictions', () => {
it( 'shows a warning if user does not have permission to edit or update navigation menus', async () => {
await createNewPost();
await insertBlock( 'Navigation' );

const startEmptyButton = await page.waitForXPath(
START_EMPTY_XPATH
);

// This creates an empty Navigation post type entity.
await startEmptyButton.click();

// Publishing the Post ensures the Navigation entity is saved.
// The Post itself is irrelevant.
await publishPost();

// Switch to a Contributor role user - they should not have
// permission to update Navigations.
await loginUser( username, contribUserPassword );

await createNewPost();

await insertBlock( 'Navigation' );

// Select the Navigation post created by the Admin early
// in the test.
const navigationPostCreatedByAdminName = 'Navigation';
const dropdown = await page.waitForXPath( SELECT_MENU_XPATH );
await dropdown.click();
const theOption = await page.waitForXPath(
`//*[contains(@class, 'components-menu-item__item')][ text()="${ navigationPostCreatedByAdminName }" ]`
);
await theOption.click();

// Make sure the snackbar error shows up
await page.waitForXPath(
`//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]`
);

// Expect a console 403 for request to Navigation Areas for lower permisison users.
// This is because reading requires the `edit_theme_options` capability
// which the Contributor level user does not have.
// See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91.
// Todo: removed once Nav Areas are removed from the Gutenberg Plugin.
expect( console ).toHaveErrored();
} );
} );
} );

0 comments on commit 131df0e

Please sign in to comment.