[7.x] [SIEM] [Detection Engine] Remove has manage api keys req… (#62534)

Alerting no longer requires the manage_api_keys privilege, so we are removing it from the detection engine code. Fixes #62387

* removes hasManageApiKeys since alerting is using the internal user api calls, manage_api_keys privilege is no longer necessary

* linting error

* fixes types and removes a test for manage api keys

* removes manage api key reducer and updates leftover tests

* moves userHasNoPermissions repeated code into a function in helpers, adds a few test cases, updated references to new function

* fix test title

* remove userHasNoPermissions function and remove tests, replace with just not canUserCRUD

* Revert "remove userHasNoPermissions function and remove tests, replace with just not canUserCRUD"

This reverts commit 93912e7e22.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Devin W. Hurley 2020-04-04 11:15:10 -04:00 committed by GitHub
parent 089f944622
commit 0f2f2113a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 51 additions and 112 deletions

View file

@ -22,7 +22,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: null,
hasIndexWrite: null,
hasManageApiKey: null,
isAuthenticated: null,
hasEncryptionKey: null,
isSignalIndexExists: null,
@ -50,7 +49,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: null,
hasIndexWrite: null,
hasManageApiKey: null,
isAuthenticated: null,
hasEncryptionKey: null,
isSignalIndexExists: null,
@ -79,7 +77,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: true,
@ -116,7 +113,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: true,
@ -139,7 +135,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: false,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: true,
@ -161,29 +156,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: false,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: true,
})
);
await waitForNextUpdate();
await waitForNextUpdate();
let resp = null;
if (result.current.createPrePackagedRules) {
resp = await result.current.createPrePackagedRules();
}
expect(resp).toEqual(false);
});
});
test('can NOT createPrePackagedRules because hasManageApiKey === false', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRules>(() =>
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: false,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: true,
@ -205,7 +177,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: false,
hasEncryptionKey: true,
isSignalIndexExists: true,
@ -227,7 +198,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: false,
isSignalIndexExists: true,
@ -249,7 +219,6 @@ describe('usePersistRule', () => {
usePrePackagedRules({
canUserCRUD: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
hasEncryptionKey: true,
isSignalIndexExists: false,

View file

@ -26,7 +26,6 @@ export interface ReturnPrePackagedRules {
interface UsePrePackagedRuleProps {
canUserCRUD: boolean | null;
hasIndexWrite: boolean | null;
hasManageApiKey: boolean | null;
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
isSignalIndexExists: boolean | null;
@ -36,7 +35,6 @@ interface UsePrePackagedRuleProps {
* Hook for using to get status about pre-packaged Rules from the Detection Engine API
*
* @param hasIndexWrite boolean
* @param hasManageApiKey boolean
* @param isAuthenticated boolean
* @param hasEncryptionKey boolean
* @param isSignalIndexExists boolean
@ -45,7 +43,6 @@ interface UsePrePackagedRuleProps {
export const usePrePackagedRules = ({
canUserCRUD,
hasIndexWrite,
hasManageApiKey,
isAuthenticated,
hasEncryptionKey,
isSignalIndexExists,
@ -117,7 +114,6 @@ export const usePrePackagedRules = ({
if (
canUserCRUD &&
hasIndexWrite &&
hasManageApiKey &&
isAuthenticated &&
hasEncryptionKey &&
isSignalIndexExists
@ -185,14 +181,7 @@ export const usePrePackagedRules = ({
isSubscribed = false;
abortCtrl.abort();
};
}, [
canUserCRUD,
hasIndexWrite,
hasManageApiKey,
isAuthenticated,
hasEncryptionKey,
isSignalIndexExists,
]);
}, [canUserCRUD, hasIndexWrite, isAuthenticated, hasEncryptionKey, isSignalIndexExists]);
return {
loading,

View file

@ -992,7 +992,6 @@ export const mockUserPrivilege: Privilege = {
monitor_watcher: true,
monitor_transform: true,
read_ilm: true,
manage_api_key: true,
manage_security: true,
manage_own_api_key: false,
manage_saml: true,

View file

@ -54,7 +54,6 @@ export interface Privilege {
monitor_watcher: boolean;
monitor_transform: boolean;
read_ilm: boolean;
manage_api_key: boolean;
manage_security: boolean;
manage_own_api_key: boolean;
manage_saml: boolean;

View file

@ -21,7 +21,6 @@ describe('usePrivilegeUser', () => {
hasEncryptionKey: null,
hasIndexManage: null,
hasIndexWrite: null,
hasManageApiKey: null,
isAuthenticated: null,
loading: true,
});
@ -39,7 +38,6 @@ describe('usePrivilegeUser', () => {
hasEncryptionKey: true,
hasIndexManage: true,
hasIndexWrite: true,
hasManageApiKey: true,
isAuthenticated: true,
loading: false,
});
@ -61,7 +59,6 @@ describe('usePrivilegeUser', () => {
hasEncryptionKey: false,
hasIndexManage: false,
hasIndexWrite: false,
hasManageApiKey: false,
isAuthenticated: false,
loading: false,
});

View file

@ -15,7 +15,6 @@ export interface ReturnPrivilegeUser {
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
hasIndexManage: boolean | null;
hasManageApiKey: boolean | null;
hasIndexWrite: boolean | null;
}
/**
@ -27,17 +26,12 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => {
const [privilegeUser, setPrivilegeUser] = useState<
Pick<
ReturnPrivilegeUser,
| 'isAuthenticated'
| 'hasEncryptionKey'
| 'hasIndexManage'
| 'hasManageApiKey'
| 'hasIndexWrite'
'isAuthenticated' | 'hasEncryptionKey' | 'hasIndexManage' | 'hasIndexWrite'
>
>({
isAuthenticated: null,
hasEncryptionKey: null,
hasIndexManage: null,
hasManageApiKey: null,
hasIndexWrite: null,
});
const [, dispatchToaster] = useStateToaster();
@ -65,10 +59,6 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => {
privilege.index[indexName].create_doc ||
privilege.index[indexName].index ||
privilege.index[indexName].write,
hasManageApiKey:
privilege.cluster.manage_security ||
privilege.cluster.manage_api_key ||
privilege.cluster.manage_own_api_key,
});
}
}
@ -78,7 +68,6 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => {
isAuthenticated: false,
hasEncryptionKey: false,
hasIndexManage: false,
hasManageApiKey: false,
hasIndexWrite: false,
});
errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster });

View file

@ -15,7 +15,6 @@ export interface State {
canUserCRUD: boolean | null;
hasIndexManage: boolean | null;
hasIndexWrite: boolean | null;
hasManageApiKey: boolean | null;
isSignalIndexExists: boolean | null;
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
@ -27,7 +26,6 @@ const initialState: State = {
canUserCRUD: null,
hasIndexManage: null,
hasIndexWrite: null,
hasManageApiKey: null,
isSignalIndexExists: null,
isAuthenticated: null,
hasEncryptionKey: null,
@ -37,10 +35,6 @@ const initialState: State = {
export type Action =
| { type: 'updateLoading'; loading: boolean }
| {
type: 'updateHasManageApiKey';
hasManageApiKey: boolean | null;
}
| {
type: 'updateHasIndexManage';
hasIndexManage: boolean | null;
@ -90,12 +84,6 @@ export const userInfoReducer = (state: State, action: Action): State => {
hasIndexWrite: action.hasIndexWrite,
};
}
case 'updateHasManageApiKey': {
return {
...state,
hasManageApiKey: action.hasManageApiKey,
};
}
case 'updateIsSignalIndexExists': {
return {
...state,
@ -151,7 +139,6 @@ export const useUserInfo = (): State => {
canUserCRUD,
hasIndexManage,
hasIndexWrite,
hasManageApiKey,
isSignalIndexExists,
isAuthenticated,
hasEncryptionKey,
@ -166,7 +153,6 @@ export const useUserInfo = (): State => {
hasEncryptionKey: isApiEncryptionKey,
hasIndexManage: hasApiIndexManage,
hasIndexWrite: hasApiIndexWrite,
hasManageApiKey: hasApiManageApiKey,
} = usePrivilegeUser();
const {
loading: indexNameLoading,
@ -197,12 +183,6 @@ export const useUserInfo = (): State => {
}
}, [loading, hasIndexWrite, hasApiIndexWrite]);
useEffect(() => {
if (!loading && hasManageApiKey !== hasApiManageApiKey && hasApiManageApiKey != null) {
dispatch({ type: 'updateHasManageApiKey', hasManageApiKey: hasApiManageApiKey });
}
}, [loading, hasManageApiKey, hasApiManageApiKey]);
useEffect(() => {
if (
!loading &&
@ -258,7 +238,6 @@ export const useUserInfo = (): State => {
canUserCRUD,
hasIndexManage,
hasIndexWrite,
hasManageApiKey,
signalIndexName,
};
};

View file

@ -24,7 +24,7 @@ import { StepScheduleRule } from '../components/step_schedule_rule';
import { StepRuleActions } from '../components/step_rule_actions';
import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page';
import * as RuleI18n from '../translations';
import { redirectToDetections, getActionMessageParams } from '../helpers';
import { redirectToDetections, getActionMessageParams, userHasNoPermissions } from '../helpers';
import {
AboutStepRule,
DefineStepRule,
@ -85,7 +85,6 @@ const CreateRulePageComponent: React.FC = () => {
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
hasManageApiKey,
} = useUserInfo();
const [, dispatchToaster] = useStateToaster();
const [openAccordionId, setOpenAccordionId] = useState<RuleStep>(RuleStep.defineRule);
@ -117,8 +116,6 @@ const CreateRulePageComponent: React.FC = () => {
getActionMessageParams((stepsData.current['define-rule'].data as DefineStepRule).ruleType),
[stepsData.current['define-rule'].data]
);
const userHasNoPermissions =
canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false;
const setStepData = useCallback(
(step: RuleStep, data: unknown, isValid: boolean) => {
@ -274,7 +271,7 @@ const CreateRulePageComponent: React.FC = () => {
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}`} />;
} else if (userHasNoPermissions) {
} else if (userHasNoPermissions(canUserCRUD)) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules`} />;
}

View file

@ -53,7 +53,7 @@ import * as detectionI18n from '../../translations';
import { ReadOnlyCallOut } from '../components/read_only_callout';
import { RuleSwitch } from '../components/rule_switch';
import { StepPanel } from '../components/step_panel';
import { getStepsData, redirectToDetections } from '../helpers';
import { getStepsData, redirectToDetections, userHasNoPermissions } from '../helpers';
import * as ruleI18n from '../translations';
import * as i18n from './translations';
import { GlobalTime } from '../../../../containers/global_time';
@ -96,7 +96,6 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
hasManageApiKey,
hasIndexWrite,
signalIndexName,
} = useUserInfo();
@ -115,8 +114,6 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
scheduleRuleData: null,
};
const [lastSignals] = useSignalInfo({ ruleId });
const userHasNoPermissions =
canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false;
const title = isLoading === true || rule === null ? <EuiLoadingSpinner size="m" /> : rule.name;
const subTitle = useMemo(
@ -227,7 +224,7 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
return (
<>
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
{userHasNoPermissions && <ReadOnlyCallOut />}
{userHasNoPermissions(canUserCRUD) && <ReadOnlyCallOut />}
<WithSource sourceId="default" indexToAdd={indexToAdd}>
{({ indicesExist, indexPattern }) => {
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
@ -264,7 +261,7 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
<EuiFlexItem grow={false}>
<RuleSwitch
id={rule?.id ?? '-1'}
isDisabled={userHasNoPermissions}
isDisabled={userHasNoPermissions(canUserCRUD)}
enabled={rule?.enabled ?? false}
optionLabel={i18n.ACTIVATE_RULE}
onChange={handleOnChangeEnabledRule}
@ -277,7 +274,7 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
<EuiButton
href={getEditRuleUrl(ruleId ?? '')}
iconType="controlsHorizontal"
isDisabled={userHasNoPermissions ?? true}
isDisabled={userHasNoPermissions(canUserCRUD) ?? true}
>
{ruleI18n.EDIT_RULE_SETTINGS}
</EuiButton>
@ -285,7 +282,7 @@ const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
<EuiFlexItem grow={false}>
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={userHasNoPermissions}
userHasNoPermissions={userHasNoPermissions(canUserCRUD)}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -33,7 +33,12 @@ import { StepDefineRule } from '../components/step_define_rule';
import { StepScheduleRule } from '../components/step_schedule_rule';
import { StepRuleActions } from '../components/step_rule_actions';
import { formatRule } from '../create/helpers';
import { getStepsData, redirectToDetections, getActionMessageParams } from '../helpers';
import {
getStepsData,
redirectToDetections,
getActionMessageParams,
userHasNoPermissions,
} from '../helpers';
import * as ruleI18n from '../translations';
import {
RuleStep,
@ -69,14 +74,10 @@ const EditRulePageComponent: FC = () => {
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
hasManageApiKey,
} = useUserInfo();
const { detailName: ruleId } = useParams();
const [loading, rule] = useRule(ruleId);
const userHasNoPermissions =
canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false;
const [initForm, setInitForm] = useState(false);
const [myAboutRuleForm, setMyAboutRuleForm] = useState<AboutStepRuleForm>({
data: null,
@ -346,7 +347,7 @@ const EditRulePageComponent: FC = () => {
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}`} />;
} else if (userHasNoPermissions) {
} else if (userHasNoPermissions(canUserCRUD)) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${ruleId}`} />;
}

View file

@ -14,6 +14,7 @@ import {
getHumanizedDuration,
getModifiedAboutDetailsData,
determineDetailsValue,
userHasNoPermissions,
} from './helpers';
import { mockRuleWithEverything, mockRule } from './all/__mocks__/mock';
import { esFilters } from '../../../../../../../../src/plugins/data/public';
@ -337,4 +338,27 @@ describe('rule helpers', () => {
expect(result).toEqual(aboutRuleDetailsData);
});
});
describe('userHasNoPermissions', () => {
test("returns false when user's CRUD operations are null", () => {
const result: boolean = userHasNoPermissions(null);
const userHasNoPermissionsExpectedResult = false;
expect(result).toEqual(userHasNoPermissionsExpectedResult);
});
test('returns true when user cannot CRUD', () => {
const result: boolean = userHasNoPermissions(false);
const userHasNoPermissionsExpectedResult = true;
expect(result).toEqual(userHasNoPermissionsExpectedResult);
});
test('returns false when user can CRUD', () => {
const result: boolean = userHasNoPermissions(true);
const userHasNoPermissionsExpectedResult = false;
expect(result).toEqual(userHasNoPermissionsExpectedResult);
});
});
});

View file

@ -267,3 +267,7 @@ export const getActionMessageParams = memoizeOne((ruleType: RuleType | undefined
...actionMessageRuleParams.map(param => `context.rule.${param}`),
];
});
// typed as null not undefined as the initial state for this value is null.
export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean =>
canUserCRUD != null ? !canUserCRUD : false;

View file

@ -23,7 +23,7 @@ import { AllRules } from './all';
import { ImportDataModal } from '../../../components/import_data_modal';
import { ReadOnlyCallOut } from './components/read_only_callout';
import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/update_callout';
import { getPrePackagedRuleStatus, redirectToDetections } from './helpers';
import { getPrePackagedRuleStatus, redirectToDetections, userHasNoPermissions } from './helpers';
import * as i18n from './translations';
type Func = (refreshPrePackagedRule?: boolean) => void;
@ -38,7 +38,6 @@ const RulesPageComponent: React.FC = () => {
hasEncryptionKey,
canUserCRUD,
hasIndexWrite,
hasManageApiKey,
} = useUserInfo();
const {
createPrePackagedRules,
@ -52,7 +51,6 @@ const RulesPageComponent: React.FC = () => {
} = usePrePackagedRules({
canUserCRUD,
hasIndexWrite,
hasManageApiKey,
isSignalIndexExists,
isAuthenticated,
hasEncryptionKey,
@ -63,9 +61,6 @@ const RulesPageComponent: React.FC = () => {
rulesNotUpdated
);
const userHasNoPermissions =
canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false;
const handleRefreshRules = useCallback(async () => {
if (refreshRulesData.current != null) {
refreshRulesData.current(true);
@ -95,7 +90,7 @@ const RulesPageComponent: React.FC = () => {
return (
<>
{userHasNoPermissions && <ReadOnlyCallOut />}
{userHasNoPermissions(canUserCRUD) && <ReadOnlyCallOut />}
<ImportDataModal
checkBoxLabel={i18n.OVERWRITE_WITH_SAME_NAME}
closeModal={() => setShowImportModal(false)}
@ -125,7 +120,7 @@ const RulesPageComponent: React.FC = () => {
<EuiButton
iconType="indexOpen"
isLoading={loadingCreatePrePackagedRules}
isDisabled={userHasNoPermissions || loading}
isDisabled={userHasNoPermissions(canUserCRUD) || loading}
onClick={handleCreatePrePackagedRules}
>
{i18n.LOAD_PREPACKAGED_RULES}
@ -138,7 +133,7 @@ const RulesPageComponent: React.FC = () => {
data-test-subj="reloadPrebuiltRulesBtn"
iconType="plusInCircle"
isLoading={loadingCreatePrePackagedRules}
isDisabled={userHasNoPermissions || loading}
isDisabled={userHasNoPermissions(canUserCRUD) || loading}
onClick={handleCreatePrePackagedRules}
>
{i18n.RELOAD_MISSING_PREPACKAGED_RULES(rulesNotInstalled ?? 0)}
@ -148,7 +143,7 @@ const RulesPageComponent: React.FC = () => {
<EuiFlexItem grow={false}>
<EuiButton
iconType="importAction"
isDisabled={userHasNoPermissions || loading}
isDisabled={userHasNoPermissions(canUserCRUD) || loading}
onClick={() => {
setShowImportModal(true);
}}
@ -162,7 +157,7 @@ const RulesPageComponent: React.FC = () => {
fill
href={getCreateRuleUrl()}
iconType="plusInCircle"
isDisabled={userHasNoPermissions || loading}
isDisabled={userHasNoPermissions(canUserCRUD) || loading}
>
{i18n.ADD_NEW_RULE}
</EuiButton>
@ -180,7 +175,7 @@ const RulesPageComponent: React.FC = () => {
createPrePackagedRules={createPrePackagedRules}
loading={loading || prePackagedRuleLoading}
loadingCreatePrePackagedRules={loadingCreatePrePackagedRules}
hasNoPermissions={userHasNoPermissions}
hasNoPermissions={userHasNoPermissions(canUserCRUD)}
refetchPrePackagedRulesStatus={handleRefetchPrePackagedRulesStatus}
rulesCustomInstalled={rulesCustomInstalled}
rulesInstalled={rulesInstalled}