mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
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:
parent
4702ba514a
commit
2b0b4ec3a4
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
/>
|
||||
),
|
||||
};
|
@ -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"
|
||||
/>
|
||||
),
|
||||
};
|
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user