Skip to content

Commit

Permalink
Move TabbedForm RBAC doc to TabbedForm chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
fzaninotto committed Dec 12, 2024
1 parent 30ff4d3 commit 3011173
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 225 deletions.
142 changes: 13 additions & 129 deletions docs/AuthRBAC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>`](#list), [`<ListButton>`](./Buttons.md#listbutton), [`<Menu.ResourceItem>`](#menu) |
| `show` | Allow to access the Show page | [`<Show>`](./Show.md), [`<ShowButton>`](./Buttons.md#showbutton), [`<Datagrid>`](#datagrid), [`<Edit>`](./Edit.md) |
| `create` | Allow to access the Create page | [`<Create>`](./Create.md), [`<CreateButton>`](./Buttons.md#createbutton), [`<List>`](#list) |
| `edit` | Allow to access the Edit page | [`<Edit>`](./Edit.md), [`<EditButton>`](./Buttons.md#editbutton), [`<Datagrid>`](#datagrid), [`<Show>`](./Show.md) |
| `delete` | Allow to delete data | [`<DeleteButton>`](./Buttons.md#deletebutton), [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton), [`<Datagrid>`](#datagrid), [`<SimpleForm>`](#simpleform), [`<TabbedForm>`](#tabform) |
| `export` | Allow to export data | [`<ExportButton>`](#exportbutton), [`<List>`](#list) |
| `list` | Allow to access the List page | [`<List>`](./List.md#access-control), [`<ListButton>`](./Buttons.md#listbutton), [`<Menu.ResourceItem>`](./Menu.md#access-control) |
| `show` | Allow to access the Show page | [`<Show>`](./Show.md), [`<ShowButton>`](./Buttons.md#showbutton), [`<Datagrid>`](./Datagrid.md#access-control), [`<Edit>`](./Edit.md) |
| `create` | Allow to access the Create page | [`<Create>`](./Create.md), [`<CreateButton>`](./Buttons.md#createbutton), [`<List>`](./List.md#access-control) |
| `edit` | Allow to access the Edit page | [`<Edit>`](./Edit.md), [`<EditButton>`](./Buttons.md#editbutton), [`<Datagrid>`](./Datagrid.md#access-control), [`<Show>`](./Show.md) |
| `delete` | Allow to delete data | [`<DeleteButton>`](./Buttons.md#deletebutton), [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton), [`<Datagrid>`](./Datagrid.md#access-control), [`<SimpleForm>`](./SimpleForm.md#access-control), [`<TabbedForm>`](#tabform) |
| `export` | Allow to export data | [`<ExportButton>`](#exportbutton), [`<List>`](./List.md#access-control) |
| `clone` | Allow to clone a record | [`<CloneButton>`](#clonebutton), [`<Edit>`](./Edit.md) |
| `read` | Allow to view a field (or a tab) | [`<Datagrid>`](#datagrid), [`<SimpleShowLayout>`](#simpleshowlayout), [`<TabbedShowLayout>`](#tabbedshowlayout) |
| `write` | Allow to edit a field (or a tab) | [`<SimpleForm>`](#simpleform), [`<TabbedForm>`](#tabbedform), [`<WizardForm>`](./WizardForm.md#enableaccesscontrol), [`<LongForm>`](./LongForm.md#enableaccesscontrol), [`<AccordionForm>`](./AccordionForm.md#enableaccesscontrol) |
| `read` | Allow to view a field (or a tab) | [`<Datagrid>`](./Datagrid.md#access-control), [`<SimpleShowLayout>`](./SimpleShowLayout.md#access-control), [`<TabbedShowLayout>`](./TabbedShowLayout.md#access-control) |
| `write` | Allow to edit a field (or a tab) | [`<SimpleForm>`](./SimpleForm.md#access-control), [`<TabbedForm>`](./TabbedForm.md#access-control), [`<WizardForm>`](./WizardForm.md#enableaccesscontrol), [`<LongForm>`](./LongForm.md#enableaccesscontrol), [`<AccordionForm>`](./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.

Expand Down Expand Up @@ -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>`](#simpleshowlayout)
- [`<TabbedShowLayout>`](#tabbedshowlayout)
- [`<SimpleForm>`](#simpleform)
- [`<TabbedForm>`](#tabbedform)
- [`<SimpleShowLayout>`](./SimpleShowLayout.md#access-control)
- [`<TabbedShowLayout>`](./TabbedShowLayout.md#access-control)
- [`<SimpleForm>`](./SimpleForm.md#access-control)
- [`<TabbedForm>`](./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.

Expand Down Expand Up @@ -370,8 +370,7 @@ Ra-rbac provides alternative components to react-admin base components. These al
- [`<TabbedShowLayout>`](./TabbedShowLayout.md#access-control)
- Form
- [`<SimpleForm>`](./SimpleForm.md#access-control)
- [`<TabbedForm>`](#tabbedform)
- [`<FormTab>`](#formtab)
- [`<TabbedForm>`](./TabbedForm.md#access-control)

## `<ExportButton>`

Expand Down Expand Up @@ -411,121 +410,6 @@ It accepts the following props in addition to the default [`<ExportButton>` prop
{ action: "read", resource: `${resource}.*` }.
```


## `<TabbedForm>`

Replacement for react-admin's `<TabbedForm>` 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 [`<TabbedForm.Tab>`](#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:** [`<TabbedForm.Tab>`](#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 = () => (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
<TextInput source="description" />
</TabbedForm.Tab>
{/* the "Stock" tab is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
<TextInput source="image" />
<TextInput source="thumbnail" />
</TabbedForm.Tab>
{/* the "Delete" button is not displayed */}
</TabbedForm>
</Edit>
);
```

## `<TabbedForm.Tab>`

Replacement for react-admin's `<TabbedForm.Tab>` 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.

`<TabbedForm.Tab>` 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 = () => (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
{/* Input Description is not displayed */}
<TextInput source="description" />
</TabbedForm.Tab>
{/* Input Stock is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
{/* Input Image is not displayed */}
<TextInput source="image" />
<TextInput source="thumbnail" />
</TabbedForm.Tab>
</TabbedForm>
</Edit>
);
```

## `<CloneButton>`

Replacement for react-admin's [`<CloneButton>`](./Buttons.md#clonebutton) that checks users have the `'clone'` permission before rendering. Use it if you want to provide your own `actions` to the `<Edit>`:
Expand Down
194 changes: 98 additions & 96 deletions docs/TabbedForm.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,114 +794,116 @@ If you're using it in an `<Edit>` page, you must also use a `pessimistic` or `op

Check [the `<AutoSave>` 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 `<TabbedForm>` 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 <Loading />;
if (error) {
return (
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}>
An error occurred while checking your permissions
</Alert>
);
}

return (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Summary">
...
</TabbedForm.Tab>
{canAccess &&
<TabbedForm.Tab label="Security">
...
</TabbedForm.Tab>
}
</TabbedForm>
</Edit>
);
}
```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 [`<TabbedForm.Tab>`](#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 <Loading />;
if (error) {
return (
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}>
An error occurred while checking your permissions
</Alert>
);
}
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 (
<Edit>
<TabbedForm>
{canAccess['users.tabs.summary'] &&
<TabbedForm.Tab label="Summary">
...
</TabbedForm.Tab>}
{canAccess['users.tabs.security'] &&
<TabbedForm.Tab label="Security">
...
</TabbedForm.Tab>
}
</TabbedForm>
</Edit>
);
}
const ProductEdit = () => (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
<TextInput source="description" />
</TabbedForm.Tab>
{/* the "Stock" tab is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
<TextInput source="image" />
<TextInput source="thumbnail" />
</TabbedForm.Tab>
{/* the "Delete" button is not displayed */}
</TabbedForm>
</Edit>
);
```
{% endraw %}

## Displaying Inputs Based On Permissions

You can leverage [the `<CanAccess>` component](./CanAccess.md) to display inputs if the user has the required access rights.
[`<TabbedForm.Tab>`](#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 (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Summary">
<TextInput source="name" validate={[required()]} />
<CanAccess resource="user.role" action="write">
<TextInput source="role" validate={[required()]} />
</CanAccess>
</TabbedForm.Tab>}
<TabbedForm.Tab label="Security">
...
</TabbedForm.Tab>
</TabbedForm>
</Edit>
);
}
const ProductEdit = () => (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
{/* Input Description is not displayed */}
<TextInput source="description" />
</TabbedForm.Tab>
{/* Input Stock is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
{/* Input Image is not displayed */}
<TextInput source="image" />
<TextInput source="thumbnail" />
</TabbedForm.Tab>
</TabbedForm>
</Edit>
);
```
{% endraw %}

## Versioning

Expand Down

0 comments on commit 3011173

Please sign in to comment.