Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add access control #10222

Merged
merged 182 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
182 commits
Select commit Hold shift + click to select a range
c929cec
Add useCanAccess hook
djhi Sep 18, 2024
0c3e8f5
Add access control to resource views
djhi Sep 18, 2024
af8dab3
Cleanup tests output
djhi Sep 18, 2024
3d1f48e
Add access control to ResourceMenuItem
djhi Sep 18, 2024
85dc156
Fix first resource detection
djhi Sep 18, 2024
80cf005
Reorganize stories
djhi Sep 19, 2024
2931d3c
Add tests for first resource detection
djhi Sep 19, 2024
2685254
Fix Admin documentation
djhi Sep 19, 2024
7dbc256
Add <CanAccess>
djhi Sep 19, 2024
69678c6
Apply suggestions from code review
djhi Sep 19, 2024
2b63cfe
Refactor useFirstResourceWithListAccess
djhi Sep 19, 2024
769cdfb
Reorganize Resource tests
djhi Sep 19, 2024
7323384
Improve ResourceMenuItem stories
djhi Sep 19, 2024
4d25054
Add default Unauthorized component to ra-ui-materialui
djhi Sep 19, 2024
484f728
Handle errors in canAccess hooks
djhi Sep 19, 2024
469ae82
Update authProvider documentation
djhi Sep 19, 2024
47e6836
Add missing export
djhi Sep 19, 2024
a5b0d45
Improve typings on useCanAccess & useCanAccessResources
djhi Sep 20, 2024
0b1252e
Add access control to views buttons
djhi Sep 20, 2024
6f13406
Pass the current record when available
djhi Sep 20, 2024
a789e19
Add documentation
djhi Sep 20, 2024
33fc627
Add CreateButton tests
djhi Sep 20, 2024
0c71f04
Improve canAccess hooks types
djhi Sep 20, 2024
e6e435e
Add EditButton tests
djhi Sep 20, 2024
c158c88
Add ShowButton tests
djhi Sep 20, 2024
9d730d6
Add ListButton tests
djhi Sep 20, 2024
c23e4f5
Add access control to DeleteButton
djhi Sep 20, 2024
a8be1f3
Fix other buttons stories
djhi Sep 20, 2024
afd578d
Add DeleteButton tests
djhi Sep 20, 2024
4472472
Apply suggestions from code review
djhi Sep 20, 2024
582b3de
Fix build
djhi Sep 23, 2024
fd77b62
Fix Unauthorized
djhi Sep 23, 2024
9cdc380
Fix useCanAccessResources documentation
djhi Sep 23, 2024
538c461
Fix CanAccess tests
djhi Sep 23, 2024
5861e9e
Fix types and exports
djhi Sep 23, 2024
97bdba1
Remove unnecessary fragment
djhi Sep 23, 2024
95485f0
Fix auth documentation
djhi Sep 23, 2024
76f32b2
Update Unauthorized design
djhi Sep 23, 2024
b40d39c
Apply suggestions from code review
djhi Sep 23, 2024
5c4251e
Update useCanAccess documentation
djhi Sep 23, 2024
82d33a0
Update useCanAccessResources documentation
djhi Sep 23, 2024
98336dc
Refactor canAccess hooks
djhi Sep 23, 2024
1d70577
Remove wrong prop
djhi Sep 23, 2024
5918515
Pessimistic authentication checks
djhi Sep 25, 2024
08258c9
Better design of authenticationError
djhi Sep 25, 2024
7d21e19
Update Admin documentation
djhi Sep 25, 2024
e6992b2
Fix default AuthenticationError and Unauthorized design and API
djhi Sep 25, 2024
680ee68
Update AuthenticationError documentation screenshot
djhi Sep 25, 2024
0a9ad7d
Simplify useListController auth check
djhi Sep 25, 2024
2cdea62
Fix useListController security story
djhi Sep 25, 2024
bff6f96
useListController security story design
djhi Sep 25, 2024
bb42e52
Avoid a rerender with useAuthState when no AuthProvider is set
djhi Sep 25, 2024
09505c4
Add authentication check to useEditController
djhi Sep 25, 2024
805a7c4
Add authentication check to useInfiniteListController
djhi Sep 25, 2024
b4c97c0
Add authentication check to useShowController
djhi Sep 25, 2024
21661bb
Simplify stories
djhi Sep 25, 2024
b2c828b
Fix typo in tests
djhi Sep 25, 2024
8455d7d
Improve tests
djhi Sep 25, 2024
b2f759b
Don't set logoutOnFailure to its default
djhi Sep 25, 2024
93099ef
Fix disableAuthentication
djhi Sep 25, 2024
2c8ce0b
Restore Resource access control
djhi Sep 25, 2024
1b84ccf
Apply suggestions from code review
fzaninotto Sep 26, 2024
87e65ba
Fix linter warnings
fzaninotto Sep 26, 2024
12d762e
Merge pull request #10238 from marmelab/pessimistic-authentication-ch…
fzaninotto Sep 26, 2024
98cf688
Introduce useRequireAccess
djhi Sep 27, 2024
ea857f0
Move access control from Resource to useShowController
djhi Sep 27, 2024
dace0e9
Move access control from Resource to useEditController
djhi Sep 27, 2024
eb61992
Move access control from Resource to useListController
djhi Sep 27, 2024
c02f540
Move access control from Resource to useCreateController
djhi Sep 27, 2024
079f4f5
Remove unnecessary contexts
djhi Sep 30, 2024
a78a2aa
Refactor CanAccess
djhi Sep 30, 2024
28d2b02
Add access control to useInfiniteController
djhi Sep 30, 2024
b1f0e00
Fix CanAccess js docs
djhi Sep 30, 2024
782b485
Avoid redirecting to /authentication-error in useCanAccess
djhi Sep 30, 2024
a552b5f
Remove old canAccess documentation
djhi Sep 30, 2024
97e85fa
Fix CanAccess documentation
djhi Sep 30, 2024
dafdc56
Update documentation
djhi Sep 30, 2024
f595820
Handle errors in <CanAccess>
djhi Sep 30, 2024
12f0777
Better useRequireAccess example
djhi Sep 30, 2024
009b852
Improve useRequireAccess tests
djhi Sep 30, 2024
de52132
Improve useRequireAccess jsDoc
djhi Sep 30, 2024
d3804d3
Revert unnecessary changes
djhi Sep 30, 2024
b4ca86b
Set the default unauthorized prop in ra-ui-materialui
djhi Sep 30, 2024
c429d46
Apply suggestions from code review
djhi Oct 1, 2024
53b24e8
Remove premium icons for non enterprise features
djhi Oct 1, 2024
766c5b5
Rename unauthorized to accessDenied
djhi Oct 1, 2024
a8adf6e
Make access control calls dependant on auth check calls
djhi Oct 1, 2024
36ec547
Rename image
djhi Oct 1, 2024
34b1c1c
Fix useRequireAccess documentation
djhi Oct 1, 2024
00e33cf
Simplify stories
djhi Oct 1, 2024
c0b26e0
Merge pull request #10247 from marmelab/access-control-controllers
fzaninotto Oct 1, 2024
4b04c41
Merge branch 'access-control-resources' into access-control-buttons
djhi Oct 1, 2024
a6df775
Improve stories names
djhi Oct 1, 2024
738b273
Merge branch 'access-control-buttons' into access-control-delete-buttons
djhi Oct 1, 2024
2b718fb
Add access control to `<Datagrid rowClick>`
djhi Sep 20, 2024
e238966
Use Record<string, any> instead of RaRecord
djhi Oct 1, 2024
b37cf6d
Merge branch 'access-control-buttons' into access-control-rowclick
djhi Oct 1, 2024
2419a2e
Fix useCanAccessCallback
djhi Oct 1, 2024
c84dff7
Merge pull request #10225 from marmelab/access-control-buttons
fzaninotto Oct 1, 2024
b569075
Merge pull request #10226 from marmelab/access-control-delete-buttons
fzaninotto Oct 1, 2024
9267276
[Doc] Rewrite access control documentation
fzaninotto Sep 30, 2024
a866f3a
Review
fzaninotto Oct 1, 2024
6556df4
Document access control in views
fzaninotto Oct 1, 2024
7ebb52f
Document controllers
fzaninotto Oct 1, 2024
c083814
Fix build
fzaninotto Oct 1, 2024
b2e2eeb
Add mention of built-in access control in action buttons
fzaninotto Oct 1, 2024
f2bc053
Merge branch 'next' into access-control-resources
fzaninotto Oct 1, 2024
d0172f9
Document buttons
fzaninotto Oct 1, 2024
38f2d17
Fix buttons doc
fzaninotto Oct 1, 2024
c88e259
Make Authenticated component secure by default
fzaninotto Oct 1, 2024
6b82a78
Fix useCanAccess result type
djhi Oct 2, 2024
8347890
Merge branch 'access-control-resources' into access-control-rowclick
djhi Oct 2, 2024
0053e0f
Update navigation and reference
djhi Oct 2, 2024
2202c9c
Apply suggestions from code review
fzaninotto Oct 2, 2024
259d6f0
Remove unnecessary canAccess calls in useGetPathForRecord
djhi Oct 2, 2024
6fb0f3a
Add tests and stories
djhi Oct 2, 2024
71f74d4
Add tests and stories for ReferenceField
djhi Oct 2, 2024
b5edf22
Merge pull request #10251 from marmelab/authenticated-pessimistic
djhi Oct 2, 2024
31934d7
Only check access rights for inferred link types
djhi Oct 2, 2024
bababb4
Revert unnecessary changes on useGetPathForRecord
djhi Oct 2, 2024
b5dabbf
Introduce `<NavigateToFirstResource>`
djhi Oct 2, 2024
c0549b4
Better formatting in documentation
djhi Oct 2, 2024
f789795
Add mention of authentication
fzaninotto Oct 3, 2024
1a43739
Merge pull request #10250 from marmelab/access-control-doc
fzaninotto Oct 3, 2024
e7c8541
Fix ShowBase should accept a ReactNode
djhi Oct 3, 2024
dfcbfe9
Reuse HintedString in useCreatePath
djhi Oct 3, 2024
10a1c76
Refactor useGetPathForRecord to leverage react-query
djhi Oct 3, 2024
439ab27
Add tests and stories for useGetPathForRecordCallback and improve can…
djhi Oct 3, 2024
3a05c9c
Improve ReferenceField tests and stories
djhi Oct 3, 2024
04fffab
Apply review suggestions
djhi Oct 3, 2024
fe49131
Merge branch 'access-control-first-resource' of github.com:marmelab/r…
djhi Oct 3, 2024
61b14c8
Export NavigateToFirstResource
djhi Oct 3, 2024
0481fb0
Reintroduce CreatePathType
djhi Oct 3, 2024
b7cfad2
Improve tests and stories
djhi Oct 3, 2024
2b76791
Add Datagrid story
djhi Oct 3, 2024
ed77a48
Add documentation
djhi Oct 3, 2024
2246a5d
Throw an error when no resources are found
djhi Oct 3, 2024
cffa9b4
Mibor tweaks
fzaninotto Oct 3, 2024
bdf0c99
Revert change on useGetPathForRecord and explain in comments
djhi Oct 3, 2024
8d02eda
Merge pull request #10227 from marmelab/access-control-rowclick
fzaninotto Oct 3, 2024
ad12e41
Remove the error
djhi Oct 3, 2024
d1be9bb
Merge pull request #10255 from marmelab/access-control-first-resource
fzaninotto Oct 3, 2024
44dc614
[Doc] Overhaul Auth introduction and auth provider writing chapters
fzaninotto Oct 3, 2024
4bdcf84
Reorganize auth doc
fzaninotto Oct 3, 2024
54b3522
Fix Admin authenticationError documentation
fzaninotto Oct 3, 2024
68c4066
Make authProvider.getPermissions optional
djhi Oct 4, 2024
c53bc86
Cleanup
djhi Oct 4, 2024
ec56f9f
Add story and screencast
fzaninotto Oct 4, 2024
86f1474
Merge pull request #10257 from marmelab/optional-authprovider-getperm…
fzaninotto Oct 4, 2024
40adf7d
Introduce useIsAuthPending
djhi Oct 4, 2024
18620e6
Ensure List check auth states pessimistically
djhi Oct 4, 2024
483d84c
Ensure InfiniteList check auth states pessimistically
djhi Oct 4, 2024
99ab061
Ensure Create check auth states pessimistically
djhi Oct 4, 2024
bb78db5
Ensure Edit check auth states pessimistically
djhi Oct 4, 2024
75c9ba5
Ensure Show check auth states pessimistically
djhi Oct 4, 2024
515c326
[Doc] Update requireAuth explanation
fzaninotto Oct 4, 2024
e6b2df3
[doc] Clarify that `getPermissions` is now optional
fzaninotto Oct 4, 2024
ecd4461
Proofreading
fzaninotto Oct 4, 2024
6076cf4
Ensure devs can provide their own loading
djhi Oct 4, 2024
41e9853
Ensure a ResourceContext is added only when needed
djhi Oct 4, 2024
abd28a2
Fix Resource documentation
fzaninotto Oct 4, 2024
d09d409
Fix missing export
fzaninotto Oct 4, 2024
ee520bb
Remove links to deleted chapters
fzaninotto Oct 4, 2024
581ac7a
Fix typo
fzaninotto Oct 4, 2024
842cd2f
Proofreading
fzaninotto Oct 4, 2024
5a2df6c
Add stories and tests for ListBase
djhi Oct 4, 2024
4e98cb6
Add stories and tests for InfiniteListBase
djhi Oct 4, 2024
fa587fd
Fix useAuthState is optimistic
fzaninotto Oct 4, 2024
68b92d5
Fix mention of optimistic default auth
fzaninotto Oct 4, 2024
8370f97
Add message to future me
fzaninotto Oct 4, 2024
5b4a06c
Update docs/CustomRoutes.md
fzaninotto Oct 4, 2024
6b984f1
Fix type
fzaninotto Oct 4, 2024
ff5b916
Fix useCanAccessResources should not log out on error
fzaninotto Oct 4, 2024
7008b31
Add stories and tests for Create
djhi Oct 4, 2024
34dfce2
Fix useCreateController isPending is always true if disableAuthentica…
fzaninotto Oct 4, 2024
0fee4fd
Add stories and tests for Edit
djhi Oct 4, 2024
3a6c3a8
Add stories and tests for Show
djhi Oct 4, 2024
8a8cead
Fix new auth error pages can't be overridden
fzaninotto Oct 4, 2024
a60d5e6
Ensure dashboard waits for all auth calls resolutions
djhi Oct 4, 2024
30924a4
Merge pull request #10258 from marmelab/access-control-views-loading
fzaninotto Oct 4, 2024
5e26e1f
Fix message key
fzaninotto Oct 4, 2024
452253d
Allow requireAuth to use react-query cache
djhi Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor useFirstResourceWithListAccess
  • Loading branch information
