mirror of https://github.com/portainer/portainer
225 lines
5.6 KiB
TypeScript
225 lines
5.6 KiB
TypeScript
import { useCurrentStateAndParams } from '@uirouter/react';
|
|
import {
|
|
createContext,
|
|
ReactNode,
|
|
useContext,
|
|
useMemo,
|
|
PropsWithChildren,
|
|
} from 'react';
|
|
|
|
import { isEdgeAdmin, isPureAdmin } from '@/portainer/users/user.helpers';
|
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
import { User } from '@/portainer/users/types';
|
|
import { useLoadCurrentUser } from '@/portainer/users/queries/useLoadCurrentUser';
|
|
|
|
import { useEnvironment } from '../portainer/environments/queries';
|
|
import { isBE } from '../portainer/feature-flags/feature-flags.service';
|
|
|
|
interface State {
|
|
user?: User;
|
|
}
|
|
|
|
export const UserContext = createContext<State | null>(null);
|
|
UserContext.displayName = 'UserContext';
|
|
|
|
/**
|
|
* @deprecated use `useCurrentUser` instead
|
|
*/
|
|
export const useUser = useCurrentUser;
|
|
|
|
export function useCurrentUser() {
|
|
const context = useContext(UserContext);
|
|
|
|
if (context === null) {
|
|
throw new Error('should be nested under UserProvider');
|
|
}
|
|
|
|
const { user } = context;
|
|
if (typeof user === 'undefined') {
|
|
throw new Error('should be authenticated');
|
|
}
|
|
|
|
return useMemo(
|
|
() => ({
|
|
user,
|
|
isPureAdmin: isPureAdmin(user),
|
|
}),
|
|
[user]
|
|
);
|
|
}
|
|
|
|
export function useIsPureAdmin() {
|
|
const { isPureAdmin } = useCurrentUser();
|
|
return isPureAdmin;
|
|
}
|
|
|
|
/**
|
|
* Load the admin status of the user, returning true if the user is edge admin or admin.
|
|
* @param forceEnvironmentId to force the environment id, used where the environment id can't be loaded from the router, like sidebar
|
|
* @returns query result with isLoading and isAdmin - isAdmin is true if the user edge admin or admin.
|
|
*/
|
|
export function useIsEdgeAdmin({
|
|
forceEnvironmentId,
|
|
noEnvScope,
|
|
}: {
|
|
forceEnvironmentId?: EnvironmentId;
|
|
noEnvScope?: boolean;
|
|
} = {}) {
|
|
const { user } = useCurrentUser();
|
|
const {
|
|
params: { endpointId },
|
|
} = useCurrentStateAndParams();
|
|
|
|
const envId = forceEnvironmentId || endpointId;
|
|
const envScope = typeof noEnvScope === 'boolean' ? !noEnvScope : !!envId;
|
|
const envQuery = useEnvironment(envScope ? envId : undefined);
|
|
|
|
if (!envScope) {
|
|
return { isLoading: false, isAdmin: isEdgeAdmin(user) };
|
|
}
|
|
|
|
if (envQuery.isLoading) {
|
|
return { isLoading: true, isAdmin: false };
|
|
}
|
|
|
|
return {
|
|
isLoading: false,
|
|
isAdmin: isEdgeAdmin(user, envQuery.data),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the user has some of the authorizations
|
|
*
|
|
* @param authorizations a list of authorizations to check
|
|
* @param forceEnvironmentId to force the environment id, used where the environment id can't be loaded from the router, like sidebar
|
|
* @param adminOnlyCE if true, will return false if the user is not an admin in CE
|
|
* @returns query result with isLoading and authorized - authorized is true if the user has some of the authorizations
|
|
*/
|
|
export function useAuthorizations(
|
|
authorizations: string | string[],
|
|
forceEnvironmentId?: EnvironmentId,
|
|
adminOnlyCE = false
|
|
) {
|
|
const { user } = useCurrentUser();
|
|
const {
|
|
params: { endpointId },
|
|
} = useCurrentStateAndParams();
|
|
const envQuery = useEnvironment(forceEnvironmentId || endpointId);
|
|
const isAdminQuery = useIsEdgeAdmin({ forceEnvironmentId });
|
|
|
|
if (!user) {
|
|
return { authorized: false, isLoading: false };
|
|
}
|
|
|
|
if (envQuery.isInitialLoading || isAdminQuery.isLoading) {
|
|
return { authorized: false, isLoading: true };
|
|
}
|
|
|
|
if (isAdminQuery.isAdmin) {
|
|
return { authorized: true, isLoading: false };
|
|
}
|
|
|
|
if (!isBE && adminOnlyCE) {
|
|
return { authorized: false, isLoading: false };
|
|
}
|
|
|
|
return {
|
|
authorized: hasAuthorizations(user, authorizations, envQuery.data?.Id),
|
|
isLoading: false,
|
|
};
|
|
}
|
|
|
|
export function useIsEnvironmentAdmin({
|
|
forceEnvironmentId,
|
|
adminOnlyCE = true,
|
|
}: {
|
|
forceEnvironmentId?: EnvironmentId;
|
|
adminOnlyCE?: boolean;
|
|
} = {}) {
|
|
return useAuthorizations(
|
|
['EndpointResourcesAccess'],
|
|
forceEnvironmentId,
|
|
adminOnlyCE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* will return true if the user has some of the authorizations. assumes the user is authenticated and not an admin
|
|
*
|
|
* @private Please use `useAuthorizations` instead. Exported only for angular's authentication service app/portainer/services/authentication.js:154
|
|
*/
|
|
export function hasAuthorizations(
|
|
user: User,
|
|
authorizations: string | string[],
|
|
environmentId?: EnvironmentId
|
|
) {
|
|
if (!isBE) {
|
|
return true;
|
|
}
|
|
|
|
const authorizationsArray =
|
|
typeof authorizations === 'string' ? [authorizations] : authorizations;
|
|
|
|
if (authorizationsArray.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
if (!environmentId) {
|
|
return false;
|
|
}
|
|
|
|
const userEndpointAuthorizations =
|
|
user.EndpointAuthorizations?.[environmentId] || [];
|
|
|
|
return authorizationsArray.some(
|
|
(authorization) => userEndpointAuthorizations[authorization]
|
|
);
|
|
}
|
|
|
|
interface AuthorizedProps {
|
|
authorizations: string | string[];
|
|
environmentId?: EnvironmentId;
|
|
adminOnlyCE?: boolean;
|
|
childrenUnauthorized?: ReactNode;
|
|
}
|
|
|
|
export function Authorized({
|
|
authorizations,
|
|
environmentId,
|
|
adminOnlyCE = false,
|
|
children,
|
|
childrenUnauthorized = null,
|
|
}: PropsWithChildren<AuthorizedProps>) {
|
|
const { authorized } = useAuthorizations(
|
|
authorizations,
|
|
environmentId,
|
|
adminOnlyCE
|
|
);
|
|
|
|
return authorized ? <>{children}</> : <>{childrenUnauthorized}</>;
|
|
}
|
|
|
|
interface UserProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function UserProvider({ children }: UserProviderProps) {
|
|
const userQuery = useLoadCurrentUser();
|
|
|
|
const providerState = useMemo(
|
|
() => ({ user: userQuery.data }),
|
|
[userQuery.data]
|
|
);
|
|
|
|
if (!providerState.user) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<UserContext.Provider value={providerState}>
|
|
{children}
|
|
</UserContext.Provider>
|
|
);
|
|
}
|