mirror of https://github.com/portainer/portainer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
3.6 KiB
181 lines
3.6 KiB
3 years ago
|
import jwtDecode from 'jwt-decode';
|
||
|
import { useCurrentStateAndParams } from '@uirouter/react';
|
||
|
import {
|
||
|
createContext,
|
||
|
ReactNode,
|
||
|
useContext,
|
||
|
useEffect,
|
||
|
useState,
|
||
3 years ago
|
useMemo,
|
||
2 years ago
|
PropsWithChildren,
|
||
3 years ago
|
} from 'react';
|
||
|
|
||
2 years ago
|
import { isAdmin } from '@/portainer/users/user.helpers';
|
||
2 years ago
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||
2 years ago
|
import { getUser } from '@/portainer/users/user.service';
|
||
|
import { User, UserId } from '@/portainer/users/types';
|
||
3 years ago
|
|
||
|
import { useLocalStorage } from './useLocalStorage';
|
||
|
|
||
|
interface State {
|
||
3 years ago
|
user?: User;
|
||
3 years ago
|
}
|
||
|
|
||
|
export const UserContext = createContext<State | null>(null);
|
||
2 years ago
|
UserContext.displayName = 'UserContext';
|
||
3 years ago
|
|
||
|
export function useUser() {
|
||
|
const context = useContext(UserContext);
|
||
|
|
||
|
if (context === null) {
|
||
|
throw new Error('should be nested under UserProvider');
|
||
|
}
|
||
|
|
||
2 years ago
|
const { user } = context;
|
||
|
if (typeof user === 'undefined') {
|
||
|
throw new Error('should be authenticated');
|
||
|
}
|
||
|
|
||
3 years ago
|
return useMemo(
|
||
2 years ago
|
() => ({
|
||
|
user,
|
||
|
isAdmin: isAdmin(user),
|
||
|
}),
|
||
|
[user]
|
||
3 years ago
|
);
|
||
3 years ago
|
}
|
||
|
|
||
3 years ago
|
export function useAuthorizations(
|
||
|
authorizations: string | string[],
|
||
2 years ago
|
forceEnvironmentId?: EnvironmentId,
|
||
3 years ago
|
adminOnlyCE = false
|
||
|
) {
|
||
3 years ago
|
const { user } = useUser();
|
||
2 years ago
|
const {
|
||
|
params: { endpointId },
|
||
|
} = useCurrentStateAndParams();
|
||
3 years ago
|
|
||
3 years ago
|
if (!user) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
2 years ago
|
return hasAuthorizations(
|
||
|
user,
|
||
|
authorizations,
|
||
|
forceEnvironmentId || endpointId,
|
||
|
adminOnlyCE
|
||
|
);
|
||
2 years ago
|
}
|
||
|
|
||
|
export function isEnvironmentAdmin(
|
||
|
user: User,
|
||
|
environmentId: EnvironmentId,
|
||
2 years ago
|
adminOnlyCE = true
|
||
2 years ago
|
) {
|
||
|
return hasAuthorizations(
|
||
|
user,
|
||
|
['EndpointResourcesAccess'],
|
||
|
environmentId,
|
||
|
adminOnlyCE
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export function hasAuthorizations(
|
||
|
user: User,
|
||
|
authorizations: string | string[],
|
||
|
environmentId?: EnvironmentId,
|
||
|
adminOnlyCE = false
|
||
|
) {
|
||
|
const authorizationsArray =
|
||
|
typeof authorizations === 'string' ? [authorizations] : authorizations;
|
||
|
|
||
|
if (authorizationsArray.length === 0) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
3 years ago
|
if (process.env.PORTAINER_EDITION === 'CE') {
|
||
|
return !adminOnlyCE || isAdmin(user);
|
||
|
}
|
||
|
|
||
2 years ago
|
if (!environmentId) {
|
||
3 years ago
|
return false;
|
||
|
}
|
||
|
|
||
|
if (isAdmin(user)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (
|
||
|
!user.EndpointAuthorizations ||
|
||
2 years ago
|
!user.EndpointAuthorizations[environmentId]
|
||
3 years ago
|
) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
2 years ago
|
const userEndpointAuthorizations = user.EndpointAuthorizations[environmentId];
|
||
3 years ago
|
return authorizationsArray.some(
|
||
|
(authorization) => userEndpointAuthorizations[authorization]
|
||
|
);
|
||
|
}
|
||
|
|
||
|
interface AuthorizedProps {
|
||
|
authorizations: string | string[];
|
||
2 years ago
|
environmentId?: EnvironmentId;
|
||
2 years ago
|
adminOnlyCE?: boolean;
|
||
2 years ago
|
childrenUnauthorized?: ReactNode;
|
||
3 years ago
|
}
|
||
|
|
||
2 years ago
|
export function Authorized({
|
||
|
authorizations,
|
||
2 years ago
|
environmentId,
|
||
2 years ago
|
adminOnlyCE = false,
|
||
|
children,
|
||
2 years ago
|
childrenUnauthorized = null,
|
||
2 years ago
|
}: PropsWithChildren<AuthorizedProps>) {
|
||
2 years ago
|
const isAllowed = useAuthorizations(
|
||
|
authorizations,
|
||
|
environmentId,
|
||
|
adminOnlyCE
|
||
|
);
|
||
3 years ago
|
|
||
2 years ago
|
return isAllowed ? <>{children}</> : <>{childrenUnauthorized}</>;
|
||
3 years ago
|
}
|
||
|
|
||
|
interface UserProviderProps {
|
||
|
children: ReactNode;
|
||
|
}
|
||
|
|
||
|
export function UserProvider({ children }: UserProviderProps) {
|
||
|
const [jwt] = useLocalStorage('JWT', '');
|
||
2 years ago
|
const [user, setUser] = useState<User>();
|
||
3 years ago
|
|
||
|
useEffect(() => {
|
||
3 years ago
|
if (jwt !== '') {
|
||
3 years ago
|
const tokenPayload = jwtDecode(jwt) as { id: number };
|
||
|
|
||
|
loadUser(tokenPayload.id);
|
||
|
}
|
||
|
}, [jwt]);
|
||
|
|
||
3 years ago
|
const providerState = useMemo(() => ({ user }), [user]);
|
||
|
|
||
3 years ago
|
if (jwt === '') {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
2 years ago
|
if (!providerState.user) {
|
||
3 years ago
|
return null;
|
||
|
}
|
||
|
|
||
|
return (
|
||
3 years ago
|
<UserContext.Provider value={providerState}>
|
||
|
{children}
|
||
|
</UserContext.Provider>
|
||
3 years ago
|
);
|
||
|
|
||
3 years ago
|
async function loadUser(id: UserId) {
|
||
3 years ago
|
const user = await getUser(id);
|
||
|
setUser(user);
|
||
|
}
|
||
|
}
|