console: improve functions permissions messages in case of missing table's select permissions

Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com>
GitOrigin-RevId: 52007786113622c15bd719cbea6b041c27ebd675
This commit is contained in:
Sameer Kolhar 2021-05-05 20:24:34 +05:30 committed by hasura-bot
parent 4702ba514a
commit 2b0b4ec3a4
6 changed files with 172 additions and 102 deletions

View File

@ -22,11 +22,6 @@
width: 250px;
}
//.permissionDelete {
// cursor: pointer;
// margin-left: 10px;
//}
.bulkSelect {
margin-right: 10px !important;
}
@ -68,17 +63,12 @@
font-size: 12px;
margin-right: 5px;
margin-bottom: 20px;
//float: right;
//clear: both;
.permissionsLegendValue {
margin-right: 15px;
}
}
.newRoleInput {
}
.fkSelect {
max-width: 200px;
}
@ -219,9 +209,14 @@
color: green;
}
.permissionSymbolPAW {
color: #ffc627;
}
.permissionSymbolNA,
.permissionSymbolFA,
.permissionSymbolPA {
.permissionSymbolPA,
.permissionSymbolPAW {
font-size: 16px;
}

View File

@ -1,23 +0,0 @@
import React from 'react';
import styles from './PermissionStyles.scss';
export const permissionsSymbols = {
fullAccess: (
<i
className={'fa fa-check ' + styles.permissionSymbolFA}
aria-hidden="true"
/>
),
noAccess: (
<i
className={'fa fa-times ' + styles.permissionSymbolNA}
aria-hidden="true"
/>
),
partialAccess: (
<i
className={'fa fa-filter ' + styles.permissionSymbolPA}
aria-hidden="true"
/>
),
};

View File

@ -0,0 +1,29 @@
import React from 'react';
import styles from './PermissionStyles.scss';
export const permissionsSymbols = {
fullAccess: (
<i
className={`fa fa-check ${styles.permissionSymbolFA}`}
aria-hidden="true"
/>
),
noAccess: (
<i
className={`fa fa-times ${styles.permissionSymbolNA}`}
aria-hidden="true"
/>
),
partialAccess: (
<i
className={`fa fa-filter ${styles.permissionSymbolPA}`}
aria-hidden="true"
/>
),
partialAccessWarning: (
<i
className={`fa fa-exclamation-triangle ${styles.permissionSymbolPAW}`}
aria-hidden="true"
/>
),
};

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import Helmet from 'react-helmet';
import { connect, ConnectedProps } from 'react-redux';
import { Link, RouteComponentProps } from 'react-router';
@ -23,9 +23,10 @@ import {
import { fetchCustomFunction } from '../customFunctionReducer';
import tabInfo from '../Modify/tabInfo';
import PermissionsEditor from './PermissionsEditor';
import { getFunctionSelector } from '../../../../../metadata/selector';
import styles from '../Modify/ModifyCustomFunction.scss';
import { PGFunction } from '../../../../../dataSources/services/postgresql/types';
import { getFunctionSelector } from '../../../../../metadata/selector';
const PermissionServerFlagNote = ({ isEditable = false }) =>
!isEditable ? (
@ -50,23 +51,12 @@ const PermissionServerFlagNote = ({ isEditable = false }) =>
<>
<br />
<p>
The function will be exposed to the role only if the SELECT Permission
are enabled for the role.
The function will be exposed to the role if SELECT permissions are
enabled and function permissions are enabled for the role.
</p>
</>
);
const checkPermissionEditState = (
functionPermsInferred: boolean,
funcExposedAsMutation: boolean
) => {
if (!functionPermsInferred) {
// case when INFERRED_PERMISSIONS=false on server
return true;
}
return functionPermsInferred && funcExposedAsMutation;
};
interface PermissionsProps extends ReduxProps {}
const Permissions: React.FC<PermissionsProps> = ({
currentDataSource,
@ -76,18 +66,35 @@ const Permissions: React.FC<PermissionsProps> = ({
dispatch,
functions,
serverConfig,
allFunctions,
}) => {
const isFunctionPermissionsInferred =
serverConfig.is_function_permissions_inferred ?? true;
const isPermissionsEditable: boolean = useMemo(() => {
const databaseFunction: PGFunction | undefined = allFunctions.find(
(f: PGFunction) =>
f.function_name === currentFunction &&
f.function_schema === currentSchema
);
const isFunctionExposedAsMutation =
currentFunctionInfo(currentFunction, currentSchema)?.configuration
?.exposed_as === 'mutation' ?? false;
const isFunctionExposedAsMutation =
currentFunctionInfo(currentFunction, currentSchema)?.configuration
?.exposed_as === 'mutation' ?? false;
const isPermissionsEditable = checkPermissionEditState(
isFunctionPermissionsInferred,
isFunctionExposedAsMutation
);
if (
databaseFunction?.function_type === 'VOLATILE' &&
(isFunctionExposedAsMutation ||
!serverConfig.is_function_permissions_inferred)
) {
return true;
}
return !serverConfig.is_function_permissions_inferred;
}, [
allFunctions,
currentFunction,
currentFunctionInfo,
currentSchema,
serverConfig.is_function_permissions_inferred,
]);
const [funcFetchCompleted, updateFunctionFetchState] = useState(false);
const urlWithSource = `/data/${currentDataSource}`;
@ -212,16 +219,15 @@ type OwnProps = RouteComponentProps<
unknown
>;
const mapStateToProps = (state: ReduxState, ownProps: OwnProps) => {
return {
currentSchema: ownProps.params.schema,
currentFunction: ownProps.params.functionName,
currentFunctionInfo: getFunctionSelector(state),
currentDataSource: state.tables.currentDataSource,
functions: state.functions,
serverConfig: state.main?.serverConfig?.data ?? {},
};
};
const mapStateToProps = (state: ReduxState, ownProps: OwnProps) => ({
currentSchema: ownProps.params.schema,
currentFunction: ownProps.params.functionName,
currentDataSource: state.tables.currentDataSource,
functions: state.functions,
serverConfig: state.main?.serverConfig?.data ?? {},
allFunctions: state.tables.postgresFunctions,
currentFunctionInfo: getFunctionSelector(state),
});
const functionsPermissionsConnector = connect(
mapStateToProps,
mapDispatchToPropsEmpty

View File

@ -2,6 +2,7 @@ import React from 'react';
import Button from '../../../../../Common/Button';
import { ButtonProps } from '../../../../../Common/Button/Button';
import styles from '../../../../../Common/Permissions/PermissionStyles.scss';
type PermissionsActionButtonProps = {
@ -25,7 +26,8 @@ type PermissionEditorProps = {
closeFn: () => void;
saveFn: () => void;
removeFn: () => void;
isPermSet: boolean;
permissionAccessInMetadata: 'full' | 'no' | 'partial';
table: string;
};
const PermissionEditor: React.FC<PermissionEditorProps> = ({
role,
@ -33,18 +35,34 @@ const PermissionEditor: React.FC<PermissionEditorProps> = ({
closeFn,
saveFn,
removeFn,
isPermSet,
permissionAccessInMetadata,
table,
}) =>
isEditing ? (
<div className={styles.activeEdit}>
<div className={styles.add_mar_bottom}>
This function is {!isPermSet ? 'not' : null} allowed for role:{' '}
<b>{role}</b>
{permissionAccessInMetadata === 'partial' ? (
<>
Partial permissions: please enable <b>select</b> permissions for
table <b>{table}</b> for role <b>{role}</b> if you want the function
exposed.
</>
) : (
<>
This function is{' '}
{permissionAccessInMetadata === 'no' ? 'not' : null} allowed for
role: <b>{role}</b>
</>
)}
<br />
Click {!isPermSet ? '"Save"' : '"Remove"'} if you wish to{' '}
{!isPermSet ? 'allow' : 'disallow'} it.
<br />
<p>
Click {permissionAccessInMetadata === 'no' ? '"Save"' : '"Remove"'} if
you wish to{' '}
{permissionAccessInMetadata === 'no' ? 'allow' : 'disallow'} it.
</p>
</div>
{!isPermSet ? (
{permissionAccessInMetadata === 'no' ? (
<PermissionsActionButton onClick={saveFn} color="yellow" text="Save" />
) : (
<PermissionsActionButton onClick={removeFn} color="red" text="Remove" />

View File

@ -3,7 +3,6 @@ import { connect, ConnectedProps } from 'react-redux';
import {
getFunctions,
// getCurrentTableInformation,
getTableInformation,
rolesSelector,
} from '../../../../../../metadata/selector';
@ -21,9 +20,9 @@ import {
FunctionPermission,
SelectPermissionEntry,
} from '../../../../../../metadata/types';
import Tooltip from '../../../../../Common/Tooltip/Tooltip';
import styles from '../../../../../Common/Permissions/PermissionStyles.scss';
import Tooltip from '../../../../../Common/Tooltip/Tooltip';
const getFunctionPermissions = (
allFunctions: InjectedProps['allFunctions'],
@ -41,7 +40,7 @@ const findFunctionPermissions = (
userRole: string
) => {
if (!allPermissions) {
return false;
return undefined;
}
return allPermissions.find(permRole => permRole.role === userRole);
};
@ -49,24 +48,47 @@ const findFunctionPermissions = (
const getRoleQueryPermissionSymbol = (
allPermissions: FunctionPermission[] | undefined | null,
permissionRole: string,
selectRoles: SelectPermissionEntry[] | null
selectPermissionsForTable: SelectPermissionEntry[] | null,
isEditable: boolean
) => {
if (permissionRole === 'admin') {
return permissionsSymbols.fullAccess;
}
// selectRoles is populated only when the fn is a query otherwise, we pass an empty array
if (selectRoles) {
if (selectRoles.find(sel => sel.role === permissionRole)) {
return permissionsSymbols.fullAccess;
}
let isTableSelectPermissionsEnabled = false;
let isPermissionsEnabledOnMetadata = false;
// Checking if select permissions are there on the reference table
if (
selectPermissionsForTable &&
selectPermissionsForTable.find(
selectPermissionEntry => selectPermissionEntry.role === permissionRole
)
) {
isTableSelectPermissionsEnabled = true;
}
// If permissions are inferred and not editable we only need to know if corresponding table has select permissions
if (!isEditable) {
return isTableSelectPermissionsEnabled
? permissionsSymbols.fullAccess
: permissionsSymbols.noAccess;
}
// Checking if permissions are enabled and visible in the metadata
if (findFunctionPermissions(allPermissions, permissionRole)) {
isPermissionsEnabledOnMetadata = true;
}
if (!isPermissionsEnabledOnMetadata) {
return permissionsSymbols.noAccess;
}
const existingPerm = findFunctionPermissions(allPermissions, permissionRole);
if (existingPerm) {
return permissionsSymbols.fullAccess;
if (isPermissionsEnabledOnMetadata && !isTableSelectPermissionsEnabled) {
return permissionsSymbols.partialAccessWarning;
}
return permissionsSymbols.noAccess;
return permissionsSymbols.fullAccess;
};
const initialState = {
@ -107,9 +129,23 @@ const PermissionsLegend = () => (
<span className={styles.permissionsLegendValue}>
{permissionsSymbols.noAccess} : not allowed
</span>
<span className={styles.permissionsLegendValue}>
{permissionsSymbols.partialAccessWarning} : partial (needs SELECT
permissions on table)
</span>
</div>
);
const getPermissionAccessString = (permissionSymbol: JSX.Element) => {
if (permissionSymbol === permissionsSymbols.fullAccess) {
return 'full';
} else if (permissionSymbol === permissionsSymbols.noAccess) {
return 'no';
}
return 'partial';
};
const EditIcon = () => (
<span className={styles.editPermsIcon}>
<i className="fa fa-pencil" aria-hidden="true" />
@ -163,7 +199,12 @@ const PermissionsTableBody: React.FC<PermissionTableProps> = ({
editIcon,
onClick,
dataTest: `${role}-${queryType}`,
access: getRoleQueryPermissionSymbol(allPermissions, role, selectRoles),
access: getRoleQueryPermissionSymbol(
allPermissions,
role,
selectRoles,
isEditable
),
tooltip,
};
});
@ -216,12 +257,13 @@ const Permissions: React.FC<PermissionsProps> = ({
readOnlyMode = false,
tableSelectPermissions,
isPermissionsEditable,
// functions,
currentTable,
}) => {
const [permissionsEditState, permissionsDispatch] = useReducer(
functionsPermissionsReducer,
initialState
);
const { isEditing, role: permEditRole } = permissionsEditState;
const permCloseEdit = () => {
@ -243,18 +285,19 @@ const Permissions: React.FC<PermissionsProps> = ({
currentFunctionName
);
const selectRoles = !isPermissionsEditable ? tableSelectPermissions : null;
const permissionAccessString = getPermissionAccessString(
getRoleQueryPermissionSymbol(
allPermissions,
permEditRole,
tableSelectPermissions,
isPermissionsEditable
)
);
const isPermSet =
getRoleQueryPermissionSymbol(allPermissions, permEditRole, selectRoles) ===
permissionsSymbols.fullAccess;
const saveFunc = () => {
const saveFunc = () =>
dispatch(setFunctionPermission(permEditRole, permCloseEdit));
};
const removeFunc = () => {
const removeFunc = () =>
dispatch(dropFunctionPermission(permEditRole, permCloseEdit));
};
return (
<>
@ -266,9 +309,9 @@ const Permissions: React.FC<PermissionsProps> = ({
readOnlyMode={readOnlyMode}
allPermissions={allPermissions}
isEditable={isPermissionsEditable}
selectRoles={selectRoles}
selectRoles={tableSelectPermissions}
/>
<div className={`${styles.add_mar_bottom}`}>
<div className={styles.add_mar_bottom}>
{!readOnlyMode && (
<PermissionEditor
saveFn={saveFunc}
@ -276,7 +319,8 @@ const Permissions: React.FC<PermissionsProps> = ({
closeFn={permCloseEdit}
role={permEditRole}
isEditing={isEditing}
isPermSet={isPermSet}
permissionAccessInMetadata={permissionAccessString}
table={currentTable}
/>
)}
</div>
@ -295,6 +339,7 @@ const mapStateToProps = (state: ReduxState) => {
getTableInformation(state)(setOffTable, setOffTableSchema)(
'select_permissions'
) ?? [],
currentTable: setOffTable,
};
};