28a06e80a8
Maintainer pre-merge review follow-up (all non-blocker dead code):
F1: delete the dead S3-backup remnants (validation, query hooks, S3-only query
key, BackupS3Model/Settings types) — kept the CE file-backup path.
F2: delete the orphaned user-activity services + their registration (kept the
notifications component and routes).
F3: drop the unused buildOpenLDAPSettingsModel().
F4: drop the dead one-option ldap-options data (the selector was already collapsed).
F5: remove the dead data-edition attribute + its process.env typing; silence the
intentional hasAuthorizations unused-params; drop the dead useRolesState meta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
208 lines
5.5 KiB
TypeScript
208 lines
5.5 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';
|
|
|
|
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 (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
|
|
*/
|
|
/* eslint-disable @typescript-eslint/no-unused-vars -- signature kept for the AngularJS authentication.js caller; args are unused because CE has no per-endpoint authorization gating (that only existed in the Business Edition). */
|
|
export function hasAuthorizations(
|
|
user: User,
|
|
authorizations: string | string[],
|
|
environmentId?: EnvironmentId
|
|
) {
|
|
// In CE every authenticated user passes endpoint authorization checks here;
|
|
// per-endpoint authorization gating only existed in the Business Edition.
|
|
return true;
|
|
}
|
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
|
|
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>
|
|
);
|
|
}
|