mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
console: function permissions UI (#413)
GitOrigin-RevId: ccdfab19751b0d238a4ebcec59ba73a798103ca9
This commit is contained in:
parent
8a68cc6650
commit
8655e6fd3a
@ -139,6 +139,7 @@ have select permissions to the target table of the function.
|
||||
- console: show only compatible postgres functions in computed fields section (close #5155) (#5978)
|
||||
- console: added export data option on browse rows page (close #1438 #5158)
|
||||
- console: add session argument field for computed fields (close #5154) (#5610)
|
||||
- console: add support for function permissions (#413)
|
||||
- cli: add missing global flags for seed command (#5565)
|
||||
- cli: allow seeds as alias for seed command (#5693)
|
||||
- cli: fix action timeouts not being picked up in metadata operations (#6220)
|
||||
|
@ -80,7 +80,12 @@ interface APIPayload {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type QueryEndpoint = 'query' | 'metadata';
|
||||
export const queryEndpoints = {
|
||||
query: 'query',
|
||||
metadata: 'metadata',
|
||||
} as const;
|
||||
|
||||
export type QueryEndpoint = keyof typeof queryEndpoints;
|
||||
|
||||
export const makeDataAPIOptions = (
|
||||
dataApiUrl: string,
|
||||
@ -211,18 +216,26 @@ export const trackCreateFunctionTable = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createSampleTable = () => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
source: 'default',
|
||||
args: {
|
||||
sql: `CREATE TABLE text_result(
|
||||
result text
|
||||
);`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
export const createSampleTable = () => ({
|
||||
type: 'run_sql',
|
||||
source: 'default',
|
||||
args: {
|
||||
sql: 'CREATE TABLE text_result(result text);',
|
||||
cascade: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const dropTableIfExists = (
|
||||
table: { name: string; schema: string },
|
||||
source = 'default'
|
||||
) => ({
|
||||
type: 'run_sql',
|
||||
source,
|
||||
args: {
|
||||
sql: `DROP TABLE IF EXISTS "${table.schema}"."${table.name}";`,
|
||||
cascade: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const getTrackSampleTableQuery = () => {
|
||||
return {
|
||||
|
@ -85,7 +85,7 @@ export const testSessVariable = () => {
|
||||
cy.wait(3000);
|
||||
|
||||
trackFunctionRequest(getTrackFnPayload(fN), ResultType.SUCCESS);
|
||||
cy.wait(1500);
|
||||
cy.wait(5000);
|
||||
|
||||
cy.visit(`data/default/schema/public/functions/${fN}/modify`);
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument-btn`), {
|
||||
@ -148,15 +148,17 @@ export const deleteCustomFunction = () => {
|
||||
|
||||
export const trackVolatileFunction = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(dropTableIfExists({ name: 'text_result', schema: 'public'}), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(1500);
|
||||
cy.wait(5000);
|
||||
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
|
||||
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
|
||||
cy.wait(1500);
|
||||
cy.wait(5000);
|
||||
cy.visit(`data/default/schema/public`);
|
||||
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
|
||||
cy.get(getElementFromAlias('track-as-mutation')).click();
|
||||
cy.wait(500);
|
||||
cy.wait(2000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
|
||||
@ -166,17 +168,19 @@ export const trackVolatileFunction = () => {
|
||||
|
||||
export const trackVolatileFunctionAsQuery = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(dropTableIfExists({ name: 'text_result', schema: 'public'}), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(1500);
|
||||
cy.wait(5000);
|
||||
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
|
||||
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
|
||||
cy.wait(1500);
|
||||
cy.wait(5000);
|
||||
cy.visit(`data/default/schema/public`);
|
||||
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
|
||||
cy.get(getElementFromAlias('track-as-query')).click();
|
||||
cy.wait(100);
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('track-as-query-confirm')).click();
|
||||
cy.wait(500);
|
||||
cy.wait(2000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
|
||||
|
@ -39,6 +39,7 @@ const TableRow = ({
|
||||
>
|
||||
{p.access}
|
||||
{p.editIcon}
|
||||
{p.tooltip}
|
||||
</td>
|
||||
);
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ export interface MainState {
|
||||
serverConfig: {
|
||||
data: {
|
||||
version: string;
|
||||
is_function_permissions_inferred: boolean;
|
||||
is_admin_secret_set: boolean;
|
||||
is_auth_hook_set: boolean;
|
||||
is_jwt_set: boolean;
|
||||
@ -50,6 +51,7 @@ const defaultState: MainState = {
|
||||
serverConfig: {
|
||||
data: {
|
||||
version: '',
|
||||
is_function_permissions_inferred: true,
|
||||
is_admin_secret_set: false,
|
||||
is_auth_hook_set: false,
|
||||
is_jwt_set: false,
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
functionWrapperConnector,
|
||||
permissionsSummaryConnector,
|
||||
ModifyCustomFunction,
|
||||
PermissionCustomFunction,
|
||||
FunctionPermissions,
|
||||
ConnectedDatabaseManagePage,
|
||||
} from '.';
|
||||
|
||||
@ -60,7 +60,7 @@ const makeDataRouter = (
|
||||
>
|
||||
<IndexRedirect to="modify" />
|
||||
<Route path="modify" component={ModifyCustomFunction} />
|
||||
<Route path="permissions" component={PermissionCustomFunction} />
|
||||
<Route path="permissions" component={FunctionPermissions} />
|
||||
</Route>
|
||||
<Route
|
||||
path=":schema/tables/:table"
|
||||
|
@ -11,11 +11,10 @@ import { showErrorNotification } from '../Common/Notification';
|
||||
import {
|
||||
fetchDataInit,
|
||||
fetchFunctionInit,
|
||||
// updateSchemaInfo,
|
||||
UPDATE_CURRENT_DATA_SOURCE,
|
||||
UPDATE_CURRENT_SCHEMA,
|
||||
} from './DataActions';
|
||||
import { setDriver } from '../../../dataSources/dataSources';
|
||||
import { setDriver } from '../../../dataSources';
|
||||
|
||||
type Params = {
|
||||
source?: string;
|
||||
|
@ -1,164 +0,0 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Link } from 'react-router';
|
||||
import { push } from 'react-router-redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import CommonTabLayout from '../../../../Common/Layout/CommonTabLayout/CommonTabLayout';
|
||||
import tabInfo from '../Modify/tabInfo';
|
||||
import globals from '../../../../../Globals';
|
||||
import { fetchCustomFunction } from '../customFunctionReducer';
|
||||
import {
|
||||
updateSchemaInfo,
|
||||
UPDATE_CURRENT_SCHEMA,
|
||||
fetchFunctionInit,
|
||||
setTable,
|
||||
} from '../../DataActions';
|
||||
import { NotFoundError } from '../../../../Error/PageNotFound';
|
||||
import {
|
||||
getSchemaBaseRoute,
|
||||
getFunctionBaseRoute,
|
||||
getTablePermissionsRoute,
|
||||
} from '../../../../Common/utils/routesUtils';
|
||||
import { pageTitle } from '../Modify/ModifyCustomFunction';
|
||||
import styles from '../Modify/ModifyCustomFunction.scss';
|
||||
|
||||
class Permission extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
funcFetchCompleted: false,
|
||||
};
|
||||
this.urlWithSource = `/data/${props.currentSource}`;
|
||||
this.urlWithSchema = `/data/${props.currentSource}/schema/${props.currentSchema}`;
|
||||
this.prefixUrl = globals.urlPrefix + this.urlWithSource;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { functionName, schema } = this.props.params;
|
||||
|
||||
if (!functionName) {
|
||||
this.props.dispatch(push(this.prefixUrl));
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
this.props
|
||||
.dispatch(
|
||||
fetchCustomFunction(functionName, schema, this.props.currentSource)
|
||||
)
|
||||
.then(() => {
|
||||
this.setState({ funcFetchCompleted: true });
|
||||
}),
|
||||
]);
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
functionSchema: schema,
|
||||
functionName,
|
||||
setOffTable,
|
||||
setOffTableSchema,
|
||||
} = this.props.functions;
|
||||
|
||||
if (this.state.funcFetchCompleted && !functionName) {
|
||||
// throw a 404 exception
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const { dispatch, currentSource } = this.props;
|
||||
|
||||
const functionBaseUrl = getFunctionBaseRoute(
|
||||
schema,
|
||||
currentSource,
|
||||
functionName
|
||||
);
|
||||
const permissionTableUrl = getTablePermissionsRoute(
|
||||
setOffTableSchema,
|
||||
currentSource,
|
||||
setOffTable,
|
||||
true
|
||||
);
|
||||
|
||||
const breadCrumbs = [
|
||||
{
|
||||
title: 'Data',
|
||||
url: this.urlWithSource,
|
||||
},
|
||||
{
|
||||
title: 'Schema',
|
||||
url: this.urlWithSchema,
|
||||
},
|
||||
{
|
||||
title: schema,
|
||||
url: getSchemaBaseRoute(schema),
|
||||
},
|
||||
];
|
||||
|
||||
const onClickPerm = () => {
|
||||
if (schema !== setOffTableSchema) {
|
||||
Promise.all([
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_SCHEMA,
|
||||
currentSchema: setOffTableSchema,
|
||||
}),
|
||||
dispatch(updateSchemaInfo()),
|
||||
dispatch(fetchFunctionInit()),
|
||||
dispatch(setTable(setOffTable)),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
if (functionName) {
|
||||
breadCrumbs.push({
|
||||
title: functionName,
|
||||
url: functionBaseUrl,
|
||||
});
|
||||
breadCrumbs.push({
|
||||
title: 'Permission',
|
||||
url: '',
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className={'col-xs-8' + ' ' + styles.modifyWrapper}>
|
||||
<Helmet
|
||||
title={`Permission ${pageTitle} - ${functionName} - ${pageTitle}s | Hasura`}
|
||||
/>
|
||||
<CommonTabLayout
|
||||
appPrefix={this.urlWithSource}
|
||||
currentTab="permissions"
|
||||
heading={functionName}
|
||||
tabsInfo={tabInfo}
|
||||
breadCrumbs={breadCrumbs}
|
||||
baseUrl={functionBaseUrl}
|
||||
showLoader={false}
|
||||
testPrefix={'functions'}
|
||||
/>
|
||||
<br />
|
||||
<p>
|
||||
Permissions defined for the SETOF table, <b>{setOffTable}</b>, are
|
||||
applicable to the data returned by this function.
|
||||
<br />
|
||||
<br />
|
||||
See <b>{setOffTable}</b> permissions{' '}
|
||||
<Link
|
||||
to={permissionTableUrl}
|
||||
data-test="custom-function-permission-link"
|
||||
onClick={onClickPerm}
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
currentSchema: state.tables.currentSchema,
|
||||
});
|
||||
|
||||
const permissionConnector = connect(mapStateToProps);
|
||||
const ConnectedPermission = permissionConnector(Permission);
|
||||
|
||||
export default ConnectedPermission;
|
@ -0,0 +1,232 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { Link, RouteComponentProps } from 'react-router';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
import globals from '../../../../../Globals';
|
||||
import { ReduxState } from '../../../../../types';
|
||||
import CommonTabLayout from '../../../../Common/Layout/CommonTabLayout/CommonTabLayout';
|
||||
import { mapDispatchToPropsEmpty } from '../../../../Common/utils/reactUtils';
|
||||
import {
|
||||
getFunctionBaseRoute,
|
||||
getSchemaBaseRoute,
|
||||
getTablePermissionsRoute,
|
||||
} from '../../../../Common/utils/routesUtils';
|
||||
import { NotFoundError } from '../../../../Error/PageNotFound';
|
||||
import {
|
||||
fetchFunctionInit,
|
||||
setTable,
|
||||
updateSchemaInfo,
|
||||
UPDATE_CURRENT_SCHEMA,
|
||||
} from '../../DataActions';
|
||||
import { fetchCustomFunction } from '../customFunctionReducer';
|
||||
import tabInfo from '../Modify/tabInfo';
|
||||
import PermissionsEditor from './PermissionsEditor';
|
||||
import { getFunctionSelector } from '../../../../../metadata/selector';
|
||||
|
||||
import styles from '../Modify/ModifyCustomFunction.scss';
|
||||
|
||||
const PermissionServerFlagNote = ({ isEditable = false }) =>
|
||||
!isEditable ? (
|
||||
<>
|
||||
<br />
|
||||
<p>
|
||||
Function will be exposed automatically if there are SELECT permissions
|
||||
for the role. To expose query functions to roles explicitly, set{' '}
|
||||
<code>HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS=false</code> on the
|
||||
server (
|
||||
<a
|
||||
href="https://hasura.io/docs/1.0/graphql/core/api-reference/schema-metadata-api/custom-functions.html#api-custom-functions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read More
|
||||
</a>
|
||||
)
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<br />
|
||||
<p>
|
||||
The function will be exposed to the role only if the SELECT Permission
|
||||
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,
|
||||
currentSchema,
|
||||
currentFunction,
|
||||
currentFunctionInfo,
|
||||
dispatch,
|
||||
functions,
|
||||
serverConfig,
|
||||
}) => {
|
||||
const isFunctionPermissionsInferred =
|
||||
serverConfig.is_function_permissions_inferred ?? true;
|
||||
|
||||
const isFunctionExposedAsMutation =
|
||||
currentFunctionInfo(currentFunction, currentSchema)?.configuration
|
||||
?.exposed_as === 'mutation' ?? false;
|
||||
|
||||
const isPermissionsEditable = checkPermissionEditState(
|
||||
isFunctionPermissionsInferred,
|
||||
isFunctionExposedAsMutation
|
||||
);
|
||||
|
||||
const [funcFetchCompleted, updateFunctionFetchState] = useState(false);
|
||||
const urlWithSource = `/data/${currentDataSource}`;
|
||||
const urlWithSchema = `/data/${currentDataSource}/schema/${currentSchema}`;
|
||||
const prefixURL = `${globals.urlPrefix}${urlWithSource}`;
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentFunction) {
|
||||
dispatch(push(prefixURL));
|
||||
}
|
||||
dispatch(
|
||||
fetchCustomFunction(currentFunction, currentSchema, currentDataSource)
|
||||
).then(() => {
|
||||
updateFunctionFetchState(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (funcFetchCompleted && !currentFunction) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const {
|
||||
functionSchema: schema,
|
||||
functionName,
|
||||
setOffTable,
|
||||
setOffTableSchema,
|
||||
} = functions;
|
||||
const functionBaseURL = getFunctionBaseRoute(
|
||||
schema,
|
||||
currentDataSource,
|
||||
functionName
|
||||
);
|
||||
const permissionTableURL = getTablePermissionsRoute(
|
||||
setOffTableSchema,
|
||||
currentDataSource,
|
||||
setOffTable,
|
||||
true
|
||||
);
|
||||
|
||||
const breadCrumbs = [
|
||||
{
|
||||
title: 'Data',
|
||||
url: urlWithSchema,
|
||||
},
|
||||
{
|
||||
title: 'Schema',
|
||||
url: urlWithSchema,
|
||||
},
|
||||
{
|
||||
title: schema,
|
||||
url: getSchemaBaseRoute(schema),
|
||||
},
|
||||
];
|
||||
|
||||
const onClickPerm = () => {
|
||||
if (schema !== setOffTableSchema) {
|
||||
Promise.all([
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_SCHEMA,
|
||||
currentSchema: setOffTableSchema,
|
||||
}),
|
||||
dispatch(updateSchemaInfo()),
|
||||
dispatch(fetchFunctionInit()),
|
||||
dispatch(setTable(setOffTable)),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
if (functionName) {
|
||||
breadCrumbs.push({
|
||||
title: functionName,
|
||||
url: functionBaseURL,
|
||||
});
|
||||
breadCrumbs.push({
|
||||
title: 'Permission',
|
||||
url: '',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`col-xs-8 ${styles.modifyWrapper}`}>
|
||||
<Helmet title={`Permission Custom Function - ${functionName} | Hasura`} />
|
||||
<CommonTabLayout
|
||||
appPrefix={urlWithSource}
|
||||
currentTab="permissions"
|
||||
heading={functionName}
|
||||
tabsInfo={tabInfo}
|
||||
breadCrumbs={breadCrumbs}
|
||||
baseUrl={functionBaseURL}
|
||||
showLoader={false}
|
||||
testPrefix="functions"
|
||||
/>
|
||||
<br />
|
||||
<p>
|
||||
Permissions will be inherited from the SELECT permissions of the
|
||||
referenced table (
|
||||
<Link
|
||||
to={permissionTableURL}
|
||||
data-test="custom-function-permission-link"
|
||||
onClick={onClickPerm}
|
||||
>
|
||||
<b>{setOffTable}</b>
|
||||
</Link>
|
||||
) by default.
|
||||
</p>
|
||||
<PermissionServerFlagNote isEditable={isPermissionsEditable} />
|
||||
<br />
|
||||
<PermissionsEditor
|
||||
currentFunctionName={currentFunction}
|
||||
currentSchema={currentSchema}
|
||||
isPermissionsEditable={isPermissionsEditable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type OwnProps = RouteComponentProps<
|
||||
{
|
||||
functionName: string;
|
||||
schema: string;
|
||||
},
|
||||
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 functionsPermissionsConnector = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToPropsEmpty
|
||||
);
|
||||
type ReduxProps = ConnectedProps<typeof functionsPermissionsConnector>;
|
||||
const FunctionPermissions = functionsPermissionsConnector(Permissions);
|
||||
|
||||
export default FunctionPermissions;
|
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../../../../../Common/Button';
|
||||
import { ButtonProps } from '../../../../../Common/Button/Button';
|
||||
import styles from '../../../../../Common/Permissions/PermissionStyles.scss';
|
||||
|
||||
type PermissionsActionButtonProps = {
|
||||
onClick: () => void;
|
||||
color: ButtonProps['color'];
|
||||
text: string;
|
||||
};
|
||||
const PermissionsActionButton: React.FC<PermissionsActionButtonProps> = ({
|
||||
onClick,
|
||||
color,
|
||||
text,
|
||||
}) => (
|
||||
<Button color={color} className={styles.add_mar_right} onClick={onClick}>
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
type PermissionEditorProps = {
|
||||
role: string;
|
||||
isEditing: boolean;
|
||||
closeFn: () => void;
|
||||
saveFn: () => void;
|
||||
removeFn: () => void;
|
||||
isPermSet: boolean;
|
||||
};
|
||||
const PermissionEditor: React.FC<PermissionEditorProps> = ({
|
||||
role,
|
||||
isEditing,
|
||||
closeFn,
|
||||
saveFn,
|
||||
removeFn,
|
||||
isPermSet,
|
||||
}) =>
|
||||
isEditing ? (
|
||||
<div className={styles.activeEdit}>
|
||||
<div className={styles.add_mar_bottom}>
|
||||
This function is {!isPermSet ? 'not' : null} allowed for role:{' '}
|
||||
<b>{role}</b>
|
||||
<br />
|
||||
Click {!isPermSet ? '"Save"' : '"Remove"'} if you wish to{' '}
|
||||
{!isPermSet ? 'allow' : 'disallow'} it.
|
||||
</div>
|
||||
{!isPermSet ? (
|
||||
<PermissionsActionButton onClick={saveFn} color="yellow" text="Save" />
|
||||
) : (
|
||||
<PermissionsActionButton onClick={removeFn} color="red" text="Remove" />
|
||||
)}
|
||||
<PermissionsActionButton onClick={closeFn} color="white" text="Cancel" />
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
export default PermissionEditor;
|
@ -0,0 +1,304 @@
|
||||
import React, { useReducer } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import {
|
||||
getFunctions,
|
||||
getCurrentTableInformation,
|
||||
rolesSelector,
|
||||
} from '../../../../../../metadata/selector';
|
||||
import { permissionsSymbols } from '../../../../../Common/Permissions/PermissionSymbols';
|
||||
import PermTableBody from '../../../../../Common/Permissions/TableBody';
|
||||
import PermTableHeader from '../../../../../Common/Permissions/TableHeader';
|
||||
import { mapDispatchToPropsEmpty } from '../../../../../Common/utils/reactUtils';
|
||||
import {
|
||||
dropFunctionPermission,
|
||||
setFunctionPermission,
|
||||
} from '../../customFunctionReducer';
|
||||
import PermissionEditor from './PermissionEditor';
|
||||
import { ReduxState } from '../../../../../../types';
|
||||
import {
|
||||
FunctionPermission,
|
||||
SelectPermissionEntry,
|
||||
} from '../../../../../../metadata/types';
|
||||
|
||||
import styles from '../../../../../Common/Permissions/PermissionStyles.scss';
|
||||
import Tooltip from '../../../../../Common/Tooltip/Tooltip';
|
||||
|
||||
const getFunctionPermissions = (
|
||||
allFunctions: InjectedProps['allFunctions'],
|
||||
currentFunctionSchema: string,
|
||||
currentFunctionName: string
|
||||
) =>
|
||||
allFunctions.find(
|
||||
fn =>
|
||||
fn.function_name === currentFunctionName &&
|
||||
fn.function_schema === currentFunctionSchema
|
||||
)?.permissions;
|
||||
|
||||
const findFunctionPermissions = (
|
||||
allPermissions: FunctionPermission[] | undefined | null,
|
||||
userRole: string
|
||||
) => {
|
||||
if (!allPermissions) {
|
||||
return false;
|
||||
}
|
||||
return allPermissions.find(permRole => permRole.role === userRole);
|
||||
};
|
||||
|
||||
const getRoleQueryPermissionSymbol = (
|
||||
allPermissions: FunctionPermission[] | undefined | null,
|
||||
permissionRole: string,
|
||||
selectRoles: SelectPermissionEntry[] | null
|
||||
) => {
|
||||
console.log(allPermissions, permissionRole, selectRoles);
|
||||
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;
|
||||
}
|
||||
return permissionsSymbols.noAccess;
|
||||
}
|
||||
|
||||
const existingPerm = findFunctionPermissions(allPermissions, permissionRole);
|
||||
if (existingPerm) {
|
||||
return permissionsSymbols.fullAccess;
|
||||
}
|
||||
return permissionsSymbols.noAccess;
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
isEditing: false,
|
||||
role: '',
|
||||
};
|
||||
type ReducerState = typeof initialState;
|
||||
type ReducerAction = { type: string; role: string };
|
||||
|
||||
const PERM_UPDATE_OPEN_STATE = 'PERM_UPDATE_OPEN_STATE';
|
||||
const PERM_UPDATE_CLOSE_STATE = 'PERM_UPDATE_CLOSE_STATE';
|
||||
|
||||
const functionsPermissionsReducer = (
|
||||
state: ReducerState,
|
||||
action: ReducerAction
|
||||
) => {
|
||||
switch (action && action.type) {
|
||||
case PERM_UPDATE_OPEN_STATE:
|
||||
return {
|
||||
isEditing: true,
|
||||
role: action?.role,
|
||||
};
|
||||
case PERM_UPDATE_CLOSE_STATE:
|
||||
return {
|
||||
...state,
|
||||
isEditing: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const PermissionsLegend = () => (
|
||||
<div className={styles.permissionsLegend}>
|
||||
<span className={styles.permissionsLegendValue}>
|
||||
{permissionsSymbols.fullAccess} : allowed
|
||||
</span>
|
||||
<span className={styles.permissionsLegendValue}>
|
||||
{permissionsSymbols.noAccess} : not allowed
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const EditIcon = () => (
|
||||
<span className={styles.editPermsIcon}>
|
||||
<i className="fa fa-pencil" aria-hidden="true" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const PermissionsTableBody: React.FC<PermissionTableProps> = ({
|
||||
allPermissions,
|
||||
allRoles,
|
||||
permCloseEdit,
|
||||
permOpenEdit,
|
||||
permissionsEditState,
|
||||
readOnlyMode,
|
||||
isEditable,
|
||||
selectRoles,
|
||||
}) => {
|
||||
const queryTypes = ['Permission'];
|
||||
const { isEditing, role: permEditRole } = permissionsEditState;
|
||||
|
||||
const getQueryTypes = (role: string) =>
|
||||
queryTypes.map(queryType => {
|
||||
const dispatchOpenEdit = (r: string) => () => {
|
||||
if (r) {
|
||||
permOpenEdit(r);
|
||||
}
|
||||
};
|
||||
|
||||
const isCurrEdit = isEditing && permEditRole === role;
|
||||
let editIcon = null;
|
||||
let className = '';
|
||||
let onClick = () => {};
|
||||
const tooltip =
|
||||
!isEditable && role !== 'admin' ? (
|
||||
<Tooltip message="Forbidden from edits since function permissions are inferred" />
|
||||
) : null;
|
||||
|
||||
if (role !== 'admin' && !readOnlyMode && isEditable) {
|
||||
editIcon = <EditIcon />;
|
||||
if (isCurrEdit) {
|
||||
onClick = permCloseEdit;
|
||||
className += ` ${styles.currEdit}`;
|
||||
} else {
|
||||
className += styles.clickableCell;
|
||||
onClick = dispatchOpenEdit(role);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
permType: queryType,
|
||||
className,
|
||||
editIcon,
|
||||
onClick,
|
||||
dataTest: `${role}-${queryType}`,
|
||||
access: getRoleQueryPermissionSymbol(allPermissions, role, selectRoles),
|
||||
tooltip,
|
||||
};
|
||||
});
|
||||
|
||||
const roleList = ['admin', ...allRoles];
|
||||
const rolePermissions = roleList.map(r => ({
|
||||
roleName: r,
|
||||
permTypes: getQueryTypes(r),
|
||||
}));
|
||||
|
||||
return (
|
||||
<PermTableBody
|
||||
rolePermissions={rolePermissions}
|
||||
dispatchRoleNameChange={() => {}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type PermissionTableProps = {
|
||||
permCloseEdit: () => void;
|
||||
permOpenEdit: (role: string) => void;
|
||||
permissionsEditState: ReducerState;
|
||||
allRoles: string[];
|
||||
readOnlyMode: boolean;
|
||||
allPermissions: FunctionPermission[] | undefined | null;
|
||||
isEditable: boolean;
|
||||
selectRoles: SelectPermissionEntry[] | null;
|
||||
};
|
||||
const PermissionsTable: React.FC<PermissionTableProps> = props => (
|
||||
<>
|
||||
<PermissionsLegend />
|
||||
<table className={`table table-bordered ${styles.permissionsTable}`}>
|
||||
<PermTableHeader headings={['Role', 'Permission']} />
|
||||
<PermissionsTableBody {...props} />
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
|
||||
interface PermissionsProps extends InjectedProps {
|
||||
currentSchema: string;
|
||||
currentFunctionName: string;
|
||||
isPermissionsEditable: boolean;
|
||||
}
|
||||
const Permissions: React.FC<PermissionsProps> = ({
|
||||
allFunctions,
|
||||
dispatch,
|
||||
currentSchema,
|
||||
currentFunctionName,
|
||||
allRoles,
|
||||
readOnlyMode = false,
|
||||
getTableSelectPermissions,
|
||||
isPermissionsEditable,
|
||||
functions,
|
||||
}) => {
|
||||
const [permissionsEditState, permissionsDispatch] = useReducer(
|
||||
functionsPermissionsReducer,
|
||||
initialState
|
||||
);
|
||||
const { isEditing, role: permEditRole } = permissionsEditState;
|
||||
|
||||
const permCloseEdit = () => {
|
||||
permissionsDispatch({
|
||||
type: PERM_UPDATE_CLOSE_STATE,
|
||||
role: '',
|
||||
});
|
||||
};
|
||||
const permOpenEdit = (role: string) => {
|
||||
permissionsDispatch({
|
||||
type: PERM_UPDATE_OPEN_STATE,
|
||||
role,
|
||||
});
|
||||
};
|
||||
|
||||
const allPermissions = getFunctionPermissions(
|
||||
allFunctions,
|
||||
currentSchema,
|
||||
currentFunctionName
|
||||
);
|
||||
|
||||
const { setOffTable, setOffTableSchema } = functions;
|
||||
|
||||
const selectRoles = !isPermissionsEditable
|
||||
? getTableSelectPermissions(setOffTable, setOffTableSchema)
|
||||
: null;
|
||||
|
||||
const isPermSet =
|
||||
getRoleQueryPermissionSymbol(allPermissions, permEditRole, selectRoles) ===
|
||||
permissionsSymbols.fullAccess;
|
||||
|
||||
const saveFunc = () => {
|
||||
dispatch(setFunctionPermission(permEditRole, permCloseEdit));
|
||||
};
|
||||
const removeFunc = () => {
|
||||
dispatch(dropFunctionPermission(permEditRole, permCloseEdit));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PermissionsTable
|
||||
permCloseEdit={permCloseEdit}
|
||||
permOpenEdit={permOpenEdit}
|
||||
permissionsEditState={permissionsEditState}
|
||||
allRoles={allRoles}
|
||||
readOnlyMode={readOnlyMode}
|
||||
allPermissions={allPermissions}
|
||||
isEditable={isPermissionsEditable}
|
||||
selectRoles={selectRoles}
|
||||
/>
|
||||
<div className={`${styles.add_mar_bottom}`}>
|
||||
{!readOnlyMode && (
|
||||
<PermissionEditor
|
||||
saveFn={saveFunc}
|
||||
removeFn={removeFunc}
|
||||
closeFn={permCloseEdit}
|
||||
role={permEditRole}
|
||||
isEditing={isEditing}
|
||||
isPermSet={isPermSet}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: ReduxState) => ({
|
||||
allRoles: rolesSelector(state),
|
||||
allFunctions: getFunctions(state),
|
||||
functions: state.functions,
|
||||
readOnlyMode: state.main.readOnlyMode,
|
||||
getTableSelectPermissions: getCurrentTableInformation(state),
|
||||
});
|
||||
|
||||
const permissionsUIConnector = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToPropsEmpty
|
||||
);
|
||||
type InjectedProps = ConnectedProps<typeof permissionsUIConnector>;
|
||||
export default permissionsUIConnector(Permissions);
|
@ -1,22 +1,19 @@
|
||||
/* Import default State */
|
||||
|
||||
import { functionData } from './customFunctionState';
|
||||
|
||||
import Endpoints, { globalCookiePolicy } from '../../../../Endpoints';
|
||||
|
||||
import requestAction from '../../../../utils/requestAction';
|
||||
import dataHeaders from '../Common/Headers';
|
||||
|
||||
import _push from '../push';
|
||||
import { getSchemaBaseRoute } from '../../../Common/utils/routesUtils';
|
||||
import { dataSource } from '../../../../dataSources';
|
||||
import Endpoints, { globalCookiePolicy } from '../../../../Endpoints';
|
||||
import { exportMetadata } from '../../../../metadata/actions';
|
||||
import { getRunSqlQuery } from '../../../Common/utils/v1QueryUtils';
|
||||
import {
|
||||
getUntrackFunctionQuery,
|
||||
createFunctionPermissionQuery,
|
||||
dropFunctionPermissionQuery,
|
||||
getTrackFunctionQuery,
|
||||
getUntrackFunctionQuery,
|
||||
} from '../../../../metadata/queryUtils';
|
||||
import requestAction from '../../../../utils/requestAction';
|
||||
import { getSchemaBaseRoute } from '../../../Common/utils/routesUtils';
|
||||
import { getRunSqlQuery } from '../../../Common/utils/v1QueryUtils';
|
||||
import { makeRequest } from '../../RemoteSchema/Actions';
|
||||
import dataHeaders from '../Common/Headers';
|
||||
import _push from '../push';
|
||||
import { functionData } from './customFunctionState';
|
||||
|
||||
/* Constants */
|
||||
|
||||
@ -41,6 +38,15 @@ const SESSVAR_CUSTOM_FUNCTION_ADD_FAIL =
|
||||
const SESSVAR_CUSTOM_FUNCTION_ADD_SUCCESS =
|
||||
'@customFunction/SESSVAR_CUSTOM_FUNCTION_ADD_SUCCESS';
|
||||
|
||||
const PERMISSION_CUSTOM_FUNCTION_SET_SUCCESS =
|
||||
'@customFunction/PERMISSION_CUSTOM_FUNCTION_SET_SUCCESS';
|
||||
const PERMISSION_CUSTOM_FUNCTION_SET_FAIL =
|
||||
'@customFunction/PERMISSION_CUSTOM_FUNCTION_SET_FAIL';
|
||||
const PERMISSION_CUSTOM_FUNCTION_DROP_SUCCESS =
|
||||
'@customFunction/PERMISSION_CUSTOM_FUNCTION_DROP_SUCCESS';
|
||||
const PERMISSION_CUSTOM_FUNCTION_DROP_FAIL =
|
||||
'@customFunction/PERMISSION_CUSTOM_FUNCTION_DROP_FAIL';
|
||||
|
||||
/* Action creators */
|
||||
const fetchCustomFunction = (functionName, schema, source) => {
|
||||
return (dispatch, getState) => {
|
||||
@ -103,11 +109,12 @@ const deleteFunction = () => (dispatch, getState) => {
|
||||
const successMsg = 'Function deleted';
|
||||
const errorMsg = 'Deleting function failed';
|
||||
|
||||
const customOnSuccess = () =>
|
||||
const customOnSuccess = () => {
|
||||
dispatch({ type: DELETING_CUSTOM_FUNCTION });
|
||||
dispatch(_push(getSchemaBaseRoute(currentSchema, source)));
|
||||
};
|
||||
const customOnError = () => dispatch({ type: DELETE_CUSTOM_FUNCTION_FAIL });
|
||||
|
||||
dispatch({ type: DELETING_CUSTOM_FUNCTION });
|
||||
return dispatch(
|
||||
makeRequest(
|
||||
sqlUpQueries,
|
||||
@ -146,6 +153,7 @@ const unTrackCustomFunction = () => {
|
||||
const errorMsg = 'Delete custom function failed';
|
||||
|
||||
const customOnSuccess = () => {
|
||||
dispatch({ type: UNTRACKING_CUSTOM_FUNCTION });
|
||||
dispatch(_push(getSchemaBaseRoute(currentSchema, currentDataSource)));
|
||||
dispatch({ type: RESET });
|
||||
dispatch(exportMetadata());
|
||||
@ -156,7 +164,6 @@ const unTrackCustomFunction = () => {
|
||||
]);
|
||||
};
|
||||
|
||||
dispatch({ type: UNTRACKING_CUSTOM_FUNCTION });
|
||||
return dispatch(
|
||||
makeRequest(
|
||||
[payload],
|
||||
@ -230,13 +237,13 @@ const updateSessVar = session_argument => {
|
||||
const errorMsg = 'Updating Session argument variable failed';
|
||||
|
||||
const customOnSuccess = () => {
|
||||
dispatch({ type: SESSVAR_CUSTOM_FUNCTION_REQUEST });
|
||||
dispatch(exportMetadata());
|
||||
};
|
||||
const customOnError = error => {
|
||||
dispatch({ type: SESSVAR_CUSTOM_FUNCTION_ADD_FAIL, data: error });
|
||||
};
|
||||
|
||||
dispatch({ type: SESSVAR_CUSTOM_FUNCTION_REQUEST });
|
||||
return dispatch(
|
||||
makeRequest(
|
||||
upQuery.args,
|
||||
@ -252,16 +259,105 @@ const updateSessVar = session_argument => {
|
||||
};
|
||||
};
|
||||
|
||||
/* */
|
||||
const setFunctionPermission = (userRole, onSuccessCb) => (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
const { currentDataSource, currentSchema } = getState().tables;
|
||||
const { functionName } = getState().functions;
|
||||
const currentFunction = { schema: currentSchema, name: functionName };
|
||||
|
||||
const upQuery = createFunctionPermissionQuery(
|
||||
currentDataSource,
|
||||
currentFunction,
|
||||
userRole
|
||||
);
|
||||
const downQuery = dropFunctionPermissionQuery(
|
||||
currentDataSource,
|
||||
currentFunction,
|
||||
userRole
|
||||
);
|
||||
|
||||
const migrationName = `set_permission_role_${userRole}_function_${functionName}`;
|
||||
const requestMsg = `Setting permisions for ${userRole} role on ${functionName}`;
|
||||
const successMsg = `Successfully set permissions for ${userRole}`;
|
||||
const errorMsg = `Failed to set permissions for ${userRole}`;
|
||||
|
||||
const customOnSuccess = () => {
|
||||
dispatch({ type: PERMISSION_CUSTOM_FUNCTION_SET_SUCCESS });
|
||||
dispatch(exportMetadata(onSuccessCb));
|
||||
};
|
||||
|
||||
const customOnError = err => {
|
||||
dispatch({ type: PERMISSION_CUSTOM_FUNCTION_SET_FAIL, data: err });
|
||||
};
|
||||
|
||||
return dispatch(
|
||||
makeRequest(
|
||||
[upQuery],
|
||||
[downQuery],
|
||||
migrationName,
|
||||
customOnSuccess,
|
||||
customOnError,
|
||||
requestMsg,
|
||||
successMsg,
|
||||
errorMsg
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const dropFunctionPermission = (userRole, onSuccessCb) => (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
const { currentDataSource, currentSchema } = getState().tables;
|
||||
const { functionName } = getState().functions;
|
||||
const currentFunction = { schema: currentSchema, name: functionName };
|
||||
|
||||
const upQuery = dropFunctionPermissionQuery(
|
||||
currentDataSource,
|
||||
currentFunction,
|
||||
userRole
|
||||
);
|
||||
const downQuery = createFunctionPermissionQuery(
|
||||
currentDataSource,
|
||||
currentFunction,
|
||||
userRole
|
||||
);
|
||||
|
||||
const migrationName = `drop_permission_role_${userRole}_function_${functionName}`;
|
||||
const requestMsg = `Dropping permisions for ${userRole} role on ${functionName}`;
|
||||
const successMsg = `Successfully dropped permissions for ${userRole}`;
|
||||
const errorMsg = `Failed to drop permissions for ${userRole}`;
|
||||
|
||||
const customOnSuccess = () => {
|
||||
dispatch({ type: PERMISSION_CUSTOM_FUNCTION_DROP_SUCCESS });
|
||||
dispatch(exportMetadata(onSuccessCb));
|
||||
};
|
||||
|
||||
const customOnError = err => {
|
||||
dispatch({ type: PERMISSION_CUSTOM_FUNCTION_DROP_FAIL, data: err });
|
||||
};
|
||||
|
||||
return dispatch(
|
||||
makeRequest(
|
||||
[upQuery],
|
||||
[downQuery],
|
||||
migrationName,
|
||||
customOnSuccess,
|
||||
customOnError,
|
||||
requestMsg,
|
||||
successMsg,
|
||||
errorMsg
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/* Reducer */
|
||||
|
||||
const customFunctionReducer = (state = functionData, action) => {
|
||||
switch (action.type) {
|
||||
case RESET:
|
||||
return {
|
||||
...functionData,
|
||||
};
|
||||
return functionData;
|
||||
case FETCHING_INDIV_CUSTOM_FUNCTION:
|
||||
return {
|
||||
...state,
|
||||
@ -300,7 +396,6 @@ const customFunctionReducer = (state = functionData, action) => {
|
||||
isDeleting: true,
|
||||
isError: null,
|
||||
};
|
||||
|
||||
case UNTRACK_CUSTOM_FUNCTION_FAIL:
|
||||
return {
|
||||
...state,
|
||||
@ -331,20 +426,42 @@ const customFunctionReducer = (state = functionData, action) => {
|
||||
isUpdating: false,
|
||||
isError: null,
|
||||
};
|
||||
default:
|
||||
case PERMISSION_CUSTOM_FUNCTION_SET_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isPermissionSet: true,
|
||||
isError: null,
|
||||
};
|
||||
case PERMISSION_CUSTOM_FUNCTION_SET_FAIL:
|
||||
return {
|
||||
...state,
|
||||
isPermissionSet: false,
|
||||
isError: action.data,
|
||||
};
|
||||
case PERMISSION_CUSTOM_FUNCTION_DROP_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isPermissionDrop: true,
|
||||
isError: null,
|
||||
};
|
||||
case PERMISSION_CUSTOM_FUNCTION_DROP_FAIL:
|
||||
return {
|
||||
...state,
|
||||
isPermissionDrop: false,
|
||||
isError: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
/* End of it */
|
||||
|
||||
export {
|
||||
RESET,
|
||||
deleteFunction,
|
||||
dropFunctionPermission,
|
||||
fetchCustomFunction,
|
||||
RESET,
|
||||
setFunctionPermission,
|
||||
unTrackCustomFunction,
|
||||
updateSessVar,
|
||||
deleteFunction,
|
||||
};
|
||||
export default customFunctionReducer;
|
||||
|
@ -6,6 +6,8 @@ const asyncState = {
|
||||
isFetching: false,
|
||||
isUpdating: false,
|
||||
isFetchError: null,
|
||||
isPermissionSet: false,
|
||||
isPermissionDrop: false,
|
||||
};
|
||||
|
||||
const functionData = {
|
||||
@ -13,6 +15,7 @@ const functionData = {
|
||||
functionSchema: '',
|
||||
functionDefinition: '',
|
||||
configuration: {},
|
||||
permissions: {},
|
||||
setOffTable: '',
|
||||
setOffTableSchema: '',
|
||||
inputArgNames: [],
|
||||
|
@ -25,7 +25,7 @@ import dataRouterUtils from './DataRouter';
|
||||
import dataReducer from './DataReducer';
|
||||
import functionWrapperConnector from './Function/FunctionWrapper';
|
||||
import ModifyCustomFunction from './Function/Modify/ModifyCustomFunction';
|
||||
import PermissionCustomFunction from './Function/Permission/Permission';
|
||||
import FunctionPermissions from './Function/Permission/Permission';
|
||||
import ConnectedDatabaseManagePage from './Schema/ManageDatabase';
|
||||
|
||||
export {
|
||||
@ -49,6 +49,6 @@ export {
|
||||
dataReducer,
|
||||
functionWrapperConnector,
|
||||
ModifyCustomFunction,
|
||||
PermissionCustomFunction,
|
||||
FunctionPermissions,
|
||||
ConnectedDatabaseManagePage,
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
CustomTypes,
|
||||
HasuraMetadataV2,
|
||||
QualifiedTable,
|
||||
QualifiedFunction,
|
||||
} from './types';
|
||||
import { transformHeaders } from '../components/Common/Headers/utils';
|
||||
import { LocalEventTriggerState } from '../components/Services/Events/EventTriggers/state';
|
||||
@ -82,6 +83,8 @@ export const metadataQueryTypes = [
|
||||
'get_event_invocations',
|
||||
'get_scheduled_events',
|
||||
'delete_scheduled_event',
|
||||
'create_function_permission',
|
||||
'drop_function_permission',
|
||||
] as const;
|
||||
|
||||
export type MetadataQueryType = typeof metadataQueryTypes[number];
|
||||
@ -753,3 +756,23 @@ export const invokeManualTriggerQuery = (
|
||||
args: InvokeManualTriggerArgs,
|
||||
source: string
|
||||
) => getMetadataQuery('invoke_event_trigger', source, args);
|
||||
|
||||
export const createFunctionPermissionQuery = (
|
||||
source: string,
|
||||
func: QualifiedFunction,
|
||||
role: string
|
||||
) =>
|
||||
getMetadataQuery('create_function_permission', source, {
|
||||
function: func,
|
||||
role,
|
||||
});
|
||||
|
||||
export const dropFunctionPermissionQuery = (
|
||||
source: string,
|
||||
func: QualifiedFunction,
|
||||
role: string
|
||||
) =>
|
||||
getMetadataQuery('drop_function_permission', source, {
|
||||
function: func,
|
||||
role,
|
||||
});
|
||||
|
@ -155,7 +155,16 @@ export const getTablesInfoSelector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const getFunctions = createSelector(
|
||||
// TODO?: make it generic i.e to fetch any property from all tables
|
||||
export const getCurrentTableInformation = createSelector(
|
||||
getTables,
|
||||
tables => (tableName: string, tableSchema: string) =>
|
||||
tables?.find(
|
||||
t => tableName === t.table.name && tableSchema === t.table.schema
|
||||
)?.select_permissions ?? []
|
||||
);
|
||||
|
||||
export const getFunctions = createSelector(
|
||||
getDataSourceMetadata,
|
||||
source =>
|
||||
source?.functions?.map(f => ({
|
||||
@ -163,6 +172,7 @@ const getFunctions = createSelector(
|
||||
function_name: f.function.name,
|
||||
function_schema: f.function.schema,
|
||||
configuration: f.configuration,
|
||||
permissions: f?.permissions,
|
||||
})) || []
|
||||
);
|
||||
|
||||
|
@ -152,6 +152,11 @@ export interface FunctionConfiguration {
|
||||
session_argument?: string;
|
||||
}
|
||||
|
||||
export interface FunctionPermission {
|
||||
role: string;
|
||||
definition?: Record<string, any>;
|
||||
}
|
||||
|
||||
// ////////////////////////////
|
||||
// #endregion CUSTOM FUNCTIONS
|
||||
// /////////////////////////////
|
||||
@ -865,8 +870,12 @@ export interface MetadataDataSource {
|
||||
};
|
||||
tables: TableEntry[];
|
||||
functions?: Array<{
|
||||
function: { schema: string; name: string };
|
||||
configuration: Record<string, any>;
|
||||
function: QualifiedFunction;
|
||||
configuration?: {
|
||||
exposed_as?: 'mutation' | 'query';
|
||||
session_argument?: string;
|
||||
};
|
||||
permissions?: FunctionPermission[];
|
||||
}>;
|
||||
query_collections?: QueryCollectionEntry[];
|
||||
allowlist?: AllowList[];
|
||||
|
@ -46,6 +46,7 @@ Sample response
|
||||
|
||||
{
|
||||
"version": "v1.0.0-beta.3",
|
||||
"is_function_permissions_inferred": true,
|
||||
"is_admin_secret_set": true,
|
||||
"is_auth_hook_set": false,
|
||||
"is_jwt_set": true,
|
||||
|
@ -11,6 +11,7 @@ import Data.Aeson.TH
|
||||
|
||||
import qualified Hasura.GraphQL.Execute.LiveQuery.Options as LQ
|
||||
|
||||
import Hasura.RQL.Types (FunctionPermissionsCtx)
|
||||
import Hasura.Server.Auth
|
||||
import Hasura.Server.Auth.JWT
|
||||
import Hasura.Server.Version (HasVersion, Version, currentVersion)
|
||||
@ -27,21 +28,23 @@ $(deriveToJSON hasuraJSON ''JWTInfo)
|
||||
|
||||
data ServerConfig
|
||||
= ServerConfig
|
||||
{ scfgVersion :: !Version
|
||||
, scfgIsAdminSecretSet :: !Bool
|
||||
, scfgIsAuthHookSet :: !Bool
|
||||
, scfgIsJwtSet :: !Bool
|
||||
, scfgJwt :: !(Maybe JWTInfo)
|
||||
, scfgIsAllowListEnabled :: !Bool
|
||||
, scfgLiveQueries :: !LQ.LiveQueriesOptions
|
||||
, scfgConsoleAssetsDir :: !(Maybe Text)
|
||||
{ scfgVersion :: !Version
|
||||
, scfgIsFunctionPermissionsInferred :: !FunctionPermissionsCtx
|
||||
, scfgIsAdminSecretSet :: !Bool
|
||||
, scfgIsAuthHookSet :: !Bool
|
||||
, scfgIsJwtSet :: !Bool
|
||||
, scfgJwt :: !(Maybe JWTInfo)
|
||||
, scfgIsAllowListEnabled :: !Bool
|
||||
, scfgLiveQueries :: !LQ.LiveQueriesOptions
|
||||
, scfgConsoleAssetsDir :: !(Maybe Text)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
$(deriveToJSON hasuraJSON ''ServerConfig)
|
||||
|
||||
runGetConfig :: HasVersion => AuthMode -> Bool -> LQ.LiveQueriesOptions -> Maybe Text -> ServerConfig
|
||||
runGetConfig am isAllowListEnabled liveQueryOpts consoleAssetsDir = ServerConfig
|
||||
runGetConfig :: HasVersion => FunctionPermissionsCtx -> AuthMode -> Bool -> LQ.LiveQueriesOptions -> Maybe Text -> ServerConfig
|
||||
runGetConfig functionPermsCtx am isAllowListEnabled liveQueryOpts consoleAssetsDir = ServerConfig
|
||||
currentVersion
|
||||
functionPermsCtx
|
||||
(isAdminSecretSet am)
|
||||
(isAuthHookSet am)
|
||||
(isJWTSet am)
|
||||
|
@ -688,7 +688,7 @@ configApiGetHandler serverCtx@ServerCtx{..} consoleAssetsDir =
|
||||
Spock.get "v1alpha1/config" $ mkSpockAction serverCtx encodeQErr id $
|
||||
mkGetHandler $ do
|
||||
onlyAdmin
|
||||
let res = runGetConfig scAuthMode scEnableAllowlist
|
||||
let res = runGetConfig scFunctionPermsCtx scAuthMode scEnableAllowlist
|
||||
(EL._lqsOptions $ scLQState) consoleAssetsDir
|
||||
return $ JSONResp $ HttpResponse (encJFromJValue res) []
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user