mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: add hook useEnabledRoleFromAllowList
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5645 GitOrigin-RevId: 9484f4512839b3790f56110800dab590c996eece
This commit is contained in:
parent
d5b356c53f
commit
7773687528
@ -0,0 +1 @@
|
||||
export * from './useEnabledRolesFromAllowList';
|
@ -0,0 +1,11 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { rest } from 'msw';
|
||||
import { metadata } from './metadata';
|
||||
|
||||
const baseUrl = 'http://localhost:8080';
|
||||
|
||||
export const handlers = (url = baseUrl) => [
|
||||
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
|
||||
return res(ctx.json(metadata));
|
||||
}),
|
||||
];
|
@ -0,0 +1,106 @@
|
||||
import { MetadataResponse } from '@/features/MetadataAPI';
|
||||
|
||||
export const metadata: MetadataResponse = {
|
||||
resource_version: 2,
|
||||
metadata: {
|
||||
version: 3,
|
||||
inherited_roles: [],
|
||||
sources: [
|
||||
{
|
||||
name: 'default',
|
||||
kind: 'postgres',
|
||||
tables: [
|
||||
{
|
||||
table: { name: 'test', schema: 'public' },
|
||||
insert_permissions: [
|
||||
{ role: 'manager', permission: { check: {}, columns: [] } },
|
||||
{ role: 'users', permission: { check: {}, columns: ['id'] } },
|
||||
],
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
connection_info: {
|
||||
use_prepared_statements: true,
|
||||
database_url: {
|
||||
from_env: 'HASURA_GRAPHQL_DATABASE_URL',
|
||||
},
|
||||
isolation_level: 'read-committed',
|
||||
pool_settings: {
|
||||
connection_lifetime: 600,
|
||||
retries: 1,
|
||||
idle_timeout: 180,
|
||||
max_connections: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
remote_schemas: [
|
||||
{
|
||||
name: 'RS1',
|
||||
definition: {
|
||||
url: 'https://graphql-pokemon2.vercel.app',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
role: 'rs_role',
|
||||
definition: {
|
||||
schema:
|
||||
'schema { query: Query }\n\ntype Attack { damage: Int\n name: String\n type: String\n}\n\ntype Pokemon { attacks: PokemonAttack\n classification: String\n evolutionRequirements: PokemonEvolutionRequirement\n evolutions: [Pokemon]\n fleeRate: Float\n height: PokemonDimension\n id: ID!\n image: String\n maxCP: Int\n maxHP: Int\n name: String\n number: String\n resistant: [String]\n types: [String]\n weaknesses: [String]\n weight: PokemonDimension\n}\n\ntype PokemonAttack { fast: [Attack]\n special: [Attack]\n}\n\ntype PokemonDimension { maximum: String\n minimum: String\n}\n\ntype PokemonEvolutionRequirement { amount: Int\n name: String\n}\n\ntype Query { pokemon(id: String @preset(value: "X-Hasura-User-Id"), name: String @preset(value: "X-Hasura-User-Id")): Pokemon\n pokemons(first: Int!): [Pokemon]\n query: Query\n}',
|
||||
},
|
||||
remote_schema_name: '',
|
||||
comment: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
query_collections: [
|
||||
{
|
||||
name: 'allowed-queries',
|
||||
definition: {
|
||||
queries: [
|
||||
{
|
||||
name: 'introspection query',
|
||||
query:
|
||||
'query IntrospectionQuery {\n __schema {\n queryType { name }\n mutationType { name }\n subscriptionType { name }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n }\n\n fragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n }\n\n fragment InputValue on __InputValue {\n name\n description\n type { ...TypeRef }\n defaultValue\n }\n\n fragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
allowlist: [
|
||||
{
|
||||
collection: 'allowed-queries',
|
||||
scope: {
|
||||
global: false,
|
||||
roles: ['manager'],
|
||||
},
|
||||
},
|
||||
{
|
||||
collection: 'admin-only-queries',
|
||||
scope: {
|
||||
global: false,
|
||||
roles: ['users'],
|
||||
},
|
||||
},
|
||||
{
|
||||
collection: 'rest-endpoint',
|
||||
scope: {
|
||||
global: false,
|
||||
roles: ['users', 'manager'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const metadata_with_no_query_collections: MetadataResponse = {
|
||||
resource_version: 48,
|
||||
metadata: {
|
||||
version: 3,
|
||||
sources: [],
|
||||
remote_schemas: [],
|
||||
inherited_roles: [],
|
||||
},
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
|
||||
import { useEnabledRolesFromAllowList } from './useEnabledRolesFromAllowList';
|
||||
import { handlers } from './mock/handlers.mocks';
|
||||
|
||||
const UseEnabledRolesFromAllowList: React.FC = () => {
|
||||
const { data, isLoading, isError } =
|
||||
useEnabledRolesFromAllowList('rest-endpoint');
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
return data ? <ReactJson src={data} /> : null;
|
||||
};
|
||||
|
||||
export const Primary: Story = args => {
|
||||
return <UseEnabledRolesFromAllowList {...args} />;
|
||||
};
|
||||
|
||||
Primary.args = {
|
||||
collectionName: 'rest-endpoint',
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'hooks/Allow List Permission/useCreateNewRolePermission',
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
} as Meta;
|
@ -0,0 +1,55 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { metadata, metadata_with_no_query_collections } from './mock/metadata';
|
||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||
import { useEnabledRolesFromAllowList } from '../../hooks/AllowListPermissions/useEnabledRolesFromAllowList';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('useEnabledRolesFromAllowList with valid data', () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.post('/v1/metadata', (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(metadata))
|
||||
)
|
||||
);
|
||||
});
|
||||
test('When useEnabledRolesFromAllowList hook is called with query collection Name, then a valid list of enabled roles are returned', async () => {
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEnabledRolesFromAllowList('allowed-queries'),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForValueToChange(() => result.current.data);
|
||||
|
||||
const roles = result.current.data!;
|
||||
|
||||
expect(roles).toEqual(['manager']);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useEnabledRolesFromAllowList hooks' with no query collections", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.post('/v1/metadata', (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(metadata_with_no_query_collections))
|
||||
)
|
||||
);
|
||||
});
|
||||
test('When useEnabledRolesFromAllowListn is called with an invalid query collection, then empty array should be returned', async () => {
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEnabledRolesFromAllowList('allowed-queries'),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForValueToChange(() => result.current.data);
|
||||
|
||||
const roles = result.current.data!;
|
||||
|
||||
expect(roles).toEqual([]);
|
||||
});
|
||||
});
|
@ -0,0 +1,4 @@
|
||||
import { MetadataSelector, useMetadata } from '@/features/MetadataAPI';
|
||||
|
||||
export const useEnabledRolesFromAllowList = (queryCollectionName: string) =>
|
||||
useMetadata(MetadataSelector.getNewRolePermission(queryCollectionName));
|
@ -271,4 +271,13 @@ export namespace MetadataSelector {
|
||||
);
|
||||
return queryCollectionDefinition?.definition?.queries ?? [];
|
||||
};
|
||||
export const getNewRolePermission =
|
||||
(queryCollectionName: string) => (m: MetadataResponse) => {
|
||||
const queryCollectionDefinition = m.metadata?.allowlist?.find(
|
||||
qs => qs.collection === queryCollectionName
|
||||
);
|
||||
return queryCollectionDefinition?.scope?.global === false
|
||||
? queryCollectionDefinition?.scope?.roles
|
||||
: [];
|
||||
};
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ export function useRoles() {
|
||||
MetadataSelector.getTablesFromAllSources
|
||||
);
|
||||
const { data: remoteSchemas } = useMetadata(d => d.metadata.remote_schemas);
|
||||
const { data: allowlists } = useMetadata(d => d.metadata.allowlist);
|
||||
const { data: securitySettings } = useMetadata(
|
||||
MetadataSelector.getSecuritySettings
|
||||
);
|
||||
@ -37,6 +38,11 @@ export function useRoles() {
|
||||
remoteSchemas?.forEach(remoteSchema => {
|
||||
remoteSchema?.permissions?.forEach(p => roleNames.push(p.role));
|
||||
});
|
||||
allowlists?.forEach(allowlist => {
|
||||
if (allowlist?.scope?.global === false) {
|
||||
allowlist?.scope?.roles?.forEach(role => roleNames.push(role));
|
||||
}
|
||||
});
|
||||
|
||||
Object.entries(securitySettings?.api_limits ?? {}).forEach(
|
||||
([limit, value]) => {
|
||||
|
@ -39,6 +39,7 @@ export const allowedMetadataTypesArr = [
|
||||
'update_remote_schema_remote_relationship',
|
||||
'delete_remote_schema_remote_relationship',
|
||||
'add_remote_schema',
|
||||
'update_scope_of_collection_in_allowlist',
|
||||
'bulk',
|
||||
] as const;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user