Dsf 248 permissions add view checks to permissions

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8600
GitOrigin-RevId: f877085e96cc75d1ba784095ef5a6701ddad9fcc
This commit is contained in:
Erik Magnusson 2023-04-19 10:28:23 +02:00 committed by hasura-bot
parent f93eed713f
commit 56beb705b2
23 changed files with 255 additions and 4 deletions

View File

@ -0,0 +1,26 @@
import { DataSource } from '../../DataSource';
import { useHttpClient } from '../../Network';
import { useQuery } from 'react-query';
export const useIsTableView = ({
dataSourceName,
table,
}: {
dataSourceName: string;
table: unknown;
}) => {
const httpClient = useHttpClient();
return useQuery({
queryKey: [dataSourceName, table, 'isView'],
queryFn: async () => {
const isView = await DataSource(httpClient).getIsTableView({
dataSourceName,
table,
httpClient,
});
return isView;
},
refetchOnWindowFocus: false,
});
};

View File

@ -13,6 +13,7 @@ import {
import { getTableRows } from '../postgres/query';
import { runSQL } from '../api';
import { postgresCapabilities } from '../common/capabilities';
import { getIsTableView } from './introspection/getIsTableView';
export type AlloyDbTable = { name: string; schema: string };
@ -47,6 +48,7 @@ export const alloy: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas,
getIsTableView,
},
query: {
getTableRows,

View File

@ -0,0 +1,30 @@
import { runSQL } from '../../api';
import { GetIsTableViewProps } from '../../types';
import { AlloyDbTable } from '../index';
export const getIsTableView = async ({
dataSourceName,
table,
httpClient,
}: GetIsTableViewProps) => {
const { schema, name } = table as AlloyDbTable;
const sql = `
SELECT TABLE_NAME
FROM information_schema.views
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${name}';`;
const views = await runSQL({
source: {
name: dataSourceName,
kind: 'postgres',
},
sql: sql,
readOnly: true,
httpClient,
});
if (Array.isArray(views?.result)) return views?.result?.length > 1;
return false;
};

View File

@ -38,6 +38,7 @@ export const bigquery: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView: async () => Feature.NotImplemented,
},
query: {
getTableRows,

View File

@ -9,6 +9,7 @@ import {
getFKRelationships,
getTablesListAsTree,
getSupportedOperators,
getIsTableView,
} from './introspection';
import { getTableRows } from './query';
import { postgresCapabilities } from '../common/capabilities';
@ -80,6 +81,7 @@ export const citus: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView,
},
query: {
getTableRows,

View File

@ -0,0 +1,30 @@
import { runSQL } from '../../api';
import { GetIsTableViewProps } from '../../types';
import { CitusTable } from '../index';
export const getIsTableView = async ({
dataSourceName,
table,
httpClient,
}: GetIsTableViewProps) => {
const { schema, name } = table as CitusTable;
const sql = `
SELECT TABLE_NAME
FROM information_schema.views
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${name}';`;
const views = await runSQL({
source: {
name: dataSourceName,
kind: 'postgres',
},
sql: sql,
readOnly: true,
httpClient,
});
if (Array.isArray(views?.result)) return views?.result?.length > 1;
return false;
};

View File

@ -2,3 +2,4 @@ export { getTableColumns } from './getTableColumns';
export { getFKRelationships } from './getFKRelationships';
export { getTablesListAsTree } from './getTablesListAsTree';
export { getSupportedOperators } from './getSupportedOperators';
export { getIsTableView } from './getIsTableView';

View File

@ -9,6 +9,7 @@ import {
getFKRelationships,
getTablesListAsTree,
getSupportedOperators,
getIsTableView,
} from './introspection';
import { getTableRows } from './query';
import { postgresCapabilities } from '../common/capabilities';
@ -78,6 +79,7 @@ export const cockroach: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView,
},
query: {
getTableRows,

View File

@ -0,0 +1,30 @@
import { runSQL } from '../../api';
import { GetIsTableViewProps } from '../../types';
import { CockroachDBTable } from '../index';
export const getIsTableView = async ({
dataSourceName,
table,
httpClient,
}: GetIsTableViewProps) => {
const { schema, name } = table as CockroachDBTable;
const sql = `
SELECT TABLE_NAME
FROM information_schema.views
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${name}';`;
const views = await runSQL({
source: {
name: dataSourceName,
kind: 'postgres',
},
sql: sql,
readOnly: true,
httpClient,
});
if (Array.isArray(views?.result)) return views?.result?.length > 1;
return false;
};

View File

@ -1,3 +1,4 @@
export { getIsTableView } from './getIsTableView';
export { getTableColumns } from './getTableColumns';
export { getFKRelationships } from './getFKRelationships';
export { getTablesListAsTree } from './getTablesListAsTree';

View File

@ -17,6 +17,7 @@ export const defaultDatabaseProps: Database = {
getTablesListAsTree: async () => Feature.NotImplemented,
getSupportedOperators: async () => Feature.NotImplemented,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView: async () => Feature.NotImplemented,
},
query: {
getTableRows: async () => Feature.NotImplemented,

View File

@ -32,6 +32,7 @@ export const gdc: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView: async () => Feature.NotImplemented,
},
query: {
getTableRows,

View File

@ -23,6 +23,7 @@ import type {
GetTablesListAsTreeProps,
GetTrackableTablesProps,
GetVersionProps,
GetIsTableViewProps,
// Property,
IntrospectedTable,
Operator,
@ -135,6 +136,9 @@ export type Database = {
getDatabaseSchemas: (
props: GetDatabaseSchemaProps
) => Promise<string[] | Feature.NotImplemented>;
getIsTableView: (
props: GetIsTableViewProps
) => Promise<boolean | Feature.NotImplemented>;
};
query?: {
getTableRows: (
@ -550,4 +554,30 @@ export const DataSource = (httpClient: AxiosInstance) => ({
return database.modify;
},
getIsTableView: async ({
dataSourceName,
table,
httpClient,
}: {
dataSourceName: string;
table: Table;
} & NetworkArgs) => {
const database = await getDatabaseMethods({ dataSourceName, httpClient });
if (!database) return false;
const introspection = database.introspection;
if (!introspection) return false;
const isView = await introspection.getIsTableView({
dataSourceName,
httpClient,
table,
});
if (isView === Feature.NotImplemented) return false;
return isView;
},
});

View File

@ -15,6 +15,7 @@ import {
getSupportedOperators,
getTableColumns,
getTablesListAsTree,
getIsTableView,
} from './introspection';
import { getTableRows } from './query';
@ -81,6 +82,7 @@ export const mssql: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas,
getIsTableView,
},
modify: {
defaultQueryRoot: async () => Feature.NotImplemented,

View File

@ -0,0 +1,30 @@
import { runSQL } from '../../api';
import { GetIsTableViewProps } from '../../types';
import { MssqlTable } from '../index';
export const getIsTableView = async ({
dataSourceName,
table,
httpClient,
}: GetIsTableViewProps) => {
const { schema, name } = table as MssqlTable;
const sql = `
SELECT TABLE_NAME
FROM information_schema.views
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${name}';`;
const views = await runSQL({
source: {
name: dataSourceName,
kind: 'postgres',
},
sql: sql,
readOnly: true,
httpClient,
});
if (Array.isArray(views?.result)) return views?.result?.length > 1;
return false;
};

View File

@ -3,3 +3,4 @@ export { getFKRelationships } from './getFKRelationships';
export { getTablesListAsTree } from './getTablesListAsTree';
export { getSupportedOperators } from './getSupportedOperators';
export { getDatabaseSchemas } from './getDatabaseSchemas';
export { getIsTableView } from './getIsTableView';

View File

@ -20,6 +20,7 @@ export const mysql: Database = {
getTablesListAsTree: async () => Feature.NotImplemented,
getSupportedOperators: async () => Feature.NotImplemented,
getDatabaseSchemas: async () => Feature.NotImplemented,
getIsTableView: async () => Feature.NotImplemented,
},
query: {
getTableRows: async () => Feature.NotImplemented,

View File

@ -16,6 +16,7 @@ import {
getTableColumns,
getTablesListAsTree,
getTrackableTables,
getIsTableView,
} from './introspection';
import { getTableRows } from './query';
@ -58,6 +59,7 @@ export const postgres: Database = {
getTablesListAsTree,
getSupportedOperators,
getDatabaseSchemas,
getIsTableView,
},
query: {
getTableRows,

View File

@ -0,0 +1,30 @@
import { runSQL } from '../../api';
import { GetIsTableViewProps } from '../../types';
import { PostgresTable } from '../index';
export const getIsTableView = async ({
dataSourceName,
table,
httpClient,
}: GetIsTableViewProps) => {
const { schema, name } = table as PostgresTable;
const sql = `
SELECT TABLE_NAME
FROM information_schema.views
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${name}';`;
const views = await runSQL({
source: {
name: dataSourceName,
kind: 'postgres',
},
sql: sql,
readOnly: true,
httpClient,
});
if (Array.isArray(views?.result)) return views?.result?.length > 1;
return false;
};

View File

@ -34,7 +34,7 @@ export const getTableColumns = async ({
const { schema, name } = table as PostgresTable;
const sql = `
SELECT
SELECT
column_name, data_type, is_nullable
FROM
information_schema.columns

View File

@ -5,3 +5,4 @@ export { getFKRelationships } from './getFKRelationships';
export { getTablesListAsTree } from './getTablesListAsTree';
export { getSupportedOperators } from './getSupportedOperators';
export { getDatabaseSchemas } from './getDatabaseSchemas';
export { getIsTableView } from './getIsTableView';

View File

@ -178,3 +178,8 @@ export type ChangeDatabaseSchemaProps = {
dataSourceName: string;
schemaName: string;
} & NetworkArgs;
export type GetIsTableViewProps = {
dataSourceName: string;
table: Table;
httpClient: NetworkArgs['httpClient'];
};

View File

@ -9,10 +9,27 @@ import { TableMachine } from './hooks';
import { useDriverCapabilities } from '../../Data/hooks/useDriverCapabilities';
import { Capabilities } from '@hasura/dc-api-types';
import { getDriversSupportedQueryTypes } from './utils/getDriversSupportedQueryTypes';
import { useIsTableView } from '../../Data/hooks/useIsTableView';
const queryType = ['insert', 'select', 'update', 'delete'] as const;
type QueryType = (typeof queryType)[number];
const getIsColumnEditable = (
roleName: string,
isView: boolean | undefined,
driverSupportedQueries: string[],
permissionType: string
) => {
if (roleName === 'admin') return false;
if (isView) {
return permissionType === 'select';
}
if (driverSupportedQueries.includes(permissionType)) return true;
return false;
};
interface ViewPermissionsNoteProps {
viewsSupported: boolean;
supportedQueryTypes: QueryType[];
@ -72,6 +89,8 @@ export const PermissionsTable: React.FC<PermissionsTableProps> = ({
const [state, send] = machine;
const { data: isView } = useIsTableView({ dataSourceName, table });
if (isLoading)
return (
<div>
@ -128,9 +147,12 @@ export const PermissionsTable: React.FC<PermissionsTableProps> = ({
{permissionTypes.map(({ permissionType, access }) => {
// TODO: add checks to see what permissions are supported by each db
const isEditable =
driverSupportedQueries.includes(permissionType) ||
roleName !== 'admin';
const isEditable = getIsColumnEditable(
roleName,
isView,
driverSupportedQueries,
permissionType
);
if (isNewRole) {
return (