Skip to content

Commit

Permalink
Add PLI authorisation for Cases Connectors
Browse files Browse the repository at this point in the history
  • Loading branch information
machadoum committed Jul 24, 2023
1 parent e298d2c commit 8b47175
Show file tree
Hide file tree
Showing 23 changed files with 253 additions and 56 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const READ_CASES_CAPABILITY = 'read_cases' as const;
export const UPDATE_CASES_CAPABILITY = 'update_cases' as const;
export const DELETE_CASES_CAPABILITY = 'delete_cases' as const;
export const PUSH_CASES_CAPABILITY = 'push_cases' as const;
export const CASES_CONNECTOR_CAPABILITY = 'cases_connector' as const;

/**
* Cases API Tags
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import type {
CommentResponseTypePersistableState,
GetCaseUsersResponse,
} from '../api';
import type { PUSH_CASES_CAPABILITY } from '../constants';
import type { CASES_CONNECTOR_CAPABILITY, PUSH_CASES_CAPABILITY } from '../constants';
import type { SnakeToCamelCase } from '../types';
import type { ActionConnector, UserAction } from '../types/domain';
import type {
Expand Down Expand Up @@ -287,6 +287,7 @@ export interface CasesPermissions {
update: boolean;
delete: boolean;
push: boolean;
connectors: boolean;
}

export interface CasesCapabilities {
Expand All @@ -295,4 +296,5 @@ export interface CasesCapabilities {
[UPDATE_CASES_CAPABILITY]: boolean;
[DELETE_CASES_CAPABILITY]: boolean;
[PUSH_CASES_CAPABILITY]: boolean;
[CASES_CONNECTOR_CAPABILITY]: boolean;
}
4 changes: 3 additions & 1 deletion x-pack/plugins/cases/common/utils/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import {
CASES_CONNECTOR_CAPABILITY,
CREATE_CASES_CAPABILITY,
DELETE_CASES_CAPABILITY,
PUSH_CASES_CAPABILITY,
Expand All @@ -23,7 +24,8 @@ export const createUICapabilities = () => ({
READ_CASES_CAPABILITY,
UPDATE_CASES_CAPABILITY,
PUSH_CASES_CAPABILITY,
CASES_CONNECTOR_CAPABILITY,
] as const,
read: [READ_CASES_CAPABILITY] as const,
read: [READ_CASES_CAPABILITY, CASES_CONNECTOR_CAPABILITY] as const,
delete: [DELETE_CASES_CAPABILITY] as const,
});
5 changes: 4 additions & 1 deletion x-pack/plugins/cases/public/client/helpers/can_use_cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ export const canUseCases =
acc.update = acc.update || userCapabilitiesForOwner.update;
acc.delete = acc.delete || userCapabilitiesForOwner.delete;
acc.push = acc.push || userCapabilitiesForOwner.push;
const allFromAcc = acc.create && acc.read && acc.update && acc.delete && acc.push;
const allFromAcc =
acc.create && acc.read && acc.update && acc.delete && acc.push && acc.connectors;
acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc;
acc.connectors = acc.connectors || userCapabilitiesForOwner.connectors;

return acc;
},
Expand All @@ -52,6 +54,7 @@ export const canUseCases =
update: false,
delete: false,
push: false,
connectors: false,
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities(undefined)).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": false,
"create": false,
"delete": false,
"push": false,
Expand All @@ -25,6 +26,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities()).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": false,
"create": false,
"delete": false,
"push": false,
Expand All @@ -38,6 +40,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": false,
"create": true,
"delete": false,
"push": false,
Expand All @@ -55,10 +58,12 @@ describe('getUICapabilities', () => {
update_cases: false,
delete_cases: false,
push_cases: false,
cases_connector: false,
})
).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": false,
"create": false,
"delete": false,
"push": false,
Expand All @@ -72,6 +77,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities({})).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": false,
"create": false,
"delete": false,
"push": false,
Expand All @@ -89,10 +95,12 @@ describe('getUICapabilities', () => {
update_cases: true,
delete_cases: true,
push_cases: true,
cases_connector: true,
})
).toMatchInlineSnapshot(`
Object {
"all": false,
"connectors": true,
"create": false,
"delete": true,
"push": true,
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/cases/public/client/helpers/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import type { CasesPermissions } from '../../../common';
import {
CASES_CONNECTOR_CAPABILITY,
CREATE_CASES_CAPABILITY,
DELETE_CASES_CAPABILITY,
PUSH_CASES_CAPABILITY,
Expand All @@ -22,7 +23,8 @@ export const getUICapabilities = (
const update = !!featureCapabilities?.[UPDATE_CASES_CAPABILITY];
const deletePriv = !!featureCapabilities?.[DELETE_CASES_CAPABILITY];
const push = !!featureCapabilities?.[PUSH_CASES_CAPABILITY];
const all = create && read && update && deletePriv && push;
const connectors = !!featureCapabilities?.[CASES_CONNECTOR_CAPABILITY];
const all = create && read && update && deletePriv && push && connectors;

return {
all,
Expand All @@ -31,5 +33,6 @@ export const getUICapabilities = (
update,
delete: deletePriv,
push,
connectors,
};
};
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/public/common/lib/kibana/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => {
update: permissions.update,
delete: permissions.delete,
push: permissions.push,
connectors: permissions.connectors,
},
visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show },
dashboard: {
Expand All @@ -213,6 +214,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => {
permissions.update,
permissions.delete,
permissions.push,
permissions.connectors,
]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta
update_cases: true,
delete_cases: true,
push_cases: true,
cases_connector: true,
},
visualize: { save: true, show: true },
dashboard: { show: true, createNew: true },
Expand Down
23 changes: 21 additions & 2 deletions x-pack/plugins/cases/public/common/mock/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,23 @@ import type { CasesCapabilities, CasesPermissions } from '../../containers/types

export const allCasesPermissions = () => buildCasesPermissions();
export const noCasesPermissions = () =>
buildCasesPermissions({ read: false, create: false, update: false, delete: false, push: false });
buildCasesPermissions({
read: false,
create: false,
update: false,
delete: false,
push: false,
connectors: false,
});
export const readCasesPermissions = () =>
buildCasesPermissions({ read: true, create: false, update: false, delete: false, push: false });
buildCasesPermissions({
read: true,
create: false,
update: false,
delete: false,
push: false,
connectors: true,
});
export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false });
export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false });
export const noPushCasesPermissions = () => buildCasesPermissions({ push: false });
Expand All @@ -26,6 +40,7 @@ export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions,
const update = overrides.update ?? true;
const deletePermissions = overrides.delete ?? true;
const push = overrides.push ?? true;
const connectors = overrides.connectors ?? true;
const all = create && read && update && deletePermissions && push;

return {
Expand All @@ -35,6 +50,7 @@ export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions,
update,
delete: deletePermissions,
push,
connectors,
};
};

Expand All @@ -46,13 +62,15 @@ export const noCasesCapabilities = () =>
update_cases: false,
delete_cases: false,
push_cases: false,
cases_connector: false,
});
export const readCasesCapabilities = () =>
buildCasesCapabilities({
create_cases: false,
update_cases: false,
delete_cases: false,
push_cases: false,
cases_connector: true,
});
export const writeCasesCapabilities = () => {
return buildCasesCapabilities({
Expand All @@ -67,5 +85,6 @@ export const buildCasesCapabilities = (overrides?: Partial<CasesCapabilities>) =
update_cases: overrides?.update_cases ?? true,
delete_cases: overrides?.delete_cases ?? true,
push_cases: overrides?.push_cases ?? true,
cases_connector: overrides?.cases_connector ?? true,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import { ConnectorsDropdown } from './connectors_dropdown';
import { connectors, actionTypes } from './__mock__';
import { ConnectorTypes } from '../../../common/types/domain';

const mockUseCasesContext = jest.fn().mockReturnValue({
permissions: {
connectors: true,
},
});

jest.mock('../cases_context/use_cases_context', () => ({
useCasesContext: () => mockUseCasesContext(),
}));

describe('Connectors', () => {
let wrapper: ReactWrapper;
let appMockRender: AppMockRenderer;
Expand Down Expand Up @@ -161,4 +171,18 @@ describe('Connectors', () => {
).toBeInTheDocument();
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
});

it('shows the actions permission message if the user does not have access to case connector', async () => {
mockUseCasesContext.mockReturnValue({
permissions: {
connectors: false,
},
});

const result = appMockRender.render(<Connectors {...props} />);
expect(
result.getByTestId('configure-case-connector-permissions-error-msg')
).toBeInTheDocument();
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ConnectorTypes } from '../../../common/types/domain';
import { DeprecatedCallout } from '../connectors/deprecated_callout';
import { isDeprecatedConnector } from '../utils';
import { useApplicationCapabilities } from '../../common/lib/kibana';
import { useCasesContext } from '../cases_context/use_cases_context';

const EuiFormRowExtended = styled(EuiFormRow)`
.euiFormRow__labelWrapper {
Expand Down Expand Up @@ -63,6 +64,8 @@ const ConnectorsComponent: React.FC<Props> = ({
() => connectors.find((c) => c.id === selectedConnector.id),
[connectors, selectedConnector.id]
);
const { permissions } = useCasesContext();
const hasReadPermissions = permissions.connectors && actions.read;

const connectorsName = connector?.name ?? 'none';

Expand Down Expand Up @@ -105,7 +108,7 @@ const ConnectorsComponent: React.FC<Props> = ({
>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
{actions.read ? (
{hasReadPermissions ? (
<ConnectorsDropdown
connectors={connectors}
disabled={disabled}
Expand Down
26 changes: 26 additions & 0 deletions x-pack/plugins/cases/public/components/create/connector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ jest.mock('../connectors/resilient/use_get_severity');
jest.mock('../connectors/servicenow/use_get_choices');
jest.mock('../../containers/configure/use_configure');

const mockUseCasesContext = jest.fn().mockReturnValue({
permissions: {
connectors: true,
},
});

jest.mock('../cases_context/use_cases_context', () => ({
useCasesContext: () => mockUseCasesContext(),
}));

const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock;
const useGetSeverityMock = useGetSeverity as jest.Mock;
const useGetChoicesMock = useGetChoices as jest.Mock;
Expand Down Expand Up @@ -190,4 +200,20 @@ describe('Connector', () => {
expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument();
expect(result.queryByTestId('caseConnectors')).toBe(null);
});

it('shows the actions permission message if the user does not have access to case connector', async () => {
mockUseCasesContext.mockReturnValue({
permissions: {
connectors: false,
},
});

const result = appMockRender.render(
<MockHookWrapperComponent>
<Connector {...defaultProps} />
</MockHookWrapperComponent>
);
expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument();
expect(result.queryByTestId('caseConnectors')).toBe(null);
});
});
5 changes: 4 additions & 1 deletion x-pack/plugins/cases/public/components/create/connector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useCaseConfigure } from '../../containers/configure/use_configure';
import { getConnectorById, getConnectorsFormValidators } from '../utils';
import { useApplicationCapabilities } from '../../common/lib/kibana';
import * as i18n from '../../common/translations';
import { useCasesContext } from '../cases_context/use_cases_context';

interface Props {
connectors: ActionConnector[];
Expand All @@ -30,6 +31,8 @@ const ConnectorComponent: React.FC<Props> = ({ connectors, isLoading, isLoadingC
const connector = getConnectorById(connectorId, connectors) ?? null;
const { connector: configurationConnector } = useCaseConfigure();
const { actions } = useApplicationCapabilities();
const { permissions } = useCasesContext();
const hasReadPermissions = permissions.connectors && actions.read;

const defaultConnectorId = useMemo(() => {
return connectors.some((c) => c.id === configurationConnector.id)
Expand All @@ -42,7 +45,7 @@ const ConnectorComponent: React.FC<Props> = ({ connectors, isLoading, isLoadingC
connectors,
});

if (!actions.read) {
if (!hasReadPermissions) {
return (
<EuiText data-test-subj="create-case-connector-permissions-error-msg" size="s">
<span>{i18n.READ_ACTIONS_PERMISSIONS_ERROR_MSG}</span>
Expand Down
Loading

0 comments on commit 8b47175

Please sign in to comment.