From dfc2d3979b922aefc71cc54ef5bb1bdb35ec2418 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:21:00 +0100 Subject: [PATCH 01/39] Update AccordionForm documentation --- docs/AccordionForm.md | 350 +++++++++++++++++++++++++++++++++++------- 1 file changed, 297 insertions(+), 53 deletions(-) diff --git a/docs/AccordionForm.md b/docs/AccordionForm.md index 82431935392..895ec642d16 100644 --- a/docs/AccordionForm.md +++ b/docs/AccordionForm.md @@ -84,10 +84,13 @@ Here are all the props you can set on the `` component: | Prop | Required | Type | Default | Description | | ------------------------- | -------- | ----------------- | ------- | ---------------------------------------------------------------------------- | +| `authorizationError` | Optional | `ReactNode` | `null` | The content to display when authorization checks fail | | `autoClose` | Optional | `boolean` | - | Set to `true` to close the current accordion when opening another one. | | `children` | Required | `ReactNode` | - | A list of `` elements. | | `defaultValues` | Optional | `object|function` | - | The default values of the record. | +| `enableAccessControl` | Optional | `boolean` | `false` | Enable checking authorization rights for each panel and input | | `id` | Optional | `string` | - | The id of the underlying `
` tag. | +| `loading` | Optional | `ReactNode` | | The content to display when checking authorizations | | `noValidate` | Optional | `boolean` | - | Set to `true` to disable the browser's default validation. | | `onSubmit` | Optional | `function` | `save` | A callback to call when the form is submitted. | | `sanitize EmptyValues` | Optional | `boolean` | - | Set to `true` to remove empty values from the form state. | @@ -98,6 +101,48 @@ Here are all the props you can set on the `` component: Additional props are passed to `react-hook-form`'s [`useForm` hook](https://react-hook-form.com/docs/useform). +## `authorizationError` + +Content displayed when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + +{% raw %} +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionForm } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const CustomerEdit = () => ( + + + An error occurred while loading your permissions + + } + > + + + + + + + + + + + + + + + +); +``` +{% endraw %} + ## `autoClose` When setting the `` prop, only one accordion remains open at a time. The first accordion is open by default, and when a user opens another one, the current open accordion closes. @@ -165,6 +210,47 @@ export const PostCreate = () => ( **Tip**: React-admin also allows to define default values at the input level. See the [Setting default Values](./Forms.md#default-values) section. +## `enableAccessControl` + +When set to `true`, React-admin will call the `authProvider.canAccess` method for each panel with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.panel.PANEL_ID_OR_LABEL`. For instance: `customers.panel.identity` +- `record`: The current record + +For each panel, react-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionForm } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + + + + +); +``` + +**Tip**: If you only want access control for the panels but not for the inputs, set the `enableAccessControl` prop to `false` on the ``. + ## `id` Normally, a submit button only works when placed inside a `` tag. However, you can place a submit button outside the form if the submit button `form` matches the form `id`. @@ -186,6 +272,43 @@ export const PostCreate = () => ( ); ``` +## `loading` + +Content displayed when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionForm } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const CustomerEdit = () => ( + + + Loading your permissions... + + } + > + + + + + + + + + + + + + + + +); +``` + ## `noValidate` The `` attribute prevents the browser from validating the form. This is useful if you don't want to use the browser's default validation, or if you want to customize the error messages. To set this attribute on the underlying `` tag, set the `noValidate` prop to `true`. @@ -377,11 +500,14 @@ Here are all the props you can set on the `` component: | Prop | Required | Type | Default | Description | | ----------------- | -------- | ----------------------- | ------- | ------------------------------------------------------------------------------------------------ | +| `authorizationError` | Optional | `ReactNode` | `null` | The content to display when authorization checks fail | | `children` | Required | `ReactNode` | - | A list of `` elements | | `defaultExpanded` | Optional | `boolean` | `false` | Set to true to have the accordion expanded by default (except if autoClose = true on the parent) | | `disabled` | Optional | `boolean` | `false` | If true, the accordion will be displayed in a disabled state. | +| `enableAccessControl` | Optional | `boolean` | `false` | Enable checking authorization rights for this panel's inputs | | `id` | Optional | `string` | - | An id for this Accordion to be used in the [`useFormGroup`](./Upgrade.md#useformgroup-hook-returned-state-has-changed) hook and for CSS classes. | | `label` | Required | `string` or `ReactNode` | - | The main label used as the accordion summary. Appears in red when the accordion has errors | +| `loading` | Optional | `ReactNode` | | The content to display when checking authorizations | | `secondary` | Optional | `string` or `ReactNode` | - | The secondary label used as the accordion summary | | `square` | Optional | `boolean` | `false` | If true, rounded corners are disabled. | | `sx` | Optional | `Object` | - | An object containing the MUI style overrides to apply to the root component. | @@ -433,6 +559,8 @@ Here are all the props you can set on the `` component: | Prop | Required | Type | Default | Description | | ------------------ | -------- | ----------------------- | ------- | ------------------------------------------------------------- | +| `accessDenied` | Optional | `Component` | - | The component to use when users don't have the permissions required to access this section. | +| `authorizationError` | Optional | `Component` | - | The component to use when an error occurs while checking permissions. | | `Accordion` | Optional | `Component` | - | The component to use as the accordion. | | `AccordionDetails` | Optional | `Component` | - | The component to use as the accordion details. | | `AccordionSummary` | Optional | `Component` | - | The component to use as the accordion summary. | @@ -440,9 +568,11 @@ Here are all the props you can set on the `` component: | `className` | Optional | `string` | - | A class name to style the underlying `` | | `defaultExpanded` | Optional | `boolean` | `false` | Set to true to have the accordion expanded by default | | `disabled` | Optional | `boolean` | `false` | If true, the accordion will be displayed in a disabled state. | +| `enableAccessControl` | Optional | `boolean` | - | Enable access control to the section and its inputs | | `fullWidth` | Optional | `boolean` | `false` | If true, the Accordion takes the entire form width. | | `id` | Optional | `string` | - | An id for this Accordion to be used for CSS classes. | | `label` | Required | `string` or `ReactNode` | - | The main label used as the accordion summary. | +| `loading` | Optional | `Component` | - | The component to use while checking permissions. | | `secondary` | Optional | `string` or `ReactNode` | - | The secondary label used as the accordion summary | | `square` | Optional | `boolean` | `false` | If true, rounded corners are disabled. | @@ -491,6 +621,173 @@ const CustomerEdit = () => ( ); ``` +### `accessDenied` + +Content displayed when `enableAccessControl` is set to `true` and users don't have access to the section. Defaults to `null`: + +{% raw %} +```tsx +import { ReactNode } from 'react'; +import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionSection } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const AccessDenied = ({ children }: { children: ReactNode }) => ( + + Upgrade to Premium + + } + > + {children} + +) + +const CustomerEdit = () => ( + + + + + You don't have access to the preferences section} + > + + + + + + +); +``` +{% endraw %} + + +### `authorizationError` + +Content displayed when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + +{% raw %} +```tsx +import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionSection } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const AuthorizationError = () => ( + + An error occurred while loading your permissions + +); + +const CustomerEdit = () => ( + + + + + }> + + + + + + +); +``` +{% endraw %} + +### `enableAccessControl` + +When set to `true`, React-admin will call the `authProvider.canAccess` method the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.section.PANEL_ID_OR_LABEL`. For instance: `customers.section.identity` +- `record`: The current record + +React-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +```tsx +import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionSection } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + +); +``` + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +### `loading` + +Content displayed when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { AccordionSection } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const AuthorizationLoading = () => ( + + Loading your permissions... + +); + +const CustomerEdit = () => ( + + + + + + + + }> + + + + + + +); +``` + + ## AutoSave In forms where users may spend a lot of time, it's a good idea to save the form automatically after a few seconds of inactivity. You turn on this feature by using [the `` component](./AutoSave.md). @@ -532,56 +829,3 @@ Note that you **must** set the `` prop to `{ keepDir If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](https://marmelab.com/react-admin/Edit.html#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. Check [the `` component](./AutoSave.md) documentation for more details. - -## Role-Based Access Control (RBAC) - -Fine-grained permissions control can be added by using the [``](./AuthRBAC.md#accordionform), [``](./AuthRBAC.md#accordionform) and [``](./AuthRBAC.md#accordionsection) components provided by the `@react-admin/ra-enterprise` package. - -{% raw %} -```tsx -import { AccordionForm } from '@react-admin/ra-enterprise'; - -const authProvider = { - checkAuth: () => Promise.resolve(), - login: () => Promise.resolve(), - logout: () => Promise.resolve(), - checkError: () => Promise.resolve(), - getPermissions: () =>Promise.resolve([ - // 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - { action: 'write', resource: 'products.panel.description' }, - { action: 'write', resource: 'products.panel.images' }, - // 'products.panel.stock' is missing - ]), -}; - -const ProductEdit = () => ( - - - - - - - - - - - - - - - - { /* delete button not displayed */ } - - -); -``` -{% endraw %} - -Check [the RBAC `` component](./AuthRBAC.md#accordionform) documentation for more details. From 33ae7f588194be8bd5ee76123fde0c67d3ac3712 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:21:20 +0100 Subject: [PATCH 02/39] Update LongForm documentation --- docs/LongForm.md | 296 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 239 insertions(+), 57 deletions(-) diff --git a/docs/LongForm.md b/docs/LongForm.md index f1129a54f80..d3ca5da4515 100644 --- a/docs/LongForm.md +++ b/docs/LongForm.md @@ -88,9 +88,12 @@ Here are all the props you can set on the `` component: | Prop | Required | Type | Default | Description | | ------------------------ | -------- | ----------------- | ------- | ---------------------------------------------------------- | +| `authorizationError` | Optional | `ReactNode` | `null` | The content to display when authorization checks fail | | `children` | Required | `ReactNode` | - | A list of `` elements. | | `defaultValues` | Optional | `object|function` | - | The default values of the record. | +| `enableAccessControl` | Optional | `boolean` | `false` | Enable checking authorization rights for each section and input | | `id` | Optional | `string` | - | The id of the underlying `` tag. | +| `loading` | Optional | `ReactNode` | | The content to display when checking authorizations | | `noValidate` | Optional | `boolean` | - | Set to `true` to disable the browser's default validation. | | `onSubmit` | Optional | `function` | `save` | A callback to call when the form is submitted. | | `sanitizeEmptyValues` | Optional | `boolean` | - | Set to `true` to remove empty values from the form state. | @@ -102,6 +105,48 @@ Here are all the props you can set on the `` component: Additional props are passed to `react-hook-form`'s [`useForm` hook](https://react-hook-form.com/docs/useform). +## `authorizationError` + +Used when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + +{% raw %} +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const CustomerEdit = () => ( + + + An error occurred while loading your permissions + + } + > + + + + + + + + + + + + + + + +); +``` +{% endraw %} + ## `children` The children of `` must be [`` elements](#longformsection). @@ -149,6 +194,45 @@ export const PostCreate = () => ( **Tip**: React-admin also allows to define default values at the input level. See the [Setting default Values](./Forms.md#default-values) section. +## `enableAccessControl` + +When set to `true`, React-admin will call the `authProvider.canAccess` method for each section with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.section.SECTION_ID_OR_LABEL`. For instance: `customers.section.identity` +- `record`: The current record + +For each section, react-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + + + + +); +``` + ## `id` Normally, a submit button only works when placed inside a `` tag. However, you can place a submit button outside the form if the submit button `form` matches the form `id`. @@ -170,6 +254,43 @@ export const PostCreate = () => ( ); ``` +## `loading` + +Used when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const CustomerEdit = () => ( + + + Loading your permissions... + + } + > + + + + + + + + + + + + + + + +); +``` + ## `noValidate` The `` attribute prevents the browser from validating the form. This is useful if you don't want to use the browser's default validation, or if you want to customize the error messages. To set this attribute on the underlying `` tag, set the `noValidate` prop to `true`. @@ -363,10 +484,52 @@ It accepts the following props: | Prop | Required | Type | Default | Description | | ----------------- | -------- | ----------- | ------- | ------------------------------------------------------------------------------------ | -| `label` | Required | `string` | - | The main label used as the section title. Appears in red when the section has errors | -| `children` | Required | `ReactNode` | - | A list of `` elements | -| `cardinality` | Optional | `number` | - | A number to be displayed next to the label in TOC, to quantify it | -| `sx` | Optional | `object` | - | An object containing the Material UI style overrides to apply to the root component | +| `authorizationError` | Optional | `ReactNode` | - | The content to display when authorization checks fail | +| `cardinality` | Optional | `number` | - | A number to be displayed next to the label in TOC, to quantify it | +| `children` | Required | `ReactNode` | - | A list of `` elements | +| `enableAccessControl` | Optional | `ReactNode` | - | Enable authorization checks | +| `label` | Required | `string` | - | The main label used as the section title. Appears in red when the section has errors | +| `loading` | Optional | `ReactNode` | - | The content to display while checking authorizations | +| `sx` | Optional | `object` | - | An object containing the Material UI style overrides to apply to the root component | + +#### `authorizationError` + +Used when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + +{% raw %} +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const CustomerEdit = () => ( + + + + + + + + An error occurred while loading your permissions + + }> + + + + + + + + + + +); +``` +{% endraw %} ### `cardinality` @@ -428,6 +591,78 @@ const CustomerEditWithCardinality = () => { }; ``` +### `enableAccessControl` + +When set to `true`, react-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + + + + +); +``` + +### `loading` + +Used when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { LongForm } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const CustomerEdit = () => ( + + + + Loading your permissions... + + }> + + + + + Loading your permissions... + + }> + + + + + + + + + + +); +``` + ## AutoSave In forms where users may spend a lot of time, it's a good idea to save the form automatically after a few seconds of inactivity. You turn on this feature by using [the `` component](./AutoSave.md). @@ -469,56 +704,3 @@ Note that you **must** set the `` prop to `{ keepDirtyVal If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](https://marmelab.com/react-admin/Edit.html#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. Check [the `` component](./AutoSave.md) documentation for more details. - -## Role-Based Access Control (RBAC) - -Fine-grained permissions control can be added by using the [``](./AuthRBAC.md#longform) and [``](./AuthRBAC.md#longform) components provided by the `@react-admin/ra-enterprise` package. - -{% raw %} -```tsx -import { LongForm } from '@react-admin/ra-enterprise'; - -const authProvider = { - checkAuth: () => Promise.resolve(), - login: () => Promise.resolve(), - logout: () => Promise.resolve(), - checkError: () => Promise.resolve(), - getPermissions: () =>Promise.resolve([ - // 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - { action: 'write', resource: 'products.Section.description' }, - { action: 'write', resource: 'products.Section.images' }, - // 'products.Section.stock' is missing - ]), -}; - -const ProductEdit = () => ( - - - - - - - - - - - - - - - - // delete button not displayed - - -); -``` -{% endraw %} - -Check [the RBAC ``](./AuthRBAC.md#longform) documentation for more details. From af9672aedbd9a0f08b4c9c1ecaf42e3792464806 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:32:27 +0100 Subject: [PATCH 03/39] Update WizardForm documentation --- docs/WizardForm.md | 303 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 248 insertions(+), 55 deletions(-) diff --git a/docs/WizardForm.md b/docs/WizardForm.md index fe155440403..9e9885d7170 100644 --- a/docs/WizardForm.md +++ b/docs/WizardForm.md @@ -49,9 +49,12 @@ The `` component accepts the following props: | Prop | Required | Type | Default | Description | | ------------------------ | -------- | ----------------- | ------- | ---------------------------------------------------------- | +| `authorizationError` | Optional | `ReactNode` | `null` | The content to display when authorization checks fail | | `children` | Required | `ReactNode` | - | A list of `` elements. | | `defaultValues` | Optional | `object|function` | - | The default values of the record. | +| `enableAccessControl` | Optional | `boolean` | `false` | Enable checking authorization rights for each panel and input | | `id` | Optional | `string` | - | The id of the underlying `` tag. | +| `loading` | Optional | `ReactNode` | | The content to display when checking authorizations | | `noValidate` | Optional | `boolean` | - | Set to `true` to disable the browser's default validation. | | `onSubmit` | Optional | `function` | `save` | A callback to call when the form is submitted. | | `progress` | Optional | `ReactElement` | - | A custom progress stepper element. | @@ -63,6 +66,49 @@ The `` component accepts the following props: Additional props are passed to `react-hook-form`'s [`useForm` hook](https://react-hook-form.com/docs/useform). +## `authorizationError` + +Used when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + + +{% raw %} +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const CustomerEdit = () => ( + + + An error occurred while loading your permissions + + } + > + + + + + + + + + + + + + + + +); +``` +{% endraw %} + ## `children` The children of `` must be `` elements. @@ -110,6 +156,45 @@ export const PostCreate = () => ( **Tip**: React-admin also allows to define default values at the input level. See the [Setting default Values](./forms.md#default-values) section. +## `enableAccessControl` + +When set to `true`, React-admin will call the `authProvider.canAccess` method for each panel with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.section.PANEL_ID_OR_LABEL`. For instance: `customers.section.identity` +- `record`: The current record + +For each panel, react-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + + + + +); +``` + ## `id` Normally, a submit button only works when placed inside a `` tag. However, you can place a submit button outside the form if the submit button `form` matches the form `id`. @@ -131,6 +216,43 @@ export const PostCreate = () => ( ); ``` +## `loading` + +Used when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const CustomerEdit = () => ( + + + Loading your permissions... + + } + > + + + + + + + + + + + + + + + +); +``` + ## `noValidate` The `` attribute prevents the browser from validating the form. This is useful if you don't want to use the browser's default validation, or if you want to customize the error messages. To set this attribute on the underlying `` tag, set the `noValidate` prop to `true`. @@ -433,6 +555,132 @@ const PostCreate = () => ( ); ``` +The children of `` must be `` elements. + +### Props + + +| Prop | Required | Type | Default | Description | +| --------------------- | -------- | ----------- | ------- | ------------------------------------------------------------------------------------ | +| `authorizationError` | Optional | `ReactNode` | - | The content to display when authorization checks fail | +| `enableAccessControl` | Optional | `ReactNode` | - | Enable authorization checks | +| `label` | Required | `string` | - | The main label used as the step title. Appears in red when the section has errors | +| `loading` | Optional | `ReactNode` | - | The content to display while checking authorizations | +| `children` | Required | `ReactNode` | - | A list of `` elements | +| `sx` | Optional | `object` | - | An object containing the MUI style overrides to apply to the root component | + +### `authorizationError` + +Used when `enableAccessControl` is set to `true` and an error occurs while checking for users permissions. Defaults to `null`: + +{% raw %} +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; +import { Alert } from '@mui/material'; + +const CustomerEdit = () => ( + + + + + + + + An error occurred while loading your permissions + + }> + + + + + + + + + + +); +``` +{% endraw %} + + +### `enableAccessControl` + +When set to `true`, react-admin will also call the `authProvider.canAccess` method for each input with the following parameters: +- `action`: `write` +- `resource`: `RESOURCE_NAME.INPUT_SOURCE`. For instance: `customers.first_name` +- `record`: The current record + +**Tip**: `` direct children that don't have a `source` will always be displayed. + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; + +const CustomerEdit = () => ( + + + + + + + + + + + + + + + + + +); +``` + +### `loading` + +Used when `enableAccessControl` is set to `true` while checking for users permissions. Defaults to `Loading` from `react-admin`: + +```tsx +import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin'; +import { WizardForm } from '@react-admin/ra-form-layout'; +import { Typography } from '@mui/material'; + +const CustomerEdit = () => ( + + + + Loading your permissions... + + }> + + + + + Loading your permissions... + + }> + + + + + + + + + + +); +``` + ## Adding a Summary Final Step In order to add a final step with a summary of the form values before submit, you can leverage `react-hook-form` [`useWatch`](https://react-hook-form.com/docs/usewatch) hook: @@ -471,58 +719,3 @@ const PostCreate = () => ( ); ``` - -## Role-Based Access Control (RBAC) - -Fine-grained permissions control can be added by using the [``](./AuthRBAC.md#wizardform) and [``](./AuthRBAC.md#wizardform) components provided by the `@react-admin/ra-enterprise` package. - -{% raw %} -```tsx -import { WizardForm } from '@react-admin/ra-enterprise'; - -const authProvider = { - checkAuth: () => Promise.resolve(), - login: () => Promise.resolve(), - logout: () => Promise.resolve(), - checkError: () => Promise.resolve(), - getPermissions: () =>Promise.resolve([ - // 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - { action: 'write', resource: 'products.step.description' }, - { action: 'write', resource: 'products.step.images' }, - // 'products.step.stock' is missing - ]), -}; - -const ProductCreate = () => ( - - - - - - - {/* Won't be displayed */} - - - - {/* Won't be displayed */} - - - - {/* Won't be displayed */} - - - - - -); -``` -{% endraw %} - -Check [the RBAC ``](./AuthRBAC.md#wizardform) documentation for more details. From cbf50e9c9a72d00ef1f4fccd10193daa0b7bcdc5 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:04:17 +0100 Subject: [PATCH 04/39] Update OSS forms documentation --- docs/SimpleForm.md | 67 ++++++++--------------- docs/TabbedForm.md | 132 ++++++++++++++++++++++++++------------------- 2 files changed, 100 insertions(+), 99 deletions(-) diff --git a/docs/SimpleForm.md b/docs/SimpleForm.md index 5f6b1a26fe4..a03521007d7 100644 --- a/docs/SimpleForm.md +++ b/docs/SimpleForm.md @@ -514,6 +514,28 @@ const formState = useFormState(); // ❌ should deconstruct the formState ## Displaying Inputs Based On Permissions +You can leverage [the `` component](./CanAccess.md) to display inputs if the user has the required access rights. + +{% raw %} +```jsx +import { CanAccess, Create, SimpleForm, TextInput } from 'react-admin'; + +export const UserCreate = () => { + const { permissions } = useGetPermissions(); + return ( + + + + + + + + + ); +} +``` +{% endraw %} + You can leverage [the `usePermissions` hook](./usePermissions.md) to display inputs if the user has the required permissions. {% raw %} @@ -647,51 +669,6 @@ If you're using it in an `` page, you must also use a `pessimistic` or `op Check [the `` component](./AutoSave.md) documentation for more details. -## Role-Based Access Control (RBAC) - -Fine-grained permissions control can be added by using the [``](./AuthRBAC.md#simpleform) component provided by the `@react-admin/ra-rbac` package. - -{% raw %} -```jsx -import { Edit, TextInput } from 'react-admin'; -import { SimpleForm } from '@react-admin/ra-rbac'; - -const authProvider= { - // ... - getPermissions: () => Promise.resolve({ - permissions: [ - // 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - ] - }), -}; - -const ProductEdit = () => ( - - - - - - {/* not displayed */} - - {/* not displayed */} - - - {/* no delete button */} - - -); -``` -{% endraw %} - -Check [the RBAC `` component](./AuthRBAC.md#simpleform) documentation for more details. - ## Versioning By default, `` updates the current record (via `dataProvider.update()`), so the previous version of the record is lost. If you want to keep the previous version, you can use the [``](https://react-admin-ee.marmelab.com/documentation/ra-history#simpleformwithrevision) component instead: diff --git a/docs/TabbedForm.md b/docs/TabbedForm.md index 45a0443c501..6b5bd8a683b 100644 --- a/docs/TabbedForm.md +++ b/docs/TabbedForm.md @@ -796,21 +796,34 @@ Check [the `` component](./AutoSave.md) documentation for more details ## Displaying a Tab Based On Permissions -You can leverage [the `usePermissions` hook](./usePermissions.md) to display a tab only if the user has the required permissions. +You can leverage [the `useCanAccess` hook](./useCanAccess.md) to display tabs if the user has the required access rights. {% raw %} ```jsx -import { usePermissions, Edit, TabbedForm, FormTab } from 'react-admin'; +import { Edit, FormTab, Loading, TabbedForm, TextInput, useCanAccess } from 'react-admin'; +import { Alert } from '@mui/material'; + +export const UserCreate = () => { + const { canAccess, isPending, error } = useCanAccess({ + resource: 'users.tabs.security', + action: 'write', + }); + if (isPending) return ; + if (error) { + return ( + + An error occurred while checking your permissions + + ); + } -const UserEdit = () => { - const { permissions } = usePermissions(); return ( - + ... - {permissions === 'admin' && + {canAccess && ... @@ -818,65 +831,76 @@ const UserEdit = () => { ); -}; +} ``` {% endraw %} -## Role-Based Access Control (RBAC) +If you need to check access rights for multiple tabs, leverage [the `useCanAccessResources` hook](./useCanAccess.md#multiple-resources). -You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedform) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. +{% raw %} +```jsx +import { Edit, FormTab, Loading, TabbedForm, TextInput, useCanAccessResources } from 'react-admin'; +import { Alert } from '@mui/material'; + +export const UserCreate = () => { + const { canAccess, isPending, error } = useCanAccessResources({ + resources: ['users.tabs.summary', 'users.tabs.security'], + action: 'write', + }); + if (isPending) return ; + if (error) { + return ( + + An error occurred while checking your permissions + + ); + } + + return ( + + + {canAccess['users.tabs.summary'] && + + ... + } + {canAccess['users.tabs.security'] && + + ... + + } + + + ); +} +``` +{% endraw %} -[``](./AuthRBAC.md#tabbedform) shows only the tabs for which users have write permissions, using the `[resource].tab.[tabName]` string as resource identifier. It also renders the delete button only if the user has a permission for the `delete` action in the current resource. `` shows only the child inputs for which users have the write permissions, using the `[resource].[source]` string as resource identifier. +You can leverage [the `usePermissions` hook](./usePermissions.md) to display a tab only if the user has the required permissions. {% raw %} -```tsx -import { Edit, TextInput } from 'react-admin'; -import { TabbedForm } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - getPermissions: () => Promise.resolve([ - // crud (the delete action is missing) - { action: ['list', 'edit'], resource: 'products' }, - // tabs ('products.tab.stock' is missing) - { action: 'write', resource: 'products.tab.description' }, - { action: 'write', resource: 'products.tab.images' }, - // fields ('products.description' and 'products.image' are missing) - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - { action: 'write', resource: 'products.thumbnail' }, - ]), -}; +```jsx +import { usePermissions, Edit, TabbedForm, FormTab } from 'react-admin'; -const ProductEdit = () => ( - - - - - - - {/* the description input is not displayed */} - - - {/* the stock tab is not displayed */} - - - - - {/* the images input is not displayed */} - - - - {/* the delete button is not displayed */} - - -); +const UserEdit = () => { + const { permissions } = usePermissions(); + return ( + + + + ... + + {permissions === 'admin' && + + ... + + } + + + ); +}; ``` {% endraw %} -Check [the RBAC `` component](./AuthRBAC.md#tabbedform) documentation for more details. - ## Versioning By default, `` updates the current record (via `dataProvider.update()`), so the previous version of the record is lost. If you want to keep track of the previous versions of the record, you can use the [``](https://react-admin-ee.marmelab.com/documentation/ra-history#tabbedformwithrevision) component instead. From 195d26fcb950c1d018c9b04c29ffbc155412a449 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:15:25 +0100 Subject: [PATCH 05/39] Update TabbedShowLayout documentation --- docs/TabbedShowLayout.md | 55 ---------------------------------------- 1 file changed, 55 deletions(-) diff --git a/docs/TabbedShowLayout.md b/docs/TabbedShowLayout.md index 211f732c8fa..81f7f90d31d 100644 --- a/docs/TabbedShowLayout.md +++ b/docs/TabbedShowLayout.md @@ -345,61 +345,6 @@ const StaticPostShow = () => ( When passed a `record`, `` creates a `RecordContext` with the given record. -## Role-Based Access Control (RBAC) - -You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedshowlayout) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. - -[``](./AuthRBAC.md#tabbedshowlayout) shows only the tabs for which users have read permissions, using the `[resource].tab.[tabName]` string as resource identifier. `` shows only the child fields for which users have the read permissions, using the `[resource].[source]` string as resource identifier. - -{% raw %} -```tsx -import { Show, TextField } from 'react-admin'; -import { TabbedShowLayout } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - getPermissions: () => Promise.resolve([ - // crud - { action: ['list', 'show'], resource: 'products' }, - // tabs ('products.tab.stock' is missing) - { action: 'read', resource: 'products.tab.description' }, - { action: 'read', resource: 'products.tab.images' }, - // fields ('products.description' and 'products.image' are missing) - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - { action: 'read', resource: 'products.thumbnail' }, - ]), -}; - -const ProductShow = () => ( - - - - - - - {/* the description field is not displayed */} - - - {/* the stock tab is not displayed */} - - - - - {/* the images field is not displayed */} - - - - - -); -``` -{% endraw %} - -Check [the RBAC `` component](./AuthRBAC.md#tabbedshowlayout) documentation for more details. - - ## See Also * [Field components](./Fields.md) From 8560ea5f804462487a9d81962546d88cfd88ae3a Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:23:35 +0100 Subject: [PATCH 06/39] Improve OSS forms documentation --- docs/SimpleForm.md | 7 +++--- docs/TabbedForm.md | 62 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/docs/SimpleForm.md b/docs/SimpleForm.md index a03521007d7..b12267d70f7 100644 --- a/docs/SimpleForm.md +++ b/docs/SimpleForm.md @@ -521,7 +521,6 @@ You can leverage [the `` component](./CanAccess.md) to display inputs import { CanAccess, Create, SimpleForm, TextInput } from 'react-admin'; export const UserCreate = () => { - const { permissions } = useGetPermissions(); return ( @@ -548,8 +547,10 @@ export const UserCreate = () => { - {permissions === 'admin' && - } + {permissions === 'admin' + ? + : null + } ); diff --git a/docs/TabbedForm.md b/docs/TabbedForm.md index 6b5bd8a683b..85d538c5d21 100644 --- a/docs/TabbedForm.md +++ b/docs/TabbedForm.md @@ -839,10 +839,10 @@ If you need to check access rights for multiple tabs, leverage [the `useCanAcces {% raw %} ```jsx -import { Edit, FormTab, Loading, TabbedForm, TextInput, useCanAccessResources } from 'react-admin'; +import { Edit, Loading, TabbedForm, TextInput, useCanAccessResources } from 'react-admin'; import { Alert } from '@mui/material'; -export const UserCreate = () => { +export const UserEdit = () => { const { canAccess, isPending, error } = useCanAccessResources({ resources: ['users.tabs.summary', 'users.tabs.security'], action: 'write', @@ -879,7 +879,7 @@ You can leverage [the `usePermissions` hook](./usePermissions.md) to display a t {% raw %} ```jsx -import { usePermissions, Edit, TabbedForm, FormTab } from 'react-admin'; +import { usePermissions, Edit, TabbedForm } from 'react-admin'; const UserEdit = () => { const { permissions } = usePermissions(); @@ -901,6 +901,62 @@ const UserEdit = () => { ``` {% endraw %} +## Displaying Inputs Based On Permissions + +You can leverage [the `` component](./CanAccess.md) to display inputs if the user has the required access rights. + +{% raw %} +```jsx +import { CanAccess, Edit, TabbedForm, TextInput } from 'react-admin'; + +export const UserEdit = () => { + return ( + + + + + + + + } + + ... + + + + ); +} +``` +{% endraw %} + +You can leverage [the `usePermissions` hook](./usePermissions.md) to display inputs if the user has the required permissions. + +{% raw %} +```jsx +import { usePermissions, Edit, TabbedForm, TextInput } from 'react-admin'; + +export const UserEdit = () => { + const { permissions } = useGetPermissions(); + return ( + + + + + {permissions === 'admin' + ? + : null + } + } + + ... + + + + ); +} +``` +{% endraw %} + ## Versioning By default, `` updates the current record (via `dataProvider.update()`), so the previous version of the record is lost. If you want to keep track of the previous versions of the record, you can use the [``](https://react-admin-ee.marmelab.com/documentation/ra-history#tabbedformwithrevision) component instead. From 247c03defdbeede3184387281ebbef0d81b4606b Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:21:02 +0100 Subject: [PATCH 07/39] Remove mentions of useGetPermissions --- docs/SimpleForm.md | 23 -------------------- docs/TabbedForm.md | 54 ---------------------------------------------- 2 files changed, 77 deletions(-) diff --git a/docs/SimpleForm.md b/docs/SimpleForm.md index b12267d70f7..812b938802d 100644 --- a/docs/SimpleForm.md +++ b/docs/SimpleForm.md @@ -535,29 +535,6 @@ export const UserCreate = () => { ``` {% endraw %} -You can leverage [the `usePermissions` hook](./usePermissions.md) to display inputs if the user has the required permissions. - -{% raw %} -```jsx -import { usePermissions, Create, SimpleForm, TextInput } from 'react-admin'; - -export const UserCreate = () => { - const { permissions } = useGetPermissions(); - return ( - - - - {permissions === 'admin' - ? - : null - } - - - ); -} -``` -{% endraw %} - ## Configurable You can let end users customize the fields displayed in the `` by using the `` component instead. diff --git a/docs/TabbedForm.md b/docs/TabbedForm.md index 85d538c5d21..7923187c52c 100644 --- a/docs/TabbedForm.md +++ b/docs/TabbedForm.md @@ -875,32 +875,6 @@ export const UserEdit = () => { ``` {% endraw %} -You can leverage [the `usePermissions` hook](./usePermissions.md) to display a tab only if the user has the required permissions. - -{% raw %} -```jsx -import { usePermissions, Edit, TabbedForm } from 'react-admin'; - -const UserEdit = () => { - const { permissions } = usePermissions(); - return ( - - - - ... - - {permissions === 'admin' && - - ... - - } - - - ); -}; -``` -{% endraw %} - ## Displaying Inputs Based On Permissions You can leverage [the `` component](./CanAccess.md) to display inputs if the user has the required access rights. @@ -929,34 +903,6 @@ export const UserEdit = () => { ``` {% endraw %} -You can leverage [the `usePermissions` hook](./usePermissions.md) to display inputs if the user has the required permissions. - -{% raw %} -```jsx -import { usePermissions, Edit, TabbedForm, TextInput } from 'react-admin'; - -export const UserEdit = () => { - const { permissions } = useGetPermissions(); - return ( - - - - - {permissions === 'admin' - ? - : null - } - } - - ... - - - - ); -} -``` -{% endraw %} - ## Versioning By default, `` updates the current record (via `dataProvider.update()`), so the previous version of the record is lost. If you want to keep track of the previous versions of the record, you can use the [``](https://react-admin-ee.marmelab.com/documentation/ra-history#tabbedformwithrevision) component instead. From 7183b492b76e15615e9c1cc88d9a9082d4bbfb70 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:55:24 +0100 Subject: [PATCH 08/39] Revert TabbedShowLayout changes --- docs/TabbedShowLayout.md | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/TabbedShowLayout.md b/docs/TabbedShowLayout.md index 81f7f90d31d..8a0b24c9698 100644 --- a/docs/TabbedShowLayout.md +++ b/docs/TabbedShowLayout.md @@ -345,6 +345,60 @@ const StaticPostShow = () => ( When passed a `record`, `` creates a `RecordContext` with the given record. +## Role-Based Access Control (RBAC) + +You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedshowlayout) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. + +[``](./AuthRBAC.md#tabbedshowlayout) shows only the tabs for which users have read permissions, using the `[resource].tab.[tabName]` string as resource identifier. `` shows only the child fields for which users have the read permissions, using the `[resource].[source]` string as resource identifier. + +{% raw %} +```tsx +import { Show, TextField } from 'react-admin'; +import { TabbedShowLayout } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + getPermissions: () => Promise.resolve([ + // crud + { action: ['list', 'show'], resource: 'products' }, + // tabs ('products.tab.stock' is missing) + { action: 'read', resource: 'products.tab.description' }, + { action: 'read', resource: 'products.tab.images' }, + // fields ('products.description' and 'products.image' are missing) + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + { action: 'read', resource: 'products.thumbnail' }, + ]), +}; + +const ProductShow = () => ( + + + + + + + {/* the description field is not displayed */} + + + {/* the stock tab is not displayed */} + + + + + {/* the images field is not displayed */} + + + + + +); +``` +{% endraw %} + +Check [the RBAC `` component](./AuthRBAC.md#tabbedshowlayout) documentation for more details. + ## See Also * [Field components](./Fields.md) From 8fc91f68ca340458705641c287e61ee7ff9ddacc Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:55:34 +0100 Subject: [PATCH 09/39] Improve SimpleShowLayout [no ci] --- docs/SimpleShowLayout.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/SimpleShowLayout.md b/docs/SimpleShowLayout.md index 7ff14f68f11..faee080d811 100644 --- a/docs/SimpleShowLayout.md +++ b/docs/SimpleShowLayout.md @@ -261,6 +261,45 @@ const PostShow = () => ( ); ``` +## Role-Based Access Control (RBAC) + +You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedshowlayout) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. + +[``](./AuthRBAC.md#tabbedshowlayout) shows only the tabs for which users have read permissions, using the `[resource].tab.[tabName]` string as resource identifier. `` shows only the child fields for which users have the read permissions, using the `[resource].[source]` string as resource identifier. + +{% raw %} +```tsx +import { Show, TextField } from 'react-admin'; +import { SimpleShowLayout } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + getPermissions: () => Promise.resolve([ + // crud + { action: ['list', 'show'], resource: 'products' }, + // fields ('products.description' is missing) + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + ]), +}; + +const ProductShow = () => ( + + + + + + {/* the description field is not displayed */} + + + +); +``` +{% endraw %} + +Check [the RBAC `` component](./AuthRBAC.md#simpleshowlayout) documentation for more details. + ## See Also * [Field components](./Fields.md) From 9fe472ce37b3b1419112fc387666fd7682d10a7e Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:10:38 +0100 Subject: [PATCH 10/39] Document Menu Access Control [no ci] --- docs/Menu.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/Menu.md b/docs/Menu.md index 4b51cd09472..2273ed232d6 100644 --- a/docs/Menu.md +++ b/docs/Menu.md @@ -465,3 +465,33 @@ const MyReactAdmin = () => ( ``` ![MenuLive](./img/MenuLive.png) + +## Displaying Menu Items Based On Permissions + +If you `authProvider` implements [the `canAccess` method](./Permissions.md#authprovidercanaccess), the `` component will call it for each resource with the follwing parameters: +- `resource`: the resource name +- `action`: `list` + +However, if you have a custom menu and want to show items based on the user permissions, you can leverage [the `` component](./CanAccess.md): + +{% raw %} +```tsx +// in src/MyMenu.js +import { CanAccess, Menu } from 'react-admin'; +import LabelIcon from '@mui/icons-material/Label'; + +export const MyMenu = () => ( + + + + + + + } /> + + +); +``` +{% endraw %} + +**Tip**: the `resource` and `action` parameter accept any string value. From c76da1c5baa253e1f529755df5ff284858f84a57 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:29:36 +0100 Subject: [PATCH 11/39] Document CloneButton and ExportButton [no ci] --- docs/Buttons.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/Buttons.md b/docs/Buttons.md index d8777e7ce33..1b8095878fb 100644 --- a/docs/Buttons.md +++ b/docs/Buttons.md @@ -568,6 +568,58 @@ To override the style of all instances of ` + + + + + ); +}; + +const AutocompleteInputWithCreate = props => { + const inputRef = React.useRef(null); + return ( + { + setTimeout(() => { + inputRef.current?.select(); + }, 50); + }, + }} + create={} + /> + ); +}; + +const StyledAutocompleteInput = styled(AutocompleteInput)({ + '& .MuiTextField-root': { + margin: '1px 0px', + }, + '& .MuiTextField-root fieldset': { + border: 'none', + }, + '& .MuiTextField-root input': { + fontSize: 14, + }, + '& .MuiInputLabel-root': { + display: 'none', + }, +}); + +const StyledListbox = styled('ul')({ + fontSize: 14, +}); + +export const CommentListWithAutocompleteWithCreate = () => { + const columnDefs = [ + // ... + { + field: 'post_id', + cellEditor: ( + + + + ), + cellEditorParams: { + submitOnChange: true, + noThemeOverride: true, // prevent the default theme override + }, + }, + ]; + return ( + + + + ); +}; +``` + ### Using AG Grid Enterprise `` is also compatible with the [Enterprise version of ag-grid](https://www.ag-grid.com/react-data-grid/licensing/). @@ -1468,16 +1799,17 @@ The client-side performance isn't affected by a large number of records, as ag-g | Prop | Required | Type | Default | Description | | ------------------- | -------- | --------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `columnDefs` | Required | Array | n/a | The columns definitions | | `bulkActionButtons` | Optional | Element | `` | The component used to render the bulk action buttons | +| `cellEditor` | Optional | String, Function or Element | | Allows to use a custom component to render the cell editor | | `cellRenderer` | Optional | String, Function or Element | | Allows to use a custom component to render the cell content | +| `columnDefs` | Required | Array | n/a | The columns definitions | | `darkTheme` | Optional | String | `'ag-theme-alpine-dark'` | The name of the ag-grid dark theme | | `defaultColDef` | Optional | Object | | The default column definition (applied to all columns) | | `mutationOptions` | Optional | Object | | The mutation options | +| `pagination` | Optional | Boolean | `true` | Enable or disable pagination | | `preferenceKey` | Optional | String or `false` | `${resource}.ag-grid.params` | The key used to persist [`gridState`](https://www.ag-grid.com/react-data-grid/grid-state/) in the Store. `false` disables persistence. | | `sx` | Optional | Object | | The sx prop passed down to the wrapping `
` element | | `theme` | Optional | String | `'ag-theme-alpine'` | The name of the ag-grid theme | -| `pagination` | Optional | Boolean | `true` | Enable or disable pagination | `` also accepts the same props as [``](https://www.ag-grid.com/react-data-grid/grid-options/) with the exception of `rowData`, since the data is fetched from the List context. @@ -1527,6 +1859,146 @@ export const PostList = () => { ``` {% endraw %} +### `cellEditor` + +In a column definition, you can use the `cellEditor` field to specify a custom cell editor. You can use any [Edit Component](https://www.ag-grid.com/react-data-grid/cell-editors/) supported by `ag-grid`, including [Custom Components](https://www.ag-grid.com/react-data-grid/cell-editors/#custom-components). + +In addition to that, `` supports using [React Admin inputs](https://marmelab.com/react-admin/Inputs.html) as `cellEditor`, such as [``](https://marmelab.com/react-admin/TextInput.html) or even [``](https://marmelab.com/react-admin/ReferenceInput.html). + +This allows to leverage all the power of react-admin inputs in your grid, for example to edit a reference. + +To use a React Admin input as `cellEditor`, you need to pass it as a *React Element*: + +```tsx +import { List, ReferenceInput } from 'react-admin'; +import { DatagridAGClient } from '@react-admin/ra-datagrid-ag'; + +export const CommentList = () => { + const columnDefs = [ + // ... + { + field: 'post_id', + cellEditor: ( + + ), + }, + ]; + return ( + + + + ); +}; +``` + + + +If you are passing a React Admin input as *React Element*, there are two additional props you can use: `submitOnChange` and `noThemeOverride`. + +These props need to be passed as `cellEditorParams`. + +`submitOnChange` allows to submit the change to ag-grid as soon as the input value changes, without waiting for the user to submit the form (e.g. by pressing Enter or clicking outside the cell). + +This provides a better UX for example with components such as `` or ``, as the value is immediately updated after the user selects an option. + +```tsx +import { List, ReferenceInput } from 'react-admin'; +import { DatagridAGClient } from '@react-admin/ra-datagrid-ag'; + +export const CommentList = () => { + const columnDefs = [ + // ... + { + field: 'post_id', + cellEditor: ( + + ), + cellEditorParams: { + submitOnChange: true, + }, + }, + ]; + return ( + + + + ); +}; +``` + +`noThemeOverride` allows to prevent `DatagridAGClient` from applying custom styles to the input. + +Indeed, `DatagridAGClient` applies custom styles to the inputs to make them look like ag-grid cells. However, this can cause issues for instance when rendering a `Dialog` containing additional inputs inside the cell editor. This can happen, for example, if you are using a custom create component with ``. + +To solve this issue, you can set `noThemeOverride` to `true` and apply your own styles to the input component. + +```tsx +import { styled } from '@mui/material'; +import { List, ReferenceInput, AutocompleteInput } from 'react-admin'; +import { DatagridAGClient } from '@react-admin/ra-datagrid-ag'; +import { CreatePostDialog } from './CreatePostDialog'; + +export const CommentList = () => { + const columnDefs = [ + // ... + { + field: 'post_id', + cellEditor: ( + + + + ), + cellEditorParams: { + noThemeOverride: true, + }, + }, + ]; + return ( + + + + ); +}; + +const AutocompleteInputWithCreate = () => { + return ( + } + /> + ); +}; + +const StyledAutocompleteInput = styled(AutocompleteInput)({ + '& .MuiTextField-root': { + margin: '1px 0px', + }, + '& .MuiTextField-root fieldset': { + border: 'none', + }, + '& .MuiTextField-root input': { + fontSize: 14, + }, + '& .MuiInputLabel-root': { + display: 'none', + }, +}); + +const StyledListbox = styled('ul')({ + fontSize: 14, +}); +``` + +**Tip:** Be sure to read the [Fine Tuning Input Components Used As Cell Editor](#fine-tuning-input-components-used-as-cell-editor) section to improve the UX of your custom cell editors. + +**Tip:** Using a custom `cellEditor` works great in combination with a custom [`cellRenderer`](#cellrenderer-1). + +**Note:** React Admin inputs used ad `cellEditor` do not (yet) support form validation. + ### `cellRenderer` In a column definition, you can use the `cellRenderer` field to specify a custom cell renderer. In addition to [ag-grid's cell rendering abilities](https://www.ag-grid.com/react-data-grid/cell-rendering/), `` supports [react-admin fields](./Fields.md) in `cellRenderer`. This is particularly useful to render a [``](./ReferenceField.md) for instance. @@ -1573,6 +2045,8 @@ export const CommentList = () => { **Note:** You still need to pass the `source` prop to the field. +**Tip:** This works great in combination with a custom [`cellEditor`](#celleditor-1). + ### `columnDefs` The `columnDefs` prop is the most important prop of ``. It defines the columns of the grid, and their properties. It is an array of objects, each object representing a column. From 0724e49900cbffe3e4d2ce66d37d48ccf5bbdc48 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:08:54 +0100 Subject: [PATCH 15/39] Fix snippets --- docs/DatagridAG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/DatagridAG.md b/docs/DatagridAG.md index 935e16bd7c6..5d1bcb41897 100644 --- a/docs/DatagridAG.md +++ b/docs/DatagridAG.md @@ -1424,6 +1424,7 @@ When rendering an `` as a cell editor, it can be useful to au This can be achieved using refs like so: +{% raw %} ```tsx const AutocompleteInputWithAutoSelect = props => { const inputRef = React.useRef(null); @@ -1442,6 +1443,8 @@ const AutocompleteInputWithAutoSelect = props => { ); }; ``` +{% endraw %} + #### Automatically Open The Options List With `` @@ -1449,6 +1452,7 @@ When rendering a `` as a cell editor, it can be useful to automatic This can be achieved using the `defaultOpen` prop like so: +{% raw %} ```tsx const SelectInputWithDefaultOpen = props => { return ( @@ -1461,6 +1465,7 @@ const SelectInputWithDefaultOpen = props => { ); }; ``` +{% endraw %} #### Allow To Create New Options On The Fly With `` Or `` @@ -1474,6 +1479,7 @@ This can be solved by stopping the event propagation when the submit button is c Here is a complete example of how to create a custom `AutocompleteInputWithCreate` component that solves both issues: +{% raw %} ```tsx import React from 'react'; import { @@ -1599,6 +1605,7 @@ export const CommentListWithAutocompleteWithCreate = () => { ); }; ``` +{% endraw %} ### Using AG Grid Enterprise `` is also compatible with the [Enterprise version of ag-grid](https://www.ag-grid.com/react-data-grid/licensing/). From 2ec4722dab49a356687d2d4dedd6faa5836f0b21 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:55:54 +0100 Subject: [PATCH 16/39] Apply suggestions from code review Co-authored-by: erwanMarmelab <131013150+erwanMarmelab@users.noreply.github.com> --- docs/DatagridAG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/DatagridAG.md b/docs/DatagridAG.md index 5d1bcb41897..6d9fa6a1df4 100644 --- a/docs/DatagridAG.md +++ b/docs/DatagridAG.md @@ -315,7 +315,7 @@ export const PostList = () => { In a column definition, you can use the `cellEditor` field to specify a custom cell editor. You can use any [Edit Component](https://www.ag-grid.com/react-data-grid/cell-editors/) supported by `ag-grid`, including [Custom Components](https://www.ag-grid.com/react-data-grid/cell-editors/#custom-components). -In addition to that, `` supports using [React Admin inputs](https://marmelab.com/react-admin/Inputs.html) as `cellEditor`, such as [``](https://marmelab.com/react-admin/TextInput.html) or even [``](https://marmelab.com/react-admin/ReferenceInput.html). +In addition to that, `` supports using [React Admin inputs](./Inputs.md) as `cellEditor`, such as [``](./TextInput.md) or even [``](./ReferenceInput.md). This allows to leverage all the power of react-admin inputs in your grid, for example to edit a reference. @@ -344,7 +344,7 @@ export const CommentList = () => { ``` @@ -1870,7 +1870,7 @@ export const PostList = () => { In a column definition, you can use the `cellEditor` field to specify a custom cell editor. You can use any [Edit Component](https://www.ag-grid.com/react-data-grid/cell-editors/) supported by `ag-grid`, including [Custom Components](https://www.ag-grid.com/react-data-grid/cell-editors/#custom-components). -In addition to that, `` supports using [React Admin inputs](https://marmelab.com/react-admin/Inputs.html) as `cellEditor`, such as [``](https://marmelab.com/react-admin/TextInput.html) or even [``](https://marmelab.com/react-admin/ReferenceInput.html). +In addition to that, `` supports using [React Admin inputs](./Inputs.md) as `cellEditor`, such as [``](./TextInput.md) or even [``](./ReferenceInput.md). This allows to leverage all the power of react-admin inputs in your grid, for example to edit a reference. @@ -1899,7 +1899,7 @@ export const CommentList = () => { ``` From 809641af3c7a77d9257cb537f94b8c4268e8d3b8 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:58:20 +0100 Subject: [PATCH 17/39] Apply suggestions from code review Co-authored-by: erwanMarmelab <131013150+erwanMarmelab@users.noreply.github.com> --- docs/AccordionForm.md | 2 +- docs/AuthRBAC.md | 8 ++++---- docs/LongForm.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/AccordionForm.md b/docs/AccordionForm.md index 895ec642d16..53357a33215 100644 --- a/docs/AccordionForm.md +++ b/docs/AccordionForm.md @@ -826,6 +826,6 @@ const PersonEdit = () => ( Note that you **must** set the `` prop to `{ keepDirtyValues: true }`. If you forget that prop, any change entered by the end user after the autosave but before its acknowledgement by the server will be lost. -If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](https://marmelab.com/react-admin/Edit.html#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. +If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](./Edit.md#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. Check [the `` component](./AutoSave.md) documentation for more details. diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index 67f7e41c433..b751250c5d7 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -486,7 +486,7 @@ export const PostList = () => ( // Users will only see the authorized columns when clicking on the export button. ``` -**Tip**: If you need a custom [`exporter`](https://marmelab.com/react-admin/List.html#exporter), you can use `useExporterWithAccessControl` to apply access control to the exported records: +**Tip**: If you need a custom [`exporter`](./List.md#exporter), you can use `useExporterWithAccessControl` to apply access control to the exported records: ```tsx import { List, useExporterWithAccessControl } from '@ra-enterprise/ra-rbac'; @@ -521,7 +521,7 @@ export const PostList = () => }>...; ## `` -Replacement for react-admin's [``](https://marmelab.com/react-admin/Buttons.html#exportbutton) that checks users have the `'export'` permission before rendering. Use it if you want to provide your own `actions` to the ``: +Replacement for react-admin's [``](./Buttons.md#exportbutton) that checks users have the `'export'` permission before rendering. Use it if you want to provide your own `actions` to the ``: ```tsx import { CreateButton, List, TopToolbar } from 'react-admin'; @@ -542,7 +542,7 @@ export const PostList = () => ( ); ``` -It accepts the following props in addition to the default [`` props](https://marmelab.com/react-admin/Buttons.html#props-8): +It accepts the following props in addition to the default [`` props](./Buttons.md#props-8): | Prop | Required | Type | Default | Description | | -------------------- | -------- | ----------------- | ---------- | ---------------------------------------------------------------------- | @@ -916,7 +916,7 @@ const ProductEdit = () => ( ## `` -Replacement for react-admin's [``](https://marmelab.com/react-admin/Buttons.html#clonebutton) that checks users have the `'clone'` permission before rendering. Use it if you want to provide your own `actions` to the ``: +Replacement for react-admin's [``](./Buttons.md#clonebutton) that checks users have the `'clone'` permission before rendering. Use it if you want to provide your own `actions` to the ``: ```tsx import { Edit, TopToolbar } from 'react-admin'; diff --git a/docs/LongForm.md b/docs/LongForm.md index d3ca5da4515..11d77410a01 100644 --- a/docs/LongForm.md +++ b/docs/LongForm.md @@ -701,6 +701,6 @@ const PersonEdit = () => ( Note that you **must** set the `` prop to `{ keepDirtyValues: true }`. If you forget that prop, any change entered by the end user after the autosave but before its acknowledgement by the server will be lost. -If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](https://marmelab.com/react-admin/Edit.html#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. +If you're using it in an `` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](./Edit.md#mutationmode) - `` doesn't work with the default `mutationMode="undoable"`. Check [the `` component](./AutoSave.md) documentation for more details. From d7e8d33f0986b88192f12f176ad7604b032ded2c Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:15:52 +0100 Subject: [PATCH 18/39] Fix link typo --- docs/DatagridAG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DatagridAG.md b/docs/DatagridAG.md index 6d9fa6a1df4..76baeb1905f 100644 --- a/docs/DatagridAG.md +++ b/docs/DatagridAG.md @@ -1899,7 +1899,7 @@ export const CommentList = () => { ``` From 4308f7cd8255d4b64160937cb3dbe7bb67e029d0 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Thu, 12 Dec 2024 19:10:15 +0100 Subject: [PATCH 19/39] Improve introduction --- docs/AuthRBAC.md | 94 ++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index b751250c5d7..dc243f9ec0e 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -5,15 +5,21 @@ title: "RBAC" # Role-Based Access Control (RBAC) -React-admin Enterprise Edition contains [the ra-rbac module](https://react-admin-ee.marmelab.com/documentation/ra-rbac), which adds fine-grained permissions to your admin. This module extends the `authProvider` and adds replacement for many react-admin components that use these permissions. +Building up on react-admin's [Access Control features](./Permissions.md#access-control), react-admin RBAC provides an implementation for `authProvider.canAccess()` to manage roles and fine-grained permissions, and exports alternative react-admin components that use these permissions. -Test it live in the [Enterprise Edition Storybook](https://storybook.ra-enterprise.marmelab.com/?path=/story/ra-rbac-full-app--full-app). +Test it live in the [Enterprise Edition Storybook](https://react-admin.github.io/ra-enterprise/?path=/story/ra-rbac-full-app--full-app). -You can define permissions for pages, fields, buttons, etc. Roles and permissions are managed by the `authProvider`, which means you can use any data source you want (including an ActiveDirectory server). +The RBAC features are part of [ra-rbac](https://react-admin-ee.marmelab.com/documentation/ra-rbac), an [Enterprise Edition](https://react-admin-ee.marmelab.com) package. + +## At a Glance + +RBAC relies on an array of roles and permissions to determine what a user can do in a React-admin application. You can define permissions for pages, fields, buttons, etc. These permissions use a serialization format that is easy to understand and to maintain. You can store them in a database, in a JSON file, or in your code. + +Roles and permissions are used by `authProvider.canAccess()` to provide fine-grained access control to the entire app. The above demo uses the following set of permissions: @@ -62,17 +68,19 @@ const roles = { ## Installation +First, install the `@react-admin/ra-rbac` package: + ``` npm install --save @react-admin/ra-rbac # or yarn add @react-admin/ra-rbac ``` -Make sure you [enable auth features](https://marmelab.com/react-admin/Authentication.html#enabling-auth-features) by setting an ``, and [disable anonymous access](https://marmelab.com/react-admin/Authentication.html#disabling-anonymous-access) by adding the `` prop. This will ensure that react-admin waits for the `authProvider` response before rendering anything. - **Tip**: ra-rbac is part of the [React-Admin Enterprise Edition](https://react-admin-ee.marmelab.com/), and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package. -## Vocabulary +Make sure you [enable auth features](https://marmelab.com/react-admin/Authentication.html#enabling-auth-features) by setting an ``, and [disable anonymous access](https://marmelab.com/react-admin/Authentication.html#disabling-anonymous-access) by adding the `` prop. This will ensure that react-admin waits for the `authProvider` response before rendering anything. + +## Concepts ### Permission @@ -88,6 +96,28 @@ Here are a few examples of permissions: **Tip**: When the `record` field is omitted, the permission is valid for all records. +### Action + +An _action_ is a string, usually a verb, that represents an operation. Examples of actions include "read", "create", "edit", "delete", or "export". + +React-admin already does page-level access control with actions like "list", "show", "edit", "create", and "delete". RBAC checks additional actions in its components: + +| Action | Description | Used In | +| -------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `list` | Allow to access the List page | [``](#list), [``](./Buttons.md#listbutton), [``](#menu) | +| `show` | Allow to access the Show page | [``](./Show.md), [``](./Buttons.md#showbutton), [``](#datagrid), [``](./Edit.md) | +| `create` | Allow to access the Create page | [``](./Create.md), [``](./Buttons.md#createbutton), [``](#list) | +| `edit` | Allow to access the Edit page | [``](./Edit.md), [``](./Buttons.md#editbutton), [``](#datagrid), [``](./Show.md) | +| `delete` | Allow to delete data | [``](./Buttons.md#deletebutton), [``](./Buttons.md#bulkdeletebutton), [``](#datagrid), [``](#simpleform), [``](#tabform) | +| `export` | Allow to export data | [``](#exportbutton), [``](#list) | +| `clone` | Allow to clone a record | [``](#clonebutton), [``](./Edit.md) | +| `read` | Allow to view a field (or a tab) | [``](#datagrid), [``](#simpleshowlayout), [``](#tabbedshowlayout) | +| `write` | Allow to edit a field (or a tab) | [``](#simpleform), [``](#tabbedform), [``](./WizardForm.md#enableaccesscontrol), [``](./LongForm.md#enableaccesscontrol), [``](./AccordionForm.md#enableaccesscontrol) | + +**Tip:** Be sure not to confuse "show" and "read", or "edit" and "write", as they are not the same. The first operate at the page level, the second at the field level. A good mnemonic is to realize "show" and "edit" are named the same as the react-admin page they allow to control: the Show and Edit pages. + +You can also add your own actions, and use them in your own components using [`useCanAccess`](./useCanAccess.md) or [``](./CanAccess.md). + ### Role A *role* is a string that represents a responsibility. Examples of roles include "admin", "reader", "moderator", and "guest". A user can have one or more roles. @@ -134,37 +164,9 @@ const corrector123Role = [ **Tip**: The _order_ of permissions isn't significant. As soon as at least one permission grants access to an action on a resource, ra-rbac grant access to it - unless there is an [explicit deny](#explicit-deny). -The RBAC system relies on *permissions* only. It's the `authProvider`'s responsibility to map roles to permissions. See the [`authProvider` Methods](#authprovider-methods) section for details. - -### Action - -An _action_ is a string, usually a verb, that represents an operation. Examples of actions include "read", "create", "edit", "delete", or "export". - -Ra-rbac defines its own actions that you can use with ra-rbac components, but you can also define your own actions, and implement them in your own components using [`useCanAccess`](https://react-admin-ee.marmelab.com/documentation/ra-rbac#usecanaccess), [`canAccess`](https://react-admin-ee.marmelab.com/documentation/ra-rbac#canaccess) or [``](https://react-admin-ee.marmelab.com/documentation/ra-rbac#ifcanaccess). - -Ra-rbac's built-in actions operate at different levels: - -- **Page:** controls visibility of a page like the Edit page -- **Field:** controls visibility of a specific field, for example in a form -- **Action:** controls permission to perform global actions, like exporting data +### Pessimistic Strategy -Here are all the actions supported by ra-rbac: - -| Action | Level | Description | Used In | -| -------- | ------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `list` | Page | Allow to access the List page | [``](#resource), [``](#menu) | -| `show` | Page | Allow to access the Show page | [``](#resource), [``](#datagrid), [``](#edit), [``](#show) | -| `create` | Page | Allow to access the Create page | [``](#resource), [``](#list) | -| `edit` | Page | Allow to access the Edit page | [``](#resource), [``](#datagrid), [``](#edit), [``](#show) | -| `export` | Action | Allow to export data | [``](#list) | -| `delete` | Action | Allow to delete data | [``](#datagrid), [``](#simpleform), [``](#tabform) | -| `clone` | Action | Allow to clone a record | [``](#edit) | -| `read` | Field | Allow to view a field (or a tab) | [``](#datagrid), [``](#simpleshowlayout), [``](#tabbedshowlayout) | -| `write` | Field | Allow to edit a field (or a tab) | [``](#simpleform), [``](#tabbedform) | - -**Tip:** Be sure not to confuse "show" and "read", or "edit" and "write", as they are not the same. The first operate at the page level, the second at the field level. A good mnemonic is to realize "show" and "edit" are named the same as the react-admin page they allow to control: the Show and Edit pages. - -## Concepts +RBAC components treat permissions in a pessimistic way: while permissions are loading, react-admin doesn't render the components that require permissions, assuming that these components are restricted by default. It's only when the `authProvider.canAccess()` has resolved that RBAC components render. ### Principle Of Least Privilege @@ -181,14 +183,22 @@ By default, a permission applies to all records of a resource. A permission can be restricted to a specific record or a specific set of records. Setting the `record` field in a permission restricts the application of that permissions to records matching that criteria (using [lodash `isMatch`](https://lodash.com/docs/4.17.15#isMatch)). ```jsx -// can read all users, without record restriction -const perm1 = { action: "read", resource: "users" }; -// can write only user of id 123 -const perm2 = { action: "write", resource: "users", record: { "id": "123" } }; -// can access only comments by user of id 123 -const perm3 = { action: "*", resource: "comments", record: { "user_id": "123" } }; +// can view all users, without record restriction +const perm1 = { action: ['list', 'show'], resource: 'users' }; +const perm2 = { action: 'read', resource: 'users.*' }; +// can only edit field 'username' for user of id 123 +const perm4 = { action: 'write', resource: 'users.username', record: { id: '123' } }; ``` +Only record-level components can perform record-level permissions checks. Below is the list of components that support them: + +- [``](#simpleshowlayout) +- [``](#tabbedshowlayout) +- [``](#simpleform) +- [``](#tabbedform) + +When you restrict permissions to a specific set of records, components that do not support record-level permissions (such as List Components) will ignore the record criteria and perform their checks at the resource-level only. + ### Explicit Deny Some users may have access to all resources but one. Instead of having to list all the resources they have access to, you can use a special permission with the "deny" type that explicitly denies access to a resource. From 40b374d8b0ff71d387c84418ff5b83a5a419a69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 20:54:59 +0100 Subject: [PATCH 20/39] Document Datagrid access control in Datagrid doc --- docs/AuthRBAC.md | 78 +---------------------------- docs/Datagrid.md | 127 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 116 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index dc243f9ec0e..1eab05685ec 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -225,7 +225,7 @@ const allProductsButStock = [ ## Setup -Define role definitions in your application code, or fetch them from the API. +Define role definitions in your application code, or fetch them from an API. ```jsx export const roleDefinitions = { @@ -365,7 +365,7 @@ Ra-rbac provides alternative components to react-admin base components. These al - List - [``](#list) - [``](#listactions) - - [``](#datagrid) + - [``](./Datagrid.md#access-control) - Detail - [``](#simpleshowlayout) - [``](#tab) @@ -375,80 +375,6 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](#tabbedform) - [``](#formtab) -## `` - -Alternative to react-admin's `` that adds RBAC control to columns. - -- Users must have the `'delete'` permission on the resource to see the ``. -- Users must have the `'read'` permission on a resource column to see it in the export: - -```jsx -{ action: "read", resource: `${resource}.${source}` }. -// or -{ action: "read", resource: `${resource}.*` }. -``` - -Also, the `rowClick` prop is automatically set depending on the user props: - -- `"edit"` if the user has the permission to edit the resource -- `"show"` if the user doesn't have the permission to edit the resource but has the permission to show it -- empty otherwise - -```tsx -import { canAccessWithPermissions, List, Datagrid } from '@react-admin/ra-rbac'; -import { - ImageField, - TextField, - ReferenceField, - NumberField, -} from 'react-admin'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - { action: 'list', resource: 'products' }, - { action: 'read', resource: 'products.thumbnail' }, - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.category_id' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - { action: 'read', resource: 'products.price' }, - { action: 'read', resource: 'products.description' }, - ], - action, - record, - resource - }), -}; - -const ProductList = () => ( - - {/* ra-rbac Datagrid */} - - - - - - - - - - - {/** these two columns are not visible to the user **/} - - - - -); -``` - -**Tip**: Adding the 'read' permission on the resource itself doesn't grant the 'read' permission on the columns. If you want a user to see all possible columns, add the 'read' permission on columns using a wildcard: - -```jsx -{ action: "read", resource: "products.*" }. -``` ## `` diff --git a/docs/Datagrid.md b/docs/Datagrid.md index 1bb8e4ef26e..2190716a9e6 100644 --- a/docs/Datagrid.md +++ b/docs/Datagrid.md @@ -1074,52 +1074,99 @@ Additionally, `` is compatible with the [Enterprise version of ag-gr Check [the `` documentation](./DatagridAG.md) for more details. -## Fields And Permissions +## Access Control -You might want to display some fields only to users with specific permissions. Use [the `useCanAccess` hook](./useCanAccess.md) to check whether users have access to a field: +If you need to hide some columns based on a set of permissions, use the `` component from the `@react-admin/ra-rbac` package. + +```diff +-import { Datagrid } from 'react-admin'; ++import { Datagrid } from '@react-admin/ra-rbac'; +``` + +This component adds the following [RBAC](./AuthRBAC.md) controls: + +- Users must have the `'read'` permission on a resource column to see it in the export: + +```jsx +{ action: "read", resource: `${resource}.${source}` }. +// or +{ action: "read", resource: `${resource}.*` }. +``` + +- Users must have the `'delete'` permission on the resource to see the ``. + +- The default `rowClick` depends on the user permissions: + - `"edit"` if the user can access the current resource with the `edit` action + - `"show"` if the user can access the current resource with the `show` action + - empty otherwise + +Here is an example of `` with RBAC: -{% raw %} ```tsx -import { List, Datagrid, TextField, TextInput, ShowButton, useCanAccess } from 'react-admin'; +import { canAccessWithPermissions, List, Datagrid } from '@react-admin/ra-rbac'; +import { + ImageField, + TextField, + ReferenceField, + NumberField, +} from 'react-admin'; -const getUserFilters = (canAccessRole) => ([ - , - , - canAccessRole ? : null, - ].filter(filter => filter !== null) -); +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: 'list', resource: 'products' }, + { action: 'read', resource: 'products.thumbnail' }, + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.category_id' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + { action: 'read', resource: 'products.price' }, + { action: 'read', resource: 'products.description' }, + // { action: 'read', resource: 'products.stock' }, + // { action: 'read', resource: 'products.sales' }, + // { action: 'delete', resource: 'products' }, + { action: 'show', resource: 'products' }, + ], + action, + record, + resource + }), +}; -export const UserList = ({ permissions, ...props }) => { - const { canAccess, error, isPending } = useCanAccess({ - resource: 'users.role', - action: 'read' - }); - return ( - - - +const ProductList = () => ( + + {/* The datagrid has no bulk actions as the user doesn't have the 'delete' permission */} + + + + - {canAccess ? : null} - - - - - ) -}; + + + + + + {/** These two columns are not visible to the user **/} + + + + +); ``` -{% endraw %} -Note how the `canAccess` value is passed down to the custom `filters` component to allow Filter customization, too. +**Tip**: Adding the 'read' permission on the resource itself doesn't grant the 'read' permission on the columns. If you want a user to see all possible columns, add the 'read' permission on columns using a wildcard: + +```jsx +{ action: "read", resource: "products.*" }. +``` -Should you need to check multiple fields, you can leverage [the `useCanAccessResources` hook](./useCanAccess.md#multiple-resources): +Fow simple cases, you can also use [the `useCanAccess` hook](./useCanAccess.md) to check whether users have access to a field: {% raw %} ```tsx -import { List, Datagrid, TextField, TextInput, ShowButton, useCanAccessResources } from 'react-admin'; +import { List, Datagrid, TextField, TextInput, ShowButton, useCanAccess } from 'react-admin'; const getUserFilters = (canAccessRole) => ([ , @@ -1129,8 +1176,8 @@ const getUserFilters = (canAccessRole) => ([ ); export const UserList = ({ permissions, ...props }) => { - const { canAccess, error, isPending } = useCanAccessResources({ - resources: ['users.id', 'users.name', 'users.role'], + const { canAccess, error, isPending } = useCanAccess({ + resource: 'users.role', action: 'read' }); return ( @@ -1140,9 +1187,9 @@ export const UserList = ({ permissions, ...props }) => { sort={{ field: 'name', order: 'ASC' }} > - {canAccess['users.id'] ? : null} - {canAccess['users.name'] ? : null} - {canAccess['users.role'] ? : null} + + + {canAccess ? : null} @@ -1152,7 +1199,7 @@ export const UserList = ({ permissions, ...props }) => { ``` {% endraw %} -**Tip**: The [ra-rbac module](./AuthRBAC.md#datagrid) provides a wrapper for the `` with built-in permission check for columns. +Note how the `canAccess` value is passed down to the custom `filters` component to allow Filter customization, too. ## Standalone Usage From a98cd5a8cef736eb7b64621cb59328823c8a2e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:04:50 +0100 Subject: [PATCH 21/39] Move the List RBAC features to the List doc --- docs/AuthRBAC.md | 81 +----------------------------------- docs/List.md | 105 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index 1eab05685ec..d704c9cba36 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -363,7 +363,7 @@ Ra-rbac provides alternative components to react-admin base components. These al - Main - [``](#menu) - List - - [``](#list) + - [``](./List.md#access-control) - [``](#listactions) - [``](./Datagrid.md#access-control) - Detail @@ -376,85 +376,6 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](#formtab) -## `` - -Replacement for react-admin's `` that adds RBAC control to actions, and to the default export function. - -- Users must have the `'create'` permission on the resource to see the ``. -- Users must have the `'export'` permission on the resource to see the ``. -- Users must have the `'read'` permission on a resource column to see it in the export: - -```jsx -{ action: "read", resource: `${resource}.${source}` }. -// -{ action: "read", resource: `${resource}.*` }. -``` - -```tsx -import { List } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async () => - canAccessWithPermissions({ - permissions: [ - { action: 'list', resource: 'products' }, - { action: 'export', resource: 'products' }, - // actions 'create' and 'delete' are missing - { action: 'read', resource: 'products.name' }, - { action: 'read', resource: 'products.description' }, - { action: 'read', resource: 'products.price' }, - { action: 'read', resource: 'products.category' }, - // resource 'products.stock' is missing - ], - action, - resource, - record - }), -}; - -export const PostList = () => ( - - {/*...*/} - -); -// Users will see the Export action on top of the list, but not the Create action. -// Users will only see the authorized columns when clicking on the export button. -``` - -**Tip**: If you need a custom [`exporter`](./List.md#exporter), you can use `useExporterWithAccessControl` to apply access control to the exported records: - -```tsx -import { List, useExporterWithAccessControl } from '@ra-enterprise/ra-rbac'; -import { myExporter } from './myExporter'; - -export const PostList = () => { - const exporter = useExporterWithAccessControl({ exporter: myExporter }) - return ( - - {/*...*/} - - ); -} - -``` - -**Tip**: This `` component relies on [the `` component](#listactions) below. - -## `` - -Replacement for react-admin's `` that adds RBAC control to actions - -- Users must have the `'create'` permission on the resource to see the ``. -- Users must have the `'export'` permission on the resource to see the ``. - -```jsx -import { List } from 'react-admin'; -import { ListActions } from '@react-admin/ra-rbac'; - -export const PostList = () => }>...; -``` - ## `` Replacement for react-admin's [``](./Buttons.md#exportbutton) that checks users have the `'export'` permission before rendering. Use it if you want to provide your own `actions` to the ``: diff --git a/docs/List.md b/docs/List.md index 9849282192b..363bf227542 100644 --- a/docs/List.md +++ b/docs/List.md @@ -673,6 +673,22 @@ const CommentList = () => ( **Tip**: You may also remove the `` by passing `false` to the `exporter` prop: `exporter={false}` +**Tip**: If you need to use [RBAC](./AuthRBAC.md) to hide some columns based on user permissions, you can use `useExporterWithAccessControl` to apply access control to the exported records: + +```tsx +import { List, useExporterWithAccessControl } from '@ra-enterprise/ra-rbac'; +import { myExporter } from './myExporter'; + +export const PostList = () => { + const exporter = useExporterWithAccessControl({ exporter: myExporter }) + return ( + + {/*...*/} + + ); +} +``` + **Tip**: Looking for an ``? React-admin doesn't provide this feature, but the community has an excellent third-party module for CSV import: [benwinding/react-admin-import-csv](https://github.com/benwinding/react-admin-import-csv). ## `filters`: Filter Inputs @@ -1354,13 +1370,25 @@ const ProductList = () => { `useListController` returns callbacks to sort, filter, and paginate the list, so you can build a complete List page. Check [the `useListController`hook documentation](./useListController.md) for details. -## Security +## Anonymous Access The `` component requires authentication and will redirect anonymous users to the login page. If you want to allow anonymous access, use the [`disableAuthentication`](#disableauthentication) prop. -If your `authProvider` implements [Access Control](./Permissions.md#access-control), `` will only render if the user has the "list" access to the related resource. +```jsx +import { List } from 'react-admin'; + +const BoolkList = () => ( + + ... + +); +``` + +## Access Control -For instance, for the `` page below: +If your `authProvider` implements [Access Control](./Permissions.md#access-control), `` will only render if the user can access the resource with the "list" action. + +For instance, to render the `` page below: ```tsx import { List, Datagrid, TextField } from 'react-admin'; @@ -1385,4 +1413,73 @@ const PostList = () => ( Users without access will be redirected to the [Access Denied page](./Admin.md#accessdenied). -**Note**: Access control is disabled when you use [the `disableAuthentication` prop](#disableauthentication). \ No newline at end of file +**Note**: Access control is disabled when you use [the `disableAuthentication` prop](#disableauthentication). + +For finer access control of the list action buttons, use the `` component from the `@react-admin/ra-rbac` package. + +```diff +-import { List } from 'react-admin'; ++import { List } from '@react-admin/ra-rbac'; +``` + +This component adds the following [RBAC](./AuthRBAC.md) controls: + +- Users must have the `'create'` permission on the resource to see the ``. +- Users must have the `'export'` permission on the resource to see the ``. +- Users must have the `'read'` permission on a resource column to see it in the export: + +```jsx +{ action: "read", resource: `${resource}.${source}` }. +// +{ action: "read", resource: `${resource}.*` }. +``` + +Here is an example of `` with RBAC: + +```tsx +import { List } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + canAccess: async () => + canAccessWithPermissions({ + permissions: [ + { action: 'list', resource: 'products' }, + { action: 'export', resource: 'products' }, + // actions 'create' and 'delete' are missing + { action: 'read', resource: 'products.name' }, + { action: 'read', resource: 'products.description' }, + { action: 'read', resource: 'products.price' }, + { action: 'read', resource: 'products.category' }, + // resource 'products.stock' is missing + ], + action, + resource, + record + }), +}; + +export const PostList = () => ( + + {/*...*/} + +); +// Users will see the Export action on top of the list, but not the Create action. +// Users will only see the authorized columns when clicking on the export button. +``` + +**Tip**: If you need a custom [`exporter`](#exporter), you can use `useExporterWithAccessControl` to apply access control to the exported records: + +```tsx +import { List, useExporterWithAccessControl } from '@ra-enterprise/ra-rbac'; +import { myExporter } from './myExporter'; + +export const PostList = () => { + const exporter = useExporterWithAccessControl({ exporter: myExporter }) + return ( + + {/*...*/} + + ); +} +``` From c2b7550264d0bd2dc5c6dee3003efadd5f39cb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:19:00 +0100 Subject: [PATCH 22/39] Move SimpleShowLayout RBAC doc to SimpleShowLayout chapter --- docs/AuthRBAC.md | 58 +--------------------------------------- docs/SimpleShowLayout.md | 58 ++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 74 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index d704c9cba36..ee9b2faee6d 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -364,10 +364,9 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](#menu) - List - [``](./List.md#access-control) - - [``](#listactions) - [``](./Datagrid.md#access-control) - Detail - - [``](#simpleshowlayout) + - [``](./SimpleShowLayout.md#access-control) - [``](#tab) - [``](#tab) - Form @@ -375,7 +374,6 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](#tabbedform) - [``](#formtab) - ## `` Replacement for react-admin's [``](./Buttons.md#exportbutton) that checks users have the `'export'` permission before rendering. Use it if you want to provide your own `actions` to the ``: @@ -491,60 +489,6 @@ const ProductEdit = () => ( ); ``` -## `` - -Alternative to react-admin's `` that adds RBAC control to fields - -To see a column, the user must have the permission to read the resource column: - -```jsx -{ action: "read", resource: `${resource}.${source}` } -// Or -{ action: "read", resource: `${resource}.*` } -``` - -```tsx -import { SimpleShowLayout } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - { action: ['list', 'show'], resource: 'products' }, - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - // 'products.description' is missing - // 'products.image' is missing - { action: 'read', resource: 'products.thumbnail' }, - // 'products.stock' is missing - ], - action, - record, - resource, - }), -}; - -const ProductShow = () => ( - - - {/* <-- RBAC SimpleShowLayout */} - - - - {/* not displayed */} - - {/* not displayed */} - - - {/* not displayed */} - - - -); -``` - ## `` Replacement for react-admin's `` that only renders a tab if the user has the right permissions. diff --git a/docs/SimpleShowLayout.md b/docs/SimpleShowLayout.md index faee080d811..a0a0350e23c 100644 --- a/docs/SimpleShowLayout.md +++ b/docs/SimpleShowLayout.md @@ -261,44 +261,68 @@ const PostShow = () => ( ); ``` -## Role-Based Access Control (RBAC) +## Access Control -You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedshowlayout) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. +If you need to hide some fields based on a set of permissions, use the `` component from the `@react-admin/ra-rbac` package. -[``](./AuthRBAC.md#tabbedshowlayout) shows only the tabs for which users have read permissions, using the `[resource].tab.[tabName]` string as resource identifier. `` shows only the child fields for which users have the read permissions, using the `[resource].[source]` string as resource identifier. +```diff +-import { SimpleShowLayout } from 'react-admin'; ++import { SimpleShowLayout } from '@react-admin/ra-rbac'; +``` + +This component adds the following [RBAC](./AuthRBAC.md) controls: + +- To see a column, the user must have the "read" permission on the resource column: + +```jsx +{ action: "read", resource: `${resource}.${source}` } +// Or +{ action: "read", resource: `${resource}.*` } +``` + +Here is an example of how to use the `` component with RBAC: -{% raw %} ```tsx -import { Show, TextField } from 'react-admin'; import { SimpleShowLayout } from '@react-admin/ra-rbac'; const authProvider = { // ... - getPermissions: () => Promise.resolve([ - // crud - { action: ['list', 'show'], resource: 'products' }, - // fields ('products.description' is missing) - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - ]), + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: ['list', 'show'], resource: 'products' }, + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + // 'products.description' is missing + // 'products.image' is missing + { action: 'read', resource: 'products.thumbnail' }, + // 'products.stock' is missing + ], + action, + record, + resource, + }), }; const ProductShow = () => ( + {/* └── RBAC SimpleShowLayout */} - {/* the description field is not displayed */} + {/* not displayed */} + {/* not displayed */} + + + {/* not displayed */} + ); ``` -{% endraw %} - -Check [the RBAC `` component](./AuthRBAC.md#simpleshowlayout) documentation for more details. ## See Also From 5a33cfd65b225a78ddb54fe368eeb3f02e1d0a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:22:50 +0100 Subject: [PATCH 23/39] Move Simpleform RBAC doc to SimpleForm chapter --- docs/AuthRBAC.md | 55 +--------------------------------- docs/SimpleForm.md | 73 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 71 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index ee9b2faee6d..3779206572e 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -370,7 +370,7 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](#tab) - [``](#tab) - Form - - [``](#simpleform) + - [``](./SimpleForm.md#access-control) - [``](#tabbedform) - [``](#formtab) @@ -436,59 +436,6 @@ export const MyMenu = () => ( ); ``` -## `` - -Alternative to react-admin's `` that shows/hides inputs based on roles and permissions. - -To see an input, the user must have the permission to write the resource field: - -```jsx -{ action: "write", resource: `${resource}.${source}` } -``` - -`` also renders the delete button only if the user has the 'delete' permission. - -```tsx -import { Edit, TextInput } from 'react-admin'; -import { SimpleForm } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - // 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - ] - action, - record, - resource, - }), -}; - -const ProductEdit = () => ( - - - - - - {/* not displayed */} - - {/* not displayed */} - - - {/* no delete button */} - - -); -``` - ## `` Replacement for react-admin's `` that only renders a tab if the user has the right permissions. diff --git a/docs/SimpleForm.md b/docs/SimpleForm.md index 812b938802d..fca42b070a6 100644 --- a/docs/SimpleForm.md +++ b/docs/SimpleForm.md @@ -512,28 +512,67 @@ const { isDirty } = useFormState(); // ✅ const formState = useFormState(); // ❌ should deconstruct the formState ``` -## Displaying Inputs Based On Permissions +## Access Control -You can leverage [the `` component](./CanAccess.md) to display inputs if the user has the required access rights. +If you need to hide some inputs based on a set of permissions, use the `` component from the `@react-admin/ra-rbac` package. + +```diff +-import { SimpleForm } from 'react-admin'; ++import { SimpleForm } from '@react-admin/ra-rbac'; +``` + +This component adds the following [RBAC](./AuthRBAC.md) controls: + +- To see an input, the user must have the 'write' permission on the resource field: -{% raw %} ```jsx -import { CanAccess, Create, SimpleForm, TextInput } from 'react-admin'; +{ action: "write", resource: `${resource}.${source}` } +``` -export const UserCreate = () => { - return ( - - - - - - - - - ); -} +- The delete button only renders if the user has the 'delete' permission. + +Here is an example of how to use the `` component with RBAC: + +```tsx +import { Edit, TextInput } from 'react-admin'; +import { SimpleForm } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + // 'delete' is missing + { action: ['list', 'edit'], resource: 'products' }, + { action: 'write', resource: 'products.reference' }, + { action: 'write', resource: 'products.width' }, + { action: 'write', resource: 'products.height' }, + // 'products.description' is missing + { action: 'write', resource: 'products.thumbnail' }, + // 'products.image' is missing + ] + action, + record, + resource, + }), +}; + +const ProductEdit = () => ( + + + + + + {/* not displayed */} + + {/* not displayed */} + + + {/* no delete button */} + + +); ``` -{% endraw %} ## Configurable From ff42f32d54e97bfba345310ac1f839ff77684deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:30:16 +0100 Subject: [PATCH 24/39] Move Menu RBAC doc to the Menu chapter --- docs/AuthRBAC.md | 26 +------------------------- docs/Menu.md | 40 +++++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index 3779206572e..f3db263ed7a 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -361,7 +361,7 @@ const authProvider = { Ra-rbac provides alternative components to react-admin base components. These alternative components include role-based access control and are as follows: - Main - - [``](#menu) + - [``](./Menu.md#access-control) - List - [``](./List.md#access-control) - [``](./Datagrid.md#access-control) @@ -412,30 +412,6 @@ It accepts the following props in addition to the default [`` prop { action: "read", resource: `${resource}.*` }. ``` -## `` - -If you want to add custom pages to the menu, you can use `ra-rbac`'s `` component. It will only display the menu item if the user has access to the specified action and resource. - -```tsx -import { Menu } from '@react-admin/ra-rbac'; - -export const MyMenu = () => ( - - {/* Resource menu items already have access control built-in */} - - {/* For custom menu items, you can specify a resource and action */} - - {/* this menu item will render for all users */} - - -); -``` - ## `` Replacement for react-admin's `` that only renders a tab if the user has the right permissions. diff --git a/docs/Menu.md b/docs/Menu.md index 2273ed232d6..14104f6bacc 100644 --- a/docs/Menu.md +++ b/docs/Menu.md @@ -466,32 +466,34 @@ const MyReactAdmin = () => ( ![MenuLive](./img/MenuLive.png) -## Displaying Menu Items Based On Permissions +## Access Control -If you `authProvider` implements [the `canAccess` method](./Permissions.md#authprovidercanaccess), the `` component will call it for each resource with the follwing parameters: -- `resource`: the resource name -- `action`: `list` +If you `authProvider` supports [Access Control](./Permissions.md#access-control), the `` component will use it to only render the `` for which the user has the `list` permission. -However, if you have a custom menu and want to show items based on the user permissions, you can leverage [the `` component](./CanAccess.md): +If you want to add access control to custom menu items, use the `` component from the `@react-admin/ra-rbac` package. + +```diff +-import { Menu } from 'react-admin'; ++import { Menu } from '@react-admin/ra-rbac'; +``` + +This Menu component only display a `` if the user has access to the specified `action` and `resource`. -{% raw %} ```tsx -// in src/MyMenu.js -import { CanAccess, Menu } from 'react-admin'; -import LabelIcon from '@mui/icons-material/Label'; +import { Menu } from '@react-admin/ra-rbac'; export const MyMenu = () => ( - - - - - - } /> - + + {/* This menu item will render only if the user has 'list' access to the 'products' resource */} + + {/* This menu item will render for all users */} + ); ``` -{% endraw %} - -**Tip**: the `resource` and `action` parameter accept any string value. From 30ff4d33423ddf915119ffba101ff9eba16afdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:43:58 +0100 Subject: [PATCH 25/39] Move TabbedShowLayout RBAC doc to TabbedshowLayout chapter --- docs/AuthRBAC.md | 114 +-------------------------------------- docs/TabbedShowLayout.md | 98 +++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 135 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index f3db263ed7a..877773b46a1 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -367,8 +367,7 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](./Datagrid.md#access-control) - Detail - [``](./SimpleShowLayout.md#access-control) - - [``](#tab) - - [``](#tab) + - [``](./TabbedShowLayout.md#access-control) - Form - [``](./SimpleForm.md#access-control) - [``](#tabbedform) @@ -412,117 +411,6 @@ It accepts the following props in addition to the default [`` prop { action: "read", resource: `${resource}.*` }. ``` -## `` - -Replacement for react-admin's `` that only renders a tab if the user has the right permissions. - -Use it in conjunction with [``](#tabbedshowlayouttab) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'read' permissions for. - -**Tip:** [``](#tabbedshowlayouttab) also allows to only render the child fields for which the user has the 'read' permissions. - -```tsx -import { Show, TextField } from 'react-admin'; -import { TabbedShowLayout } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - { action: ['list', 'show'], resource: 'products' }, - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - { action: 'read', resource: 'products.thumbnail' }, - { action: 'read', resource: 'products.tab.description' }, - // 'products.tab.stock' is missing - { action: 'read', resource: 'products.tab.images' }, - ], - action, - record, - resource, - }), -}; - -const ProductShow = () => ( - - - - - - - - - {/* Tab Stock is not displayed */} - - - - - - - - - -); -``` - -## `` - -Replacement for react-admin's `` that only renders a tab and its content if the user has the right permissions. - -Add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'read' permissions for. - -`` also only renders the child fields for which the user has the 'read' permissions. - -```tsx -import { Show, TextField } from 'react-admin'; -import { TabbedShowLayout } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - { action: ['list', 'show'], resource: 'products' }, - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - // 'products.description' is missing - { action: 'read', resource: 'products.thumbnail' }, - // 'products.image' is missing - { action: 'read', resource: 'products.tab.description' }, - // 'products.tab.stock' is missing - { action: 'read', resource: 'products.tab.images' }, - ], - action, - record, - resource, - }), -}; - -const ProductShow = () => ( - - - - - - - {/* Field Description is not displayed */} - - - {/* Tab Stock is not displayed */} - - - - - {/* Field Image is not displayed */} - - - - - -); -``` ## `` diff --git a/docs/TabbedShowLayout.md b/docs/TabbedShowLayout.md index 8a0b24c9698..0ec3fcd4e7e 100644 --- a/docs/TabbedShowLayout.md +++ b/docs/TabbedShowLayout.md @@ -249,7 +249,6 @@ const PostShow = () => ( ); ``` - The `` uses the humanized source by default. You can customize it by passing a `label` prop to the fields: ```jsx @@ -345,31 +344,39 @@ const StaticPostShow = () => ( When passed a `record`, `` creates a `RecordContext` with the given record. -## Role-Based Access Control (RBAC) +## Access Control -You can show or hide tabs and inputs based on user permissions by using the [``](./AuthRBAC.md#tabbedshowlayout) component from the `@react-admin/ra-rbac` package instead of the `react-admin` package. +If you need to hide some tabs based on a set of permissions, use the `` component from the `@react-admin/ra-rbac` package. -[``](./AuthRBAC.md#tabbedshowlayout) shows only the tabs for which users have read permissions, using the `[resource].tab.[tabName]` string as resource identifier. `` shows only the child fields for which users have the read permissions, using the `[resource].[source]` string as resource identifier. +```diff +-import { TabbedShowLayout } from 'react-admin'; ++import { TabbedShowLayout } from '@react-admin/ra-rbac'; +``` + +Use it in conjunction with [``](#tabbedshowlayouttab) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'read' permissions for. -{% raw %} ```tsx import { Show, TextField } from 'react-admin'; import { TabbedShowLayout } from '@react-admin/ra-rbac'; const authProvider = { // ... - getPermissions: () => Promise.resolve([ - // crud - { action: ['list', 'show'], resource: 'products' }, - // tabs ('products.tab.stock' is missing) - { action: 'read', resource: 'products.tab.description' }, - { action: 'read', resource: 'products.tab.images' }, - // fields ('products.description' and 'products.image' are missing) - { action: 'read', resource: 'products.reference' }, - { action: 'read', resource: 'products.width' }, - { action: 'read', resource: 'products.height' }, - { action: 'read', resource: 'products.thumbnail' }, - ]), + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: ['list', 'show'], resource: 'products' }, + { action: 'read', resource: 'products.tab.description' }, + // { action: 'read', resource: 'products.tab.stock' }, + { action: 'read', resource: 'products.tab.images' }, + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + { action: 'read', resource: 'products.thumbnail' }, + ], + action, + record, + resource, + }), }; const ProductShow = () => ( @@ -379,15 +386,13 @@ const ProductShow = () => ( - {/* the description field is not displayed */} - {/* the stock tab is not displayed */} + {/* This tab is not displayed for the user */} - {/* the images field is not displayed */} @@ -395,9 +400,58 @@ const ProductShow = () => ( ); ``` -{% endraw %} -Check [the RBAC `` component](./AuthRBAC.md#tabbedshowlayout) documentation for more details. +[``](#tabbedshowlayouttab) also renders only the child fields for which the user has the 'read' permissions. + +```tsx +import { Show, TextField } from 'react-admin'; +import { TabbedShowLayout } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: ['list', 'show'], resource: 'products' }, + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + // 'products.description' is missing + { action: 'read', resource: 'products.thumbnail' }, + // 'products.image' is missing + { action: 'read', resource: 'products.tab.description' }, + // 'products.tab.stock' is missing + { action: 'read', resource: 'products.tab.images' }, + ], + action, + record, + resource, + }), +}; + +const ProductShow = () => ( + + + + + + + {/* Field Description is not displayed */} + + + {/* Tab Stock is not displayed */} + + + + + {/* Field Image is not displayed */} + + + + + +); +``` ## See Also From 3011173ad0e997889771133138b3963942bad3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 21:52:03 +0100 Subject: [PATCH 26/39] Move TabbedForm RBAC doc to TabbedForm chapter --- docs/AuthRBAC.md | 142 +++------------------------------ docs/TabbedForm.md | 194 +++++++++++++++++++++++---------------------- 2 files changed, 111 insertions(+), 225 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index 877773b46a1..c6578472bd4 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -104,15 +104,15 @@ React-admin already does page-level access control with actions like "list", "sh | Action | Description | Used In | | -------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `list` | Allow to access the List page | [``](#list), [``](./Buttons.md#listbutton), [``](#menu) | -| `show` | Allow to access the Show page | [``](./Show.md), [``](./Buttons.md#showbutton), [``](#datagrid), [``](./Edit.md) | -| `create` | Allow to access the Create page | [``](./Create.md), [``](./Buttons.md#createbutton), [``](#list) | -| `edit` | Allow to access the Edit page | [``](./Edit.md), [``](./Buttons.md#editbutton), [``](#datagrid), [``](./Show.md) | -| `delete` | Allow to delete data | [``](./Buttons.md#deletebutton), [``](./Buttons.md#bulkdeletebutton), [``](#datagrid), [``](#simpleform), [``](#tabform) | -| `export` | Allow to export data | [``](#exportbutton), [``](#list) | +| `list` | Allow to access the List page | [``](./List.md#access-control), [``](./Buttons.md#listbutton), [``](./Menu.md#access-control) | +| `show` | Allow to access the Show page | [``](./Show.md), [``](./Buttons.md#showbutton), [``](./Datagrid.md#access-control), [``](./Edit.md) | +| `create` | Allow to access the Create page | [``](./Create.md), [``](./Buttons.md#createbutton), [``](./List.md#access-control) | +| `edit` | Allow to access the Edit page | [``](./Edit.md), [``](./Buttons.md#editbutton), [``](./Datagrid.md#access-control), [``](./Show.md) | +| `delete` | Allow to delete data | [``](./Buttons.md#deletebutton), [``](./Buttons.md#bulkdeletebutton), [``](./Datagrid.md#access-control), [``](./SimpleForm.md#access-control), [``](#tabform) | +| `export` | Allow to export data | [``](#exportbutton), [``](./List.md#access-control) | | `clone` | Allow to clone a record | [``](#clonebutton), [``](./Edit.md) | -| `read` | Allow to view a field (or a tab) | [``](#datagrid), [``](#simpleshowlayout), [``](#tabbedshowlayout) | -| `write` | Allow to edit a field (or a tab) | [``](#simpleform), [``](#tabbedform), [``](./WizardForm.md#enableaccesscontrol), [``](./LongForm.md#enableaccesscontrol), [``](./AccordionForm.md#enableaccesscontrol) | +| `read` | Allow to view a field (or a tab) | [``](./Datagrid.md#access-control), [``](./SimpleShowLayout.md#access-control), [``](./TabbedShowLayout.md#access-control) | +| `write` | Allow to edit a field (or a tab) | [``](./SimpleForm.md#access-control), [``](./TabbedForm.md#access-control), [``](./WizardForm.md#enableaccesscontrol), [``](./LongForm.md#enableaccesscontrol), [``](./AccordionForm.md#enableaccesscontrol) | **Tip:** Be sure not to confuse "show" and "read", or "edit" and "write", as they are not the same. The first operate at the page level, the second at the field level. A good mnemonic is to realize "show" and "edit" are named the same as the react-admin page they allow to control: the Show and Edit pages. @@ -192,10 +192,10 @@ const perm4 = { action: 'write', resource: 'users.username', record: { id: '123' Only record-level components can perform record-level permissions checks. Below is the list of components that support them: -- [``](#simpleshowlayout) -- [``](#tabbedshowlayout) -- [``](#simpleform) -- [``](#tabbedform) +- [``](./SimpleShowLayout.md#access-control) +- [``](./TabbedShowLayout.md#access-control) +- [``](./SimpleForm.md#access-control) +- [``](./TabbedForm.md#access-control) When you restrict permissions to a specific set of records, components that do not support record-level permissions (such as List Components) will ignore the record criteria and perform their checks at the resource-level only. @@ -370,8 +370,7 @@ Ra-rbac provides alternative components to react-admin base components. These al - [``](./TabbedShowLayout.md#access-control) - Form - [``](./SimpleForm.md#access-control) - - [``](#tabbedform) - - [``](#formtab) + - [``](./TabbedForm.md#access-control) ## `` @@ -411,121 +410,6 @@ It accepts the following props in addition to the default [`` prop { action: "read", resource: `${resource}.*` }. ``` - -## `` - -Replacement for react-admin's `` that adds RBAC control to the delete button (conditioned by the `'delete'` action) and only renders a tab if the user has the right permissions. - -Use in conjunction with [``](#tabbedformtab) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'write' permissions for. - -**Tip:** [``](#tabbedformtab) also allows to only render the child inputs for which the user has the 'write' permissions. - -```jsx -import { Edit, TextInput } from 'react-admin'; -import { TabbedForm } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - // action 'delete' is missing - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - { action: 'write', resource: 'products.thumbnail' }, - { action: 'write', resource: 'products.tab.description' }, - // tab 'stock' is missing - { action: 'write', resource: 'products.tab.images' }, - ], - action, - record, - resource, - }), -}; - -const ProductEdit = () => ( - - - - - - - - - {/* the "Stock" tab is not displayed */} - - - - - - - - {/* the "Delete" button is not displayed */} - - -); -``` - -## `` - -Replacement for react-admin's `` that only renders a tab and its content if the user has the right permissions. - -Add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'write' permissions for. - -`` also only renders the child inputs for which the user has the 'write' permissions. - -```tsx -import { Edit, TextInput } from 'react-admin'; -import { TabbedForm } from '@react-admin/ra-rbac'; - -const authProvider = { - // ... - canAccess: async ({ action, record, resource }) => - canAccessWithPermissions({ - permissions: [ - { action: ['list', 'edit'], resource: 'products' }, - { action: 'write', resource: 'products.reference' }, - { action: 'write', resource: 'products.width' }, - { action: 'write', resource: 'products.height' }, - // 'products.description' is missing - { action: 'write', resource: 'products.thumbnail' }, - // 'products.image' is missing - { action: 'write', resource: 'products.tab.description' }, - // 'products.tab.stock' is missing - { action: 'write', resource: 'products.tab.images' }, - ], - action, - record, - resource, - }) -}; - -const ProductEdit = () => ( - - - - - - - {/* Input Description is not displayed */} - - - {/* Input Stock is not displayed */} - - - - - {/* Input Image is not displayed */} - - - - - -); -``` - ## `` Replacement for react-admin's [``](./Buttons.md#clonebutton) that checks users have the `'clone'` permission before rendering. Use it if you want to provide your own `actions` to the ``: diff --git a/docs/TabbedForm.md b/docs/TabbedForm.md index 7923187c52c..eb9c9bbc459 100644 --- a/docs/TabbedForm.md +++ b/docs/TabbedForm.md @@ -794,114 +794,116 @@ If you're using it in an `` page, you must also use a `pessimistic` or `op Check [the `` component](./AutoSave.md) documentation for more details. -## Displaying a Tab Based On Permissions +## Access Control -You can leverage [the `useCanAccess` hook](./useCanAccess.md) to display tabs if the user has the required access rights. +If you need to hide some tabs based on a set of permissions, use the `` component from the `@react-admin/ra-rbac` package. -{% raw %} -```jsx -import { Edit, FormTab, Loading, TabbedForm, TextInput, useCanAccess } from 'react-admin'; -import { Alert } from '@mui/material'; - -export const UserCreate = () => { - const { canAccess, isPending, error } = useCanAccess({ - resource: 'users.tabs.security', - action: 'write', - }); - if (isPending) return ; - if (error) { - return ( - - An error occurred while checking your permissions - - ); - } - - return ( - - - - ... - - {canAccess && - - ... - - } - - - ); -} +```diff +-import { TabbedForm } from 'react-admin'; ++import { TabbedForm } from '@react-admin/ra-rbac'; ``` -{% endraw %} -If you need to check access rights for multiple tabs, leverage [the `useCanAccessResources` hook](./useCanAccess.md#multiple-resources). +Use in conjunction with [``](#tabbedformtab) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'write' permissions for. -{% raw %} ```jsx -import { Edit, Loading, TabbedForm, TextInput, useCanAccessResources } from 'react-admin'; -import { Alert } from '@mui/material'; - -export const UserEdit = () => { - const { canAccess, isPending, error } = useCanAccessResources({ - resources: ['users.tabs.summary', 'users.tabs.security'], - action: 'write', - }); - if (isPending) return ; - if (error) { - return ( - - An error occurred while checking your permissions - - ); - } +import { Edit, TextInput } from 'react-admin'; +import { TabbedForm } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + // action 'delete' is missing + { action: ['list', 'edit'], resource: 'products' }, + { action: 'write', resource: 'products.reference' }, + { action: 'write', resource: 'products.width' }, + { action: 'write', resource: 'products.height' }, + { action: 'write', resource: 'products.thumbnail' }, + { action: 'write', resource: 'products.tab.description' }, + // tab 'stock' is missing + { action: 'write', resource: 'products.tab.images' }, + ], + action, + record, + resource, + }), +}; - return ( - - - {canAccess['users.tabs.summary'] && - - ... - } - {canAccess['users.tabs.security'] && - - ... - - } - - - ); -} +const ProductEdit = () => ( + + + + + + + + + {/* the "Stock" tab is not displayed */} + + + + + + + + {/* the "Delete" button is not displayed */} + + +); ``` -{% endraw %} - -## Displaying Inputs Based On Permissions -You can leverage [the `` component](./CanAccess.md) to display inputs if the user has the required access rights. +[``](#tabbedformtab) also renders only the child inputs for which the user has the 'write' permissions. -{% raw %} -```jsx -import { CanAccess, Edit, TabbedForm, TextInput } from 'react-admin'; +```tsx +import { Edit, TextInput } from 'react-admin'; +import { TabbedForm } from '@react-admin/ra-rbac'; + +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: ['list', 'edit'], resource: 'products' }, + { action: 'write', resource: 'products.reference' }, + { action: 'write', resource: 'products.width' }, + { action: 'write', resource: 'products.height' }, + // 'products.description' is missing + { action: 'write', resource: 'products.thumbnail' }, + // 'products.image' is missing + { action: 'write', resource: 'products.tab.description' }, + // 'products.tab.stock' is missing + { action: 'write', resource: 'products.tab.images' }, + ], + action, + record, + resource, + }) +}; -export const UserEdit = () => { - return ( - - - - - - - - } - - ... - - - - ); -} +const ProductEdit = () => ( + + + + + + + {/* Input Description is not displayed */} + + + {/* Input Stock is not displayed */} + + + + + {/* Input Image is not displayed */} + + + + + +); ``` -{% endraw %} ## Versioning From 7f60ef32b201c00d706c1c80f4e595f15f8178fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 12 Dec 2024 22:01:59 +0100 Subject: [PATCH 27/39] Move RBAC buttons doc to the Buttons chapter --- docs/AuthRBAC.md | 115 ++++++++++++++++++++++------------------------- docs/Buttons.md | 69 +++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 74 deletions(-) diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index c6578472bd4..33973449126 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -109,8 +109,8 @@ React-admin already does page-level access control with actions like "list", "sh | `create` | Allow to access the Create page | [``](./Create.md), [``](./Buttons.md#createbutton), [``](./List.md#access-control) | | `edit` | Allow to access the Edit page | [``](./Edit.md), [``](./Buttons.md#editbutton), [``](./Datagrid.md#access-control), [``](./Show.md) | | `delete` | Allow to delete data | [``](./Buttons.md#deletebutton), [``](./Buttons.md#bulkdeletebutton), [``](./Datagrid.md#access-control), [``](./SimpleForm.md#access-control), [``](#tabform) | -| `export` | Allow to export data | [``](#exportbutton), [``](./List.md#access-control) | -| `clone` | Allow to clone a record | [``](#clonebutton), [``](./Edit.md) | +| `export` | Allow to export data | [``](./Buttons.md#exportbutton), [``](./List.md#access-control) | +| `clone` | Allow to clone a record | [``](./Buttons.md#clonebutton), [``](./Edit.md) | | `read` | Allow to view a field (or a tab) | [``](./Datagrid.md#access-control), [``](./SimpleShowLayout.md#access-control), [``](./TabbedShowLayout.md#access-control) | | `write` | Allow to edit a field (or a tab) | [``](./SimpleForm.md#access-control), [``](./TabbedForm.md#access-control), [``](./WizardForm.md#enableaccesscontrol), [``](./LongForm.md#enableaccesscontrol), [``](./AccordionForm.md#enableaccesscontrol) | @@ -365,80 +365,71 @@ Ra-rbac provides alternative components to react-admin base components. These al - List - [``](./List.md#access-control) - [``](./Datagrid.md#access-control) + - [``](./Buttons.md#exportbutton) - Detail - [``](./SimpleShowLayout.md#access-control) - [``](./TabbedShowLayout.md#access-control) + - [``](./Buttons.md#clonebutton) - Form - [``](./SimpleForm.md#access-control) - [``](./TabbedForm.md#access-control) -## `` - -Replacement for react-admin's [``](./Buttons.md#exportbutton) that checks users have the `'export'` permission before rendering. Use it if you want to provide your own `actions` to the ``: +Here is an example of `` with RBAC: ```tsx -import { CreateButton, List, TopToolbar } from 'react-admin'; -import { ExportButton } from '@react-admin/ra-rbac'; - -const PostListActions = () => ( - - - - - -); - -export const PostList = () => ( - }> - {/* ... */} - -); -``` - -It accepts the following props in addition to the default [`` props](./Buttons.md#props-8): - -| Prop | Required | Type | Default | Description | -| -------------------- | -------- | ----------------- | ---------- | ---------------------------------------------------------------------- | -| `accessDenied` | Optional | ReactNode | null | The content to display when users don't have the `'export'` permission | -| `action` | Optional | String | `"export"` | The action to call `authProvider.canAccess` with | -| `authorizationError` | Optional | ReactNode | null | The content to display when an error occurs while checking permission | - -**Tip**: Don't forget to give read permissions on all the fields you want to allow in exports -```jsx -{ action: "read", resource: `${resource}.${source}` }. -// or -{ action: "read", resource: `${resource}.*` }. -``` - -## `` - -Replacement for react-admin's [``](./Buttons.md#clonebutton) that checks users have the `'clone'` permission before rendering. Use it if you want to provide your own `actions` to the ``: +import { canAccessWithPermissions, List, Datagrid } from '@react-admin/ra-rbac'; +import { + ImageField, + TextField, + ReferenceField, + NumberField, +} from 'react-admin'; -```tsx -import { Edit, TopToolbar } from 'react-admin'; -import { CloneButton } from '@react-admin/ra-rbac'; - -const PostEditActions = () => ( - - - -); +const authProvider = { + // ... + canAccess: async ({ action, record, resource }) => + canAccessWithPermissions({ + permissions: [ + { action: 'list', resource: 'products' }, + { action: 'read', resource: 'products.thumbnail' }, + { action: 'read', resource: 'products.reference' }, + { action: 'read', resource: 'products.category_id' }, + { action: 'read', resource: 'products.width' }, + { action: 'read', resource: 'products.height' }, + { action: 'read', resource: 'products.price' }, + { action: 'read', resource: 'products.description' }, + // { action: 'read', resource: 'products.stock' }, + // { action: 'read', resource: 'products.sales' }, + // { action: 'delete', resource: 'products' }, + { action: 'show', resource: 'products' }, + ], + action, + record, + resource + }), +}; -export const PostEdit = () => ( - }> - {/* ... */} - +const ProductList = () => ( + + {/* The datagrid has no bulk actions as the user doesn't have the 'delete' permission */} + + + + + + + + + + + {/** These two columns are not visible to the user **/} + + + + ); ``` -It accepts the following props in addition to the default `` props: - -| Prop | Required | Type | Default | Description | -| -------------------- | -------- | ----------------- | ---------- | ---------------------------------------------------------------------- | -| `accessDenied` | Optional | ReactNode | null | The content to display when users don't have the `'clone'` permission | -| `action` | Optional | String | `"clone"` | The action to call `authProvider.canAccess` with | -| `authorizationError` | Optional | ReactNode | null | The content to display when an error occurs while checking permission | - ## Performance `authProvider.canAccess()` can return a promise, which in theory allows to rely on the authentication server for permissions. The downside is that this slows down the app a great deal, as each page may contain dozens of calls to these methods. diff --git a/docs/Buttons.md b/docs/Buttons.md index 1b8095878fb..d0e83496ee6 100644 --- a/docs/Buttons.md +++ b/docs/Buttons.md @@ -600,26 +600,48 @@ It also supports [all the other `