From 8bc81c8c4b2fbb23f1f61eede686777480bba73d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 29 Mar 2020 14:19:17 -0700 Subject: [PATCH] Update permissions checking code --- resources/scripts/components/elements/Can.tsx | 32 ++------------- .../server/users/EditSubuserModal.tsx | 40 ++++++++++++++----- .../components/server/users/UserRow.tsx | 5 ++- .../server/users/UsersContainer.tsx | 9 +++-- resources/scripts/helpers.ts | 2 +- resources/scripts/plugins/useDeepMemo.ts | 12 ++++++ resources/scripts/plugins/usePermissions.ts | 22 ++++++++++ 7 files changed, 79 insertions(+), 43 deletions(-) create mode 100644 resources/scripts/plugins/useDeepMemo.ts create mode 100644 resources/scripts/plugins/usePermissions.ts diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx index 0906f9ff8..ee6393719 100644 --- a/resources/scripts/components/elements/Can.tsx +++ b/resources/scripts/components/elements/Can.tsx @@ -1,5 +1,5 @@ -import React, { useMemo } from 'react'; -import { ServerContext } from '@/state/server'; +import React from 'react'; +import { usePermissions } from '@/plugins/usePermissions'; interface Props { action: string | string[]; @@ -8,35 +8,11 @@ interface Props { } const Can = ({ action, renderOnError, children }: Props) => { - const userPermissions = ServerContext.useStoreState(state => state.server.permissions); - const actions = Array.isArray(action) ? action : [ action ]; - - const missingPermissionCount = useMemo(() => { - if (userPermissions[0] === '*') { - return 0; - } - - return actions.filter(permission => { - return !( - // Allows checking for any permission matching a name, for example files.* - // will return if the user has any permission under the file.XYZ namespace. - ( - permission.endsWith('.*') && - permission !== 'websocket.*' && - userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0 - ) - // Otherwise just check if the entire permission exists in the array or not. - || userPermissions.indexOf(permission) >= 0); - }).length; - }, [ action, userPermissions ]); + const can = usePermissions(action); return ( <> - {missingPermissionCount > 0 ? - renderOnError - : - children - } + {can.every(p => p) ? children : renderOnError} ); }; diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index a046528db..5c49edb28 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -14,6 +14,8 @@ import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser'; import { ServerContext } from '@/state/server'; import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; +import Can from '@/components/elements/Can'; +import { usePermissions } from '@/plugins/usePermissions'; type Props = { subuser?: Subuser; @@ -25,21 +27,32 @@ interface Values { } const PermissionLabel = styled.label` - ${tw`flex items-center border border-transparent rounded p-2 cursor-pointer`}; + ${tw`flex items-center border border-transparent rounded p-2`}; text-transform: none; - &:hover { - ${tw`border-neutral-500 bg-neutral-800`}; + &:not(.disabled) { + ${tw`cursor-pointer`}; + + &:hover { + ${tw`border-neutral-500 bg-neutral-800`}; + } } `; const EditSubuserModal = forwardRef(({ subuser, ...props }, ref) => { const { values, isSubmitting, setFieldValue } = useFormikContext(); + const [ canEditUser ] = usePermissions([ 'user.update' ]); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); return ( -

{subuser ? `Modify permissions for ${subuser.email}` : 'Create new subuser'}

+

+ {subuser ? + `${canEditUser ? 'Modify' : 'View'} permissions for ${subuser.email}` + : + 'Create new subuser' + } +

{!subuser &&
@@ -50,13 +63,14 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr />
} -
+
{Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => (

{key}

+ {canEditUser && { @@ -78,6 +92,7 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr } }} /> + }
} className={index !== 0 ? 'mt-4' : undefined} @@ -87,9 +102,11 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr

{Object.keys(permissions[key].keys).map((pkey, index) => (
@@ -98,6 +115,7 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr name={'permissions'} value={`${key}.${pkey}`} className={'w-5 h-5 mr-2'} + disabled={!canEditUser} />
@@ -115,11 +133,13 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr ))}
-
- -
+ +
+ +
+
); }); diff --git a/resources/scripts/components/server/users/UserRow.tsx b/resources/scripts/components/server/users/UserRow.tsx index d419e8fb1..d12b7aef1 100644 --- a/resources/scripts/components/server/users/UserRow.tsx +++ b/resources/scripts/components/server/users/UserRow.tsx @@ -7,6 +7,7 @@ import EditSubuserModal from '@/components/server/users/EditSubuserModal'; import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons/faUnlockAlt'; import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock'; import classNames from 'classnames'; +import Can from '@/components/elements/Can'; interface Props { subuser: Subuser; @@ -58,7 +59,9 @@ export default ({ subuser }: Props) => { > - + + +
); }; diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index 082859c3d..e2361684f 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -8,6 +8,7 @@ import UserRow from '@/components/server/users/UserRow'; import FlashMessageRender from '@/components/FlashMessageRender'; import getServerSubusers from '@/api/server/users/getServerSubusers'; import { httpErrorToHuman } from '@/api/http'; +import Can from '@/components/elements/Can'; export default () => { const [ loading, setLoading ] = useState(true); @@ -53,9 +54,11 @@ export default () => { )) } -
- -
+ +
+ +
+
); }; diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 79048f778..9d531b52c 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -5,7 +5,7 @@ export function bytesToHuman (bytes: number): string { const i = Math.floor(Math.log(bytes) / Math.log(1000)); // @ts-ignore - return `${(bytes / Math.pow(1000, i)).toFixed(2) * 1} ${['Bytes', 'kB', 'MB', 'GB', 'TB'][i]}`; + return `${(bytes / Math.pow(1000, i)).toFixed(2) * 1} ${[ 'Bytes', 'kB', 'MB', 'GB', 'TB' ][i]}`; } export const bytesToMegabytes = (bytes: number) => Math.floor(bytes / 1000 / 1000); diff --git a/resources/scripts/plugins/useDeepMemo.ts b/resources/scripts/plugins/useDeepMemo.ts new file mode 100644 index 000000000..0ad6bb7df --- /dev/null +++ b/resources/scripts/plugins/useDeepMemo.ts @@ -0,0 +1,12 @@ +import { useRef } from 'react'; +import isEqual from 'lodash-es/isEqual'; + +export const useDeepMemo = (fn: () => T, key: K): T => { + const ref = useRef<{ key: K, value: T }>(); + + if (!ref.current || !isEqual(key, ref.current.key)) { + ref.current = { key, value: fn() }; + } + + return ref.current.value; +}; diff --git a/resources/scripts/plugins/usePermissions.ts b/resources/scripts/plugins/usePermissions.ts new file mode 100644 index 000000000..02f57ebde --- /dev/null +++ b/resources/scripts/plugins/usePermissions.ts @@ -0,0 +1,22 @@ +import { ServerContext } from '@/state/server'; +import { useDeepMemo } from '@/plugins/useDeepMemo'; + +export const usePermissions = (action: string | string[]): boolean[] => { + const userPermissions = ServerContext.useStoreState(state => state.server.permissions); + + return useDeepMemo(() => { + return (Array.isArray(action) ? action : [ action ]) + .map(permission => ( + // Allows checking for any permission matching a name, for example files.* + // will return if the user has any permission under the file.XYZ namespace. + ( + permission.endsWith('.*') && + permission !== 'websocket.*' && + userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0 + ) || + // Otherwise just check if the entire permission exists in the array or not. + userPermissions.indexOf(permission) >= 0 + ), + ); + }, [ action, userPermissions ]); +};