diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/app/core/actions/core.ts b/frontend/packages/console-dynamic-plugin-sdk/src/app/core/actions/core.ts index 6b7577064af..075f12fe061 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/app/core/actions/core.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/app/core/actions/core.ts @@ -12,8 +12,13 @@ export enum ActionType { } export const setUser = (userInfo: UserInfo) => action(ActionType.SetUser, { userInfo }); -export const beginImpersonate = (kind: string, name: string, subprotocols: string[]) => - action(ActionType.BeginImpersonate, { kind, name, subprotocols }); + +export const beginImpersonate = ( + kind: string, + name: string, + subprotocols: string[], + groups?: string[], +) => action(ActionType.BeginImpersonate, { kind, name, subprotocols, groups }); export const endImpersonate = () => action(ActionType.EndImpersonate); export const setAdmissionWebhookWarning = (id: string, warning: AdmissionWebhookWarning) => action(ActionType.SetAdmissionWebhookWarning, { id, warning }); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/app/core/reducers/core.ts b/frontend/packages/console-dynamic-plugin-sdk/src/app/core/reducers/core.ts index 3598222a686..b8f18a08de1 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/app/core/reducers/core.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/app/core/reducers/core.ts @@ -26,6 +26,7 @@ export const coreReducer = ( kind: action.payload.kind, name: action.payload.name, subprotocols: action.payload.subprotocols, + groups: action.payload.groups, }, }; case ActionType.EndImpersonate: { diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/app/redux-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/app/redux-types.ts index 5f2f5a1f13d..305f5342965 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/app/redux-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/app/redux-types.ts @@ -12,6 +12,7 @@ export type ImpersonateKind = { kind: string; name: string; subprotocols: string[]; + groups?: string[]; }; export type CoreState = { diff --git a/frontend/public/actions/ui.ts b/frontend/public/actions/ui.ts index a89c22544c4..a30083a0cc8 100644 --- a/frontend/public/actions/ui.ts +++ b/frontend/public/actions/ui.ts @@ -214,7 +214,10 @@ export const setActiveNamespace = (namespace: string = '') => { return action(ActionType.SetActiveNamespace, { namespace }); }; -export const startImpersonate = (kind: string, name: string) => async (dispatch, getState) => { +export const startImpersonate = (kind: string, name: string, groups?: string[]) => async ( + dispatch, + getState, +) => { const textEncoder = new TextEncoder(); const imp = getImpersonate(getState()); @@ -235,12 +238,24 @@ export const startImpersonate = (kind: string, name: string) => async (dispatch, let subprotocols; if (kind === 'User') { subprotocols = [`Impersonate-User.${encodedName}`]; - } - if (kind === 'Group') { + } else if (kind === 'Group') { subprotocols = [`Impersonate-Group.${encodedName}`]; + } else if (kind === 'UserWithGroups' && groups && groups.length > 0) { + // User with multiple groups impersonation + // Encode user subprotocol + subprotocols = [`Impersonate-User.${encodedName}`]; + // Encode each group as a separate subprotocol + groups.forEach((group) => { + const encodedGroup = Base64.encode( + String.fromCharCode.apply(String, textEncoder.encode(group)), + ) + .replace(/=/g, '_') + .replace(/\//g, '-'); + subprotocols.push(`Impersonate-Group.${encodedGroup}`); + }); } - dispatch(beginImpersonate(kind, name, subprotocols)); + dispatch(beginImpersonate(kind, name, subprotocols, groups)); subsClient.close(false, true); dispatch(clearSSARFlags()); dispatch(detectFeatures()); diff --git a/frontend/public/reducers/features.ts b/frontend/public/reducers/features.ts index 52ced286a60..390d0346529 100644 --- a/frontend/public/reducers/features.ts +++ b/frontend/public/reducers/features.ts @@ -40,6 +40,19 @@ export const defaults = _.mapValues(FLAGS, (flag) => { ); case FLAGS.DEVCONSOLE_PROXY: return true; + case FLAGS.IMPERSONATE: { + // FIXME: Check localStorage for override, default to false (disabled) + // This is the flag for the multi-group impersonation feature. + const localStorageValue = localStorage.getItem('bridge/impersonate-enabled'); + if (localStorageValue === 'true') { + // eslint-disable-next-line no-console + console.log('[Feature Flag] Impersonation enabled via localStorage'); + return true; + } + // eslint-disable-next-line no-console + console.log('[Feature Flag] Impersonation disabled (default or localStorage=false)'); + return false; + } default: return undefined; } diff --git a/frontend/public/redux.ts b/frontend/public/redux.ts index bd73bf534a0..673e0b9c5c6 100644 --- a/frontend/public/redux.ts +++ b/frontend/public/redux.ts @@ -59,4 +59,12 @@ if (process.env.NODE_ENV !== 'production') { window.store = store; } +// Temporary: Expose store for testing multi-group impersonation +// TODO: Remove this after testing. This SHOULD NOT BE IN MERGED in production!!!!! +(window as any).store = store; + +// Expose UI actions for testing +import * as UIActions from './actions/ui'; +(window as any).UIActions = UIActions; + export default store;