djhi committed Sep 19, 2024
commit 2b63cfe315445473060318c4206c3407a6a07c6f
47 changes: 47 additions & 0 deletions docs/useCanAccessResources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
layout: default
title: "useCanAccessResources"
---

# `useCanAccessResources`

This hook calls the `authProvider.canAccess()` method on mount for an array of resources, an action, and optionally a record. It's ideal for checking the access to several resources in parallel (e.g. all the columns of a `<Datagrid>`).

`useCanAccessResources` returns an object containing a `canAccess` object, which is a map of the provided resources where the value is set to `true` when users have access to it.

## Usage

`useCanAccessResources` takes an object `{ action, resource, record, sources }` as argument. The `source` parameter is an array of the record properties names for which to check the access permission. In addition to react-query result properties, it returns a `canAccess` object that has a property for each provided source determining whether the user has access to it.
djhi marked this conversation as resolved.
Show resolved Hide resolved

```jsx
import { useCanAccessResources, SimpleList } from 'react-admin';

const UserList = ({ record }) => {
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
const { isPending, canAccess } = useCanAccessResources({
action: 'delete',
resource: ['users.id', 'users.name', 'users.email'],
djhi marked this conversation as resolved.
Show resolved Hide resolved
record,
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
});
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
if (isPending) {
return null;
}
return (
<SimpleList
primaryText={record => canAccess.name ? record.name : ''}
secondaryText={record => canAccess.email ? record.email : ''}
tertiaryText={record => canAccess.id ? record.id : ''}
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
/>
);
};
```

