Skip to content

Commit

Permalink
RBAC implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kazhamiakin committed Jul 17, 2024
1 parent c454b1f commit 9ad0a16
Show file tree
Hide file tree
Showing 45 changed files with 629 additions and 163 deletions.
35 changes: 18 additions & 17 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Resource,
defaultTheme,
localStorageStore,
usePermissions,
} from 'react-admin';
import {
SchemaList,
Expand Down Expand Up @@ -51,7 +52,7 @@ import { httpClientProvider } from './providers/httpClientProvider';
import { K8SDeploymentList, K8SDeploymentShow } from './resources/k8s/k8s_deployment';
import { K8SPvcCreate, K8SPvcList, K8SPvcShow } from './resources/k8s/k8s_pvc';
import { K8SServiceList, K8SServiceShow } from './resources/k8s/k8s_service';
import { K8SSecretCreate, K8SSecretList, K8SSecretShow } from './resources/k8s/k8s_secret';
import { K8SSecretList, K8SSecretShow } from './resources/k8s/k8s_secret';
import { K8SJobList, K8SJobShow } from './resources/k8s/k8s_job';

console.log('Config', Config);
Expand Down Expand Up @@ -110,7 +111,8 @@ function App() {
function DynamicAdminUI() {
const [views, setViews] = useState<View[]>([]);
const { crdIds } = useUpdateCrdIds();

const { permissions } = usePermissions();
const canAccess = (res: string, op: string) => permissions && permissions.canAccess(res, op)

const viewsContext = useContext(ViewsContext);

Expand Down Expand Up @@ -160,42 +162,41 @@ function DynamicAdminUI() {
<Resource
name="crs"
list={SchemaList}
edit={SchemaEdit}
create={SchemaCreate}
show={SchemaShow}
edit={canAccess('crs', 'write') ? SchemaEdit : <></>}
create={canAccess('crs', 'write') ? SchemaCreate : <></>}
show={canAccess('crs', 'read') ? SchemaShow : <></>}
icon={SettingsIcon}
/>
<Resource name="crd" show={CrdShow} recordRepresentation="id" />
<Resource
name="k8s_service"
list={K8SServiceList}
show={K8SServiceShow}
list={canAccess('k8s_service', 'list') ? K8SServiceList : <></>}
show={canAccess('k8s_service', 'read') ? K8SServiceShow : <></>}
icon={LinkIcon}
/>
<Resource
name="k8s_deployment"
list={K8SDeploymentList}
show={K8SDeploymentShow}
list={canAccess('k8s_deployment', 'list') ? K8SDeploymentList : <></>}
show={canAccess('k8s_deployment', 'read') ? K8SDeploymentShow : <></>}
icon={AppIcon}
/>
<Resource
name="k8s_job"
list={K8SJobList}
show={K8SJobShow}
list={canAccess('k8s_job', 'list') ? K8SJobList : <></>}
show={canAccess('k8s_job', 'read') ? K8SJobShow : <></>}
icon={ModelTraininigIcon}
/>
<Resource
name="k8s_pvc"
create={K8SPvcCreate}
list={K8SPvcList}
show={K8SPvcShow}
create={canAccess('k8s_pvc', 'write') ? K8SPvcCreate : <></>}
list={canAccess('k8s_pvc', 'list') ? K8SPvcList : <></>}
show={canAccess('k8s_pvc', 'read') ? K8SPvcShow : <></>}
icon={AlbumIcon}
/>
<Resource
name="k8s_secret"
create={K8SSecretCreate}
list={K8SSecretList}
show={K8SSecretShow}
list={canAccess('k8s_secret', 'list') ? K8SSecretList : <></>}
show={canAccess('k8s_secret', 'read') ? K8SSecretShow : <></>}
icon={KeyIcon}
/>
</AdminUI>
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {
Menu,
MenuProps,
ResourceDefinition,
usePermissions,
useResourceDefinitions,
} from 'react-admin';
import { Divider } from '@mui/material';

const MyMenu = (props: MenuProps) => {
const resources = useResourceDefinitions();
const { permissions } = usePermissions();
return (
<Menu {...props}>
<Menu.DashboardItem />
Expand All @@ -19,19 +21,20 @@ const MyMenu = (props: MenuProps) => {
resource.name !== 'crd' &&
resource.name !== 'crs' &&
!resource.name.startsWith('k8s') &&
resource.hasList && (
resource.hasList &&
permissions && permissions.canAccess(resource.name, 'list') && (
<Menu.ResourceItem
key={resource.name}
name={resource.name}
/>
)
)}
<Divider />
<Menu.ResourceItem name={'k8s_service'} />
<Menu.ResourceItem name={'k8s_deployment'} />
<Menu.ResourceItem name={'k8s_job'} />
<Menu.ResourceItem name={'k8s_pvc'} />
<Menu.ResourceItem name={'k8s_secret'} />
{permissions && permissions.canAccess('k8s_service', 'list') && <Menu.ResourceItem name={'k8s_service'} />}
{permissions && permissions.canAccess('k8s_deployment', 'list') && <Menu.ResourceItem name={'k8s_deployment'} />}
{permissions && permissions.canAccess('k8s_job', 'list') && <Menu.ResourceItem name={'k8s_job'} /> }
{permissions && permissions.canAccess('k8s_pvc', 'list') && <Menu.ResourceItem name={'k8s_pvc'} /> }
{permissions && permissions.canAccess('k8s_secret', 'list') && <Menu.ResourceItem name={'k8s_secret'} /> }
<div key="settings">
<Divider />
<Menu.ResourceItem name={'crs'} />
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EmptyClasses,
ResourceDefinition,
useGetList,
usePermissions,
useResourceDefinitions,
useTranslate,
} from 'react-admin';
Expand Down Expand Up @@ -58,6 +59,8 @@ const AppDashboard = () => {
const translate = useTranslate();
const resources = useResourceDefinitions();
const navigate = useNavigate();
const { permissions } = usePermissions();
const hasListPermission = (resource: string) => permissions && permissions.canAccess(resource, 'list')

const cards: any[] = [];

Expand All @@ -66,7 +69,8 @@ const AppDashboard = () => {
resource.name !== 'crd' &&
resource.name !== 'crs' &&
!resource.name.startsWith('k8s_') &&
resource.hasList
resource.hasList &&
hasListPermission(resource.name)
) {
cards.push(
<Card key={resource.name}>
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/providers/authProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export const NoneAuthProvider = (): AuthProvider => {
logout: noop,
checkAuth: noop,
checkError: noop,
getPermissions: noop,
getPermissions: () => {
return Promise.resolve({canAccess: (resource: string, op: string) => true });
},
getAuthorization: noop,
};
};
Expand All @@ -39,13 +41,14 @@ export const buildAuthProvider = (config: any): AuthProvider => {
? config.application.apiUrl
: '';
return BasicAuthProvider(baseUrl + '/api/crd');
} else if (
type === AUTH_TYPE_OAUTH2 &&
'oauth2' in config.authentication
) {
} else if (type === AUTH_TYPE_OAUTH2 && 'oauth2' in config.authentication) {
const baseUrl =
'application' in config && 'apiUrl' in config.application
? config.application.apiUrl
: '';
const oauth2: [string, string, string, string] =
config.authentication.oauth2;
return OAuth2AuthProvider(...oauth2);
return OAuth2AuthProvider(baseUrl, ...oauth2);
}
}
return NoneAuthProvider();
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/providers/authProviderBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const BasicAuthProvider = (loginUrl?: string): AuthProvider => {
},
// get the user permissions (optional)
getPermissions: () => {
return Promise.resolve();
return Promise.resolve({canAccess: (resource: string, op: string) => true });
},
};
};
Expand Down
52 changes: 42 additions & 10 deletions frontend/src/providers/authProviderOAuth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
import { AuthProvider } from './authProvider';

export const OAuth2AuthProvider = (
baseUrl: string,
authority: string,
clientId: string,
redirectUri: string,
Expand All @@ -16,15 +17,17 @@ export const OAuth2AuthProvider = (
loadUserInfo: true,
});

return {
getAuthorization: async () => {
const user = await userManager.getUser();
if (user) {
return Promise.resolve('Bearer ' + user.access_token);
}
const oauthGetAuthorization = async () => {
const user = await userManager.getUser();
if (user) {
return Promise.resolve('Bearer ' + user.access_token);
}

return Promise.reject();
},
return Promise.reject();
}

return {
getAuthorization: oauthGetAuthorization,
login: () => {
return userManager.signinRedirect();
},
Expand All @@ -46,6 +49,7 @@ export const OAuth2AuthProvider = (
// remove local credentials and notify the auth server that the user logged out
logout: () => {
userManager.removeUser();
sessionStorage.removeItem('user-permissions');
return Promise.resolve();
},
// get the user's profile
Expand All @@ -58,8 +62,36 @@ export const OAuth2AuthProvider = (
});
},
// get the user permissions (optional)
getPermissions: () => {
return Promise.resolve();
getPermissions: async () => {
if (sessionStorage.getItem('user-permissions')) {
const permissions = JSON.parse(sessionStorage.getItem('user-permissions')!);
permissions.canAccess = (resource: string, op: string) => (permissions[resource] && permissions[resource].indexOf(op) >= 0);
return Promise.resolve(permissions);
}

const request = new Request(baseUrl +'/api/user', {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
'Authorization': await oauthGetAuthorization(),
}),
});

return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(permissions => {
sessionStorage.setItem('user-permissions', JSON.stringify(permissions));
permissions.canAccess = (resource: string, op: string) => (permissions[resource] && permissions[resource].indexOf(op) >= 0);
return permissions;
})
.catch(() => {
throw new Error('Network error');
});
},
handleCallback: async () => {
// get an access token based on the query paramaters
Expand Down
21 changes: 16 additions & 5 deletions frontend/src/resources/cr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
useTranslate,
useShowController,
useEditController,
usePermissions,
} from 'react-admin';
import { Box, Typography } from '@mui/material';
import { ViewToolbar } from '../components/ViewToolbar';
Expand Down Expand Up @@ -110,11 +111,16 @@ export const CrEdit = () => {
};

export const CrList = () => {
const crdId = useResourceContext();

const { permissions } = usePermissions();
const hasPermission = (op: string) => permissions && permissions.canAccess(crdId, op)

return (
<>
<Breadcrumb />
<PageTitle pageType="list" />
<List actions={<ListTopToolbar />}>
<List actions={<ListTopToolbar hasCreate={hasPermission('write')} />}>
<Datagrid>
<TextField source="id" label={'resources.cr.fields.id'} />
<TextField
Expand All @@ -126,9 +132,9 @@ export const CrList = () => {
label={'resources.cr.fields.kind'}
/>
<Box textAlign={'right'}>
<EditButton />
<ShowButton />
<DeleteWithConfirmButton />
{hasPermission('write') && <EditButton />}
{hasPermission('read') && <ShowButton />}
{hasPermission('write') && <DeleteWithConfirmButton />}
</Box>
</Datagrid>
</List>
Expand All @@ -139,13 +145,18 @@ export const CrList = () => {
export const CrShow = () => {
const { jsonSchema } = useGetCrdJsonSchema();
const { record } = useShowController();
const crdId = useResourceContext();

const { permissions } = usePermissions();
const hasPermission = (op: string) => permissions && permissions.canAccess(crdId, op)

if (!record) return null;

return (
<>
<Breadcrumb />
<PageTitle pageType="show" crId={record.id} />
<Show actions={<ShowTopToolbar hasYaml />}>
<Show actions={<ShowTopToolbar hasYaml hasDelete={hasPermission('write')} hasEdit={hasPermission('write')} />}>
<SimpleShowLayout>
<TextField source="id" label={'resources.cr.fields.id'} />
<TextField
Expand Down
16 changes: 11 additions & 5 deletions frontend/src/resources/crs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ReferenceField,
useShowController,
useEditController,
usePermissions,
} from 'react-admin';
import { useUpdateCrdIds } from '../hooks/useUpdateCrdIds';
import { Box, Typography } from '@mui/material';
Expand Down Expand Up @@ -153,6 +154,8 @@ export const SchemaEdit = () => {
export const SchemaList = () => {
const notify = useNotify();
const translate = useTranslate();
const { permissions } = usePermissions();
const canAccess = (op: string) => permissions && permissions.canAccess('crs', op)

const { updateCrdIds } = useUpdateCrdIds();

Expand All @@ -170,15 +173,15 @@ export const SchemaList = () => {
<Typography variant="subtitle1" sx={{ padding: '0px' }}>
{translate('resources.crs.listSubtitle')}
</Typography>
<List actions={<ListTopToolbar />}>
<List actions={<ListTopToolbar hasCreate={canAccess('write')} />}>
<Datagrid bulkActionButtons={false}>
<TextField source="crdId" />
<TextField source="version" />
<Box textAlign={'right'}>
<CopyButton />
<EditButton />
<ShowButton />
<DeleteWithConfirmButton mutationOptions={{ onSuccess }} />
{ canAccess('write') && <EditButton /> }
{ canAccess('read') && <ShowButton /> }
{ canAccess('write') && <DeleteWithConfirmButton mutationOptions={{ onSuccess }} /> }
</Box>
</Datagrid>
</List>
Expand All @@ -189,6 +192,9 @@ export const SchemaList = () => {
export const SchemaShow = () => {
const translate = useTranslate();
const { record } = useShowController();
const { permissions } = usePermissions();
const canAccess = (op: string) => permissions && permissions.canAccess('crs', op)

if (!record) return null;

return (
Expand All @@ -200,7 +206,7 @@ export const SchemaShow = () => {
recordRepresentation: record.id,
})}
</Typography>
<Show actions={<ShowTopToolbar />}>
<Show actions={<ShowTopToolbar hasEdit={canAccess('write')} hasDelete={canAccess('write')}/>}>
<SimpleShowLayout>
<TextField source="id" />
<ReferenceField
Expand Down
Loading

0 comments on commit 9ad0a16

Please sign in to comment.