Add basic permissions checking logic to frontend

This commit is contained in:
Dane Everitt 2020-03-28 17:25:04 -07:00
parent 7e0ac2c311
commit ab4c4e7e9e
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
4 changed files with 81 additions and 11 deletions

View file

@ -41,20 +41,23 @@ export const rawDataToServerObject = (data: any): Server => ({
port: data.sftp_details.port, port: data.sftp_details.port,
}, },
description: data.description ? ((data.description.length > 0) ? data.description : null) : null, description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
allocations: [{ allocations: [ {
ip: data.allocation.ip, ip: data.allocation.ip,
alias: null, alias: null,
port: data.allocation.port, port: data.allocation.port,
default: true, default: true,
}], } ],
limits: { ...data.limits }, limits: { ...data.limits },
featureLimits: { ...data.feature_limits }, featureLimits: { ...data.feature_limits },
}); });
export default (uuid: string): Promise<Server> => { export default (uuid: string): Promise<[ Server, string[] ]> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http.get(`/api/client/servers/${uuid}`) http.get(`/api/client/servers/${uuid}`)
.then(response => resolve(rawDataToServerObject(response.data.attributes))) .then(({ data }) => resolve([
rawDataToServerObject(data.attributes),
data.meta?.is_server_owner ? ['*'] : (data.meta?.user_permissions || []),
]))
.catch(reject); .catch(reject);
}); });
}; };

View file

@ -0,0 +1,44 @@
import React, { useMemo } from 'react';
import { ServerContext } from '@/state/server';
interface Props {
action: string | string[];
renderOnError?: React.ReactNode | null;
children: React.ReactNode;
}
const Can = ({ action, renderOnError, children }: Props) => {
const userPermissions = ServerContext.useStoreState(state => state.server.permissions);
const actions = Array.isArray(action) ? action : [ action ];
const missingPermissionCount = useMemo(() => {
if (userPermissions[0] === '*') {
return 0;
}
return actions.filter(permission => {
return !(
// Allows checking for any permission matching a name, for example files.*
// will return if the user has any permission under the file.XYZ namespace.
(
permission.endsWith('.*')
&& permission !== 'websocket.*'
&& userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0
)
// Otherwise just check if the entire permission exists in the array or not.
|| userPermissions.indexOf(permission) >= 0);
}).length;
}, [ action, userPermissions ]);
return (
<>
{missingPermissionCount > 0 ?
renderOnError
:
children
}
</>
);
};
export default Can;

View file

@ -15,6 +15,7 @@ import SettingsContainer from '@/components/server/settings/SettingsContainer';
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer'; import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer';
import UsersContainer from '@/components/server/users/UsersContainer'; import UsersContainer from '@/components/server/users/UsersContainer';
import Can from '@/components/elements/Can';
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
const server = ServerContext.useStoreState(state => state.server.data); const server = ServerContext.useStoreState(state => state.server.data);
@ -34,11 +35,21 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
<div id={'sub-navigation'}> <div id={'sub-navigation'}>
<div className={'items'}> <div className={'items'}>
<NavLink to={`${match.url}`} exact>Console</NavLink> <NavLink to={`${match.url}`} exact>Console</NavLink>
<NavLink to={`${match.url}/files`}>File Manager</NavLink> <Can action={'file.*'}>
<NavLink to={`${match.url}/databases`}>Databases</NavLink> <NavLink to={`${match.url}/files`}>File Manager</NavLink>
<NavLink to={`${match.url}/schedules`}>Schedules</NavLink> </Can>
<NavLink to={`${match.url}/users`}>Users</NavLink> <Can action={'database.*'}>
<NavLink to={`${match.url}/settings`}>Settings</NavLink> <NavLink to={`${match.url}/databases`}>Databases</NavLink>
</Can>
<Can action={'schedule.*'}>
<NavLink to={`${match.url}/schedules`}>Schedules</NavLink>
</Can>
<Can action={'user.*'}>
<NavLink to={`${match.url}/users`}>Users</NavLink>
</Can>
<Can action={'settings.*'}>
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
</Can>
</div> </div>
</div> </div>
</CSSTransition> </CSSTransition>

View file

@ -10,18 +10,30 @@ export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running';
interface ServerDataStore { interface ServerDataStore {
data?: Server; data?: Server;
permissions: string[];
getServer: Thunk<ServerDataStore, string, {}, ServerStore, Promise<void>>; getServer: Thunk<ServerDataStore, string, {}, ServerStore, Promise<void>>;
setServer: Action<ServerDataStore, Server>; setServer: Action<ServerDataStore, Server>;
setPermissions: Action<ServerDataStore, string[]>;
} }
const server: ServerDataStore = { const server: ServerDataStore = {
permissions: [],
getServer: thunk(async (actions, payload) => { getServer: thunk(async (actions, payload) => {
const server = await getServer(payload); const [server, permissions] = await getServer(payload);
actions.setServer(server); actions.setServer(server);
actions.setPermissions(permissions);
}), }),
setServer: action((state, payload) => { setServer: action((state, payload) => {
state.data = payload; state.data = payload;
}), }),
setPermissions: action((state, payload) => {
state.permissions = payload;
}),
}; };
interface ServerStatusStore { interface ServerStatusStore {
@ -75,9 +87,9 @@ export const ServerContext = createContextStore<ServerStore>({
subusers, subusers,
clearServerState: action(state => { clearServerState: action(state => {
state.server.data = undefined; state.server.data = undefined;
state.server.permissions = [];
state.databases.items = []; state.databases.items = [];
state.subusers.data = []; state.subusers.data = [];
state.files.directory = '/'; state.files.directory = '/';
state.files.contents = []; state.files.contents = [];