## Parameters

`useCanAccessResources` expects a single parameter object with the following properties:

| Name | Required | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `resources` | Required | `string[]` | - | An array of the resources for which to check access, e.g `['users.id', 'users.title']` |
| `action` | Required | `string` | - | The action to check, e.g. 'read', 'list', 'export', 'delete', etc. |
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
| `record` | Optional | `object` | - | The record to check. If passed, the child only renders if the user has permissions for that record, e.g. `{ id: 123, firstName: "John", lastName: "Doe" }` |

109 changes: 109 additions & 0 deletions packages/ra-core/src/auth/useCanAccessResources.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as React from 'react';
import expect from 'expect';
import { render, screen } from '@testing-library/react';
import { Basic } from './useCanAccessResources.stories';

describe('useCanAccessResources', () => {
it('should call authProvider.canAccess for every resources', async () => {
djhi marked this conversation as resolved.
Show resolved Hide resolved
const canAccess = jest.fn().mockImplementation(async () => true);
const authProvider = {
login: () => Promise.reject('bad method'),
logout: () => Promise.reject('bad method'),
checkAuth: () => Promise.reject('bad method'),
checkError: () => Promise.reject('bad method'),
getPermissions: () => Promise.reject('bad method'),
canAccess,
};
render(<Basic authProvider={authProvider} />);

screen.getByText('LOADING');

expect(canAccess).toBeCalledTimes(3);
expect(canAccess).toBeCalledWith({
action: 'read',
resource: 'posts.id',
signal: undefined,
});
expect(canAccess).toBeCalledWith({
action: 'read',
resource: 'posts.title',
signal: undefined,
});
expect(canAccess).toBeCalledWith({
action: 'read',
resource: 'posts.author',
signal: undefined,
});

await screen.findByText(
JSON.stringify({
'posts.id': true,
'posts.title': true,
'posts.author': true,
})
);

expect(screen.queryByText('LOADING')).toBeNull();
});

it('should grant access to each resource based on canAccess result', async () => {
const canAccess = jest
.fn()
.mockImplementation(
async ({ resource }) => resource !== 'posts.id'
);
const authProvider = {
login: () => Promise.reject('bad method'),
logout: () => Promise.reject('bad method'),
checkAuth: () => Promise.reject('bad method'),
checkError: () => Promise.reject('bad method'),
getPermissions: () => Promise.reject('bad method'),
canAccess,
};
render(<Basic authProvider={authProvider} />);

screen.getByText('LOADING');

await screen.findByText(
JSON.stringify({
'posts.id': false,
'posts.title': true,
'posts.author': true,
})
);
expect(screen.queryByText('LOADING')).toBeNull();
});

it('should grant access to all resources if no authProvider', async () => {
render(<Basic authProvider={null} />);
expect(screen.queryByText('LOADING')).toBeNull();

screen.getByText(
JSON.stringify({
'posts.id': true,
'posts.title': true,
'posts.author': true,
})
);
});

it('should grant access to all resources if no authProvider.canAccess', async () => {
const authProvider = {
login: () => Promise.reject('bad method'),
logout: () => Promise.reject('bad method'),
checkAuth: () => Promise.reject('bad method'),
checkError: () => Promise.reject('bad method'),
getPermissions: () => Promise.reject('bad method'),
};
render(<Basic authProvider={authProvider} />);
expect(screen.queryByText('LOADING')).toBeNull();

screen.getByText(
JSON.stringify({
'posts.id': true,
'posts.title': true,
'posts.author': true,
})
);
});
});
86 changes: 86 additions & 0 deletions packages/ra-core/src/auth/useCanAccessResources.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import { AuthProvider } from '../types';
import { CoreAdminContext } from '../core';
import { QueryClient } from '@tanstack/react-query';
import {
useCanAccessResources,
UseCanAccessResourcesResult,
} from './useCanAccessResources';

export default {
title: 'ra-core/auth/useCanAccessResources',
};

const UseCanAccessResources = ({
children,
action,
resources,
}: {
children: any;
action: string;
resources: string[];
}) => {
const { canAccess, isPending } = useCanAccessResources({
action,
resources,
});

return children({ canAccess, isPending });
};

const StateInspector = ({ state }: { state: UseCanAccessResourcesResult }) => {
return (
<div>
<span>{state.isPending && 'LOADING'}</span>
{state.canAccess !== undefined && (
<span>{JSON.stringify(state.canAccess)}</span>
)}
<span>{state.error && 'ERROR'}</span>
</div>
);
};

const defaultAuthProvider: AuthProvider = {
login: () => Promise.reject('bad method'),
logout: () => Promise.reject('bad method'),
checkAuth: () => Promise.reject('bad method'),
checkError: () => Promise.reject('bad method'),
getPermissions: () => Promise.reject('bad method'),
canAccess: ({ action }) =>
new Promise(resolve => setTimeout(resolve, 500, action === 'read')),
};

export const Basic = ({
authProvider = defaultAuthProvider,
queryClient,
}: {
authProvider?: AuthProvider | null;
queryClient?: QueryClient;
}) => (
<CoreAdminContext
authProvider={authProvider != null ? authProvider : undefined}
queryClient={queryClient}
>
<UseCanAccessResources
action="read"
resources={['posts.id', 'posts.title', 'posts.author']}
>
{result => <StateInspector state={result} />}
</UseCanAccessResources>
</CoreAdminContext>
);

export const NoAuthProvider = ({
queryClient,
}: {
queryClient?: QueryClient;
}) => (
<CoreAdminContext queryClient={queryClient}>
<UseCanAccessResources
action="read"
resources={['posts.id', 'posts.title', 'posts.author']}
>
{result => <StateInspector state={result} />}
</UseCanAccessResources>
</CoreAdminContext>
);
Loading
Loading