Skip to content

Commit

Permalink
[Cases] RBAC (#95058)
Browse files Browse the repository at this point in the history
* Adding feature flag for auth

* Hiding SOs and adding consumer field

* First pass at adding security changes

* Consumer as the app's plugin ID

* Create addConsumerToSO migration helper

* Fix mapping's SO consumer

* Add test for CasesActions

* Declare hidden types on SO client

* Restructure integration tests

* Init spaces_only integration tests

* Implementing the cases security string

* Adding security plugin tests for cases

* Rough concept for authorization class

* Adding comments

* Fix merge

* Get requiredPrivileges for classes

* Check privillages

* Ensure that all classes are available

* Success if hasAllRequested is true

* Failure if hasAllRequested is false

* Adding schema updates for feature plugin

* Seperate basic from trial

* Enable SIR on integration tests

* Starting the plumbing for authorization in plugin

* Unit tests working

* Move find route logic to case client

* Create integration test helper functions

* Adding auth to create call

* Create getClassFilter helper

* Add class attribute to find request

* Create getFindAuthorizationFilter

* Ensure savedObject is authorized in find method

* Include fields for authorization

* Combine authorization filter with cases & subcases filter

* Fix isAuthorized flag

* Fix merge issue

* Create/delete spaces & users before and after tests

* Add more user and roles

* [Cases] Convert filters from strings to KueryNode (#95288)

* [Cases] RBAC: Rename class to scope (#95535)

* [Cases][RBAC] Rename scope to owner (#96035)

* [Cases] RBAC: Create & Find integration tests (#95511)

* [Cases] Cases client enchantment (#95923)

* [Cases] Authorization and Client Audit Logger (#95477)

* Starting audit logger

* Finishing auth audit logger

* Fixing tests and types

* Adding audit event creator

* Renaming class to scope

* Adding audit logger messages to create and find

* Adding comments and fixing import issue

* Fixing type errors

* Fixing tests and adding username to message

* Addressing PR feedback

* Removing unneccessary log and generating id

* Fixing module issue and remove expect.anything

* [Cases] Migrate sub cases routes to a client (#96461)

* Adding sub cases client

* Move sub case routes to case client

* Throw when attempting to access the sub cases client

* Fixing throw and removing user ans soclients

* [Cases] RBAC: Migrate routes' unit tests to integration tests (#96374)

Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>

* [Cases] Move remaining HTTP functionality to client (#96507)

* Moving deletes and find for attachments

* Moving rest of comment apis

* Migrating configuration routes to client

* Finished moving routes, starting utils refactor

* Refactoring utilites and fixing integration tests

* Addressing PR feedback

* Fixing mocks and types

* Fixing integration tests

* Renaming status_stats

* Fixing test type errors

* Adding plugins to kibana.json

* Adding cases to required plugin

* [Cases] Refactoring authorization (#97483)

* Refactoring authorization

* Wrapping auth calls in helper for try catch

* Reverting name change

* Hardcoding the saved object types

* Switching ensure to owner array

* [Cases] Add authorization to configuration & cases routes (#97228)

* [Cases] Attachments RBAC (#97756)

* Starting rbac for comments

* Adding authorization to rest of comment apis

* Starting the comment rbac tests

* Fixing some of the rbac tests

* Adding some integration tests

* Starting patch tests

* Working tests for comments

* Working tests

* Fixing some tests

* Fixing type issues from pulling in master

* Fixing connector tests that only work in trial license

* Attempting to fix cypress

* Mock return of array for configure

* Fixing cypress test

* Cleaning up

* Addressing PR comments

* Reducing operations

* [Cases] Add RBAC to remaining Cases APIs (#98762)

* Starting rbac for comments

* Adding authorization to rest of comment apis

* Starting the comment rbac tests

* Fixing some of the rbac tests

* Adding some integration tests

* Starting patch tests

* Working tests for comments

* Working tests

* Fixing some tests

* Fixing type issues from pulling in master

* Fixing connector tests that only work in trial license

* Attempting to fix cypress

* Mock return of array for configure

* Fixing cypress test

* Cleaning up

* Working case update tests

* Addressing PR comments

* Reducing operations

* Working rbac push case tests

* Starting stats apis

* Working status tests

* User action tests and fixing migration errors

* Fixing type errors

* including error in message

* Addressing pr feedback

* Fixing some type errors

* [Cases] Add space only tests (#99409)

* Starting spaces tests

* Finishing space only tests

* Refactoring createCaseWithConnector

* Fixing spelling

* Addressing PR feedback and creating alert tests

* Fixing mocks

* [Cases] Add security only tests (#99679)

* Starting spaces tests

* Finishing space only tests

* Refactoring createCaseWithConnector

* Fixing spelling

* Addressing PR feedback and creating alert tests

* Fixing mocks

* Starting security only tests

* Adding remainder security only tests

* Using helper objects

* Fixing type error for null space

* Renaming utility variables

* Refactoring users and roles for security only tests

* Adding sub feature

* [Cases] Cleaning up the services and TODOs (#99723)

* Cleaning up the service intialization

* Fixing type errors

* Adding comments for the api

* Working test for cases client

* Fix type error

* Adding generated docs

* Adding more docs and cleaning up types

* Cleaning up readme

* More clean up and links

* Changing some file names

* Renaming docs

* Integration tests for cases privs and fixes (#100038)

* [Cases] RBAC on UI (#99478)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* Fixing case ids by alert id route call

* [Cases] Fixing UI feature permissions and adding UI tests (#100074)

* Integration tests for cases privs and fixes

* Fixing ui cases permissions and adding tests

* Adding test for collection failure and fixing jest

* Renaming variables

* Fixing type error

* Adding some comments

* Validate cases features

* Fix new schema

* Adding owner param for the status stats

* Fix get case status tests

* Adjusting permissions text and fixing status

* Address PR feedback

* Adding top level feature back

* Fixing feature privileges

* Renaming

* Removing uneeded else

* Fixing tests and adding cases merge tests

* [Cases][Security Solution] Basic license security solution API tests (#100925)

* Cleaning up the fixture plugins

* Adding basic feature test

* renaming to unsecuredSavedObjectsClient (#101215)

* [Cases] RBAC Refactoring audit logging (#100952)

* Refactoring audit logging

* Adding unit tests for authorization classes

* Addressing feedback and adding util tests

* return undefined on empty array

* fixing eslint

* [Cases] Cleaning up RBAC integration tests (#101324)

* Adding tests for space permissions

* Adding tests for testing a disable feature

Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 7, 2021
1 parent 999faec commit b6c982c
Show file tree
Hide file tree
Showing 487 changed files with 33,101 additions and 17,275 deletions.
6 changes: 5 additions & 1 deletion x-pack/plugins/cases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ Case management in Kibana
## Table of Contents

- [Cases API](#cases-api)
- [Cases Client API](#cases-client-api)
- [Cases UI](#cases-ui)
- [Case Action Type](#case-action-type) _feature in development, disabled by default_


## Cases API
[**Explore the API docs »**](https://www.elastic.co/guide/en/security/current/cases-api-overview.html)

## Cases Client API
[**Cases Client API docs**][cases-client-api-docs]

## Cases UI

#### Embed Cases UI components in any Kibana plugin
Expand Down Expand Up @@ -263,4 +267,4 @@ For IBM Resilient connectors:
[all-cases-modal-img]: images/all_cases_selector_modal.png
[recent-cases-img]: images/recent_cases.png
[case-view-img]: images/case_view.png

[cases-client-api-docs]: docs/cases_client/cases_client_api.md
121 changes: 119 additions & 2 deletions x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,38 @@ const SettingsRt = rt.type({
});

const CaseBasicRt = rt.type({
/**
* The description of the case
*/
description: rt.string,
/**
* The current status of the case (open, closed, in-progress)
*/
status: CaseStatusRt,
/**
* The identifying strings for filter a case
*/
tags: rt.array(rt.string),
/**
* The title of a case
*/
title: rt.string,
/**
* The type of a case (individual or collection)
*/
[caseTypeField]: CaseTypeRt,
/**
* The external system that the case can be synced with
*/
connector: CaseConnectorRt,
/**
* The alert sync settings
*/
settings: SettingsRt,
/**
* The plugin owner of the case
*/
owner: rt.string,
});

const CaseExternalServiceBasicRt = rt.type({
Expand Down Expand Up @@ -73,11 +98,31 @@ export const CaseAttributesRt = rt.intersection([
]);

const CasePostRequestNoTypeRt = rt.type({
/**
* Description of the case
*/
description: rt.string,
/**
* Identifiers for the case.
*/
tags: rt.array(rt.string),
/**
* Title of the case
*/
title: rt.string,
/**
* The external configuration for the case
*/
connector: CaseConnectorRt,
/**
* Sync settings for alerts
*/
settings: SettingsRt,
/**
* The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
* creating this case must also be granted access to that plugin's feature.
*/
owner: rt.string,
});

/**
Expand All @@ -95,23 +140,78 @@ export const CasesClientPostRequestRt = rt.type({
* has all the necessary fields. CasesClientPostRequestRt is used for validation.
*/
export const CasePostRequestRt = rt.intersection([
rt.partial({ type: CaseTypeRt }),
/**
* The case type: an individual case (one without children) or a collection case (one with children)
*/
rt.partial({ [caseTypeField]: CaseTypeRt }),
CasePostRequestNoTypeRt,
]);

export const CasesFindRequestRt = rt.partial({
/**
* Type of a case (individual, or collection)
*/
type: CaseTypeRt,
/**
* Tags to filter by
*/
tags: rt.union([rt.array(rt.string), rt.string]),
/**
* The status of the case (open, closed, in-progress)
*/
status: CaseStatusRt,
/**
* The reporters to filter by
*/
reporters: rt.union([rt.array(rt.string), rt.string]),
/**
* Operator to use for the `search` field
*/
defaultSearchOperator: rt.union([rt.literal('AND'), rt.literal('OR')]),
/**
* The fields in the entity to return in the response
*/
fields: rt.array(rt.string),
/**
* The page of objects to return
*/
page: NumberFromString,
/**
* The number of objects to include in each page
*/
perPage: NumberFromString,
/**
* An Elasticsearch simple_query_string
*/
search: rt.string,
searchFields: rt.array(rt.string),
/**
* The fields to perform the simple_query_string parsed query against
*/
searchFields: rt.union([rt.array(rt.string), rt.string]),
/**
* The field to use for sorting the found objects.
*
* This only supports, `create_at`, `closed_at`, and `status`
*/
sortField: rt.string,
/**
* The order to sort by
*/
sortOrder: rt.union([rt.literal('desc'), rt.literal('asc')]),
/**
* The owner(s) to filter by. The user making the request must have privileges to retrieve cases of that
* ownership or they will be ignored. If no owner is included, then all ownership types will be included in the response
* that the user has access to.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
});

export const CasesByAlertIDRequestRt = rt.partial({
/**
* The type of cases to retrieve given an alert ID. If no owner is provided, all cases
* that the user has access to will be returned.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
});

export const CaseResponseRt = rt.intersection([
Expand Down Expand Up @@ -141,6 +241,9 @@ export const CasesFindResponseRt = rt.intersection([

export const CasePatchRequestRt = rt.intersection([
rt.partial(CaseBasicRt.props),
/**
* The saved object ID and version
*/
rt.type({ id: rt.string, version: rt.string }),
]);

Expand Down Expand Up @@ -172,6 +275,16 @@ export const ExternalServiceResponseRt = rt.intersection([
}),
]);

export const AllTagsFindRequestRt = rt.partial({
/**
* The owner of the cases to retrieve the tags from. If no owner is provided the tags from all cases
* that the user has access to will be returned.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
});

export const AllReportersFindRequestRt = AllTagsFindRequestRt;

export type CaseAttributes = rt.TypeOf<typeof CaseAttributesRt>;
/**
* This field differs from the CasePostRequest in that the post request's type field can be optional. This type requires
Expand All @@ -183,6 +296,7 @@ export type CasePostRequest = rt.TypeOf<typeof CasePostRequestRt>;
export type CaseResponse = rt.TypeOf<typeof CaseResponseRt>;
export type CasesResponse = rt.TypeOf<typeof CasesResponseRt>;
export type CasesFindRequest = rt.TypeOf<typeof CasesFindRequestRt>;
export type CasesByAlertIDRequest = rt.TypeOf<typeof CasesByAlertIDRequestRt>;
export type CasesFindResponse = rt.TypeOf<typeof CasesFindResponseRt>;
export type CasePatchRequest = rt.TypeOf<typeof CasePatchRequestRt>;
export type CasesPatchRequest = rt.TypeOf<typeof CasesPatchRequestRt>;
Expand All @@ -194,3 +308,6 @@ export type ESCaseAttributes = Omit<CaseAttributes, 'connector'> & { connector:
export type ESCasePatchRequest = Omit<CasePatchRequest, 'connector'> & {
connector?: ESCaseConnector;
};

export type AllTagsFindRequest = rt.TypeOf<typeof AllTagsFindRequestRt>;
export type AllReportersFindRequest = AllTagsFindRequest;
14 changes: 14 additions & 0 deletions x-pack/plugins/cases/common/api/cases/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import * as rt from 'io-ts';
import { SavedObjectFindOptionsRt } from '../saved_object';

import { UserRT } from '../user';

Expand Down Expand Up @@ -42,6 +43,7 @@ export const CommentAttributesBasicRt = rt.type({
]),
created_at: rt.string,
created_by: UserRT,
owner: rt.string,
pushed_at: rt.union([rt.string, rt.null]),
pushed_by: rt.union([UserRT, rt.null]),
updated_at: rt.union([rt.string, rt.null]),
Expand All @@ -57,6 +59,7 @@ export enum CommentType {
export const ContextTypeUserRt = rt.type({
comment: rt.string,
type: rt.literal(CommentType.user),
owner: rt.string,
});

/**
Expand All @@ -72,6 +75,7 @@ export const AlertCommentRequestRt = rt.type({
id: rt.union([rt.string, rt.null]),
name: rt.union([rt.string, rt.null]),
}),
owner: rt.string,
});

const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]);
Expand Down Expand Up @@ -127,7 +131,17 @@ export const CommentsResponseRt = rt.type({

export const AllCommentsResponseRt = rt.array(CommentResponseRt);

export const FindQueryParamsRt = rt.partial({
...SavedObjectFindOptionsRt.props,
/**
* If specified the attachments found will be associated to a sub case instead of a case object
*/
subCaseId: rt.string,
});

export type FindQueryParams = rt.TypeOf<typeof FindQueryParamsRt>;
export type AttributesTypeAlerts = rt.TypeOf<typeof AttributesTypeAlertsRt>;
export type AttributesTypeUser = rt.TypeOf<typeof AttributesTypeUserRt>;
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
export type CommentRequest = rt.TypeOf<typeof CommentRequestRt>;
export type CommentResponse = rt.TypeOf<typeof CommentResponseRt>;
Expand Down
37 changes: 36 additions & 1 deletion x-pack/plugins/cases/common/api/cases/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,34 @@ import * as rt from 'io-ts';

import { UserRT } from '../user';
import { CaseConnectorRt, ConnectorMappingsRt, ESCaseConnector } from '../connectors';
import { OmitProp } from '../runtime_types';
import { OWNER_FIELD } from './constants';

// TODO: we will need to add this type rt.literal('close-by-third-party')
const ClosureTypeRT = rt.union([rt.literal('close-by-user'), rt.literal('close-by-pushing')]);

const CasesConfigureBasicRt = rt.type({
/**
* The external connector
*/
connector: CaseConnectorRt,
/**
* Whether to close the case after it has been synced with the external system
*/
closure_type: ClosureTypeRT,
/**
* The plugin owner that manages this configuration
*/
owner: rt.string,
});

const CasesConfigureBasicWithoutOwnerRt = rt.type(
OmitProp(CasesConfigureBasicRt.props, OWNER_FIELD)
);

export const CasesConfigureRequestRt = CasesConfigureBasicRt;
export const CasesConfigurePatchRt = rt.intersection([
rt.partial(CasesConfigureBasicRt.props),
rt.partial(CasesConfigureBasicWithoutOwnerRt.props),
rt.type({ version: rt.string }),
]);

Expand All @@ -38,18 +54,37 @@ export const CaseConfigureResponseRt = rt.intersection([
CaseConfigureAttributesRt,
ConnectorMappingsRt,
rt.type({
id: rt.string,
version: rt.string,
error: rt.union([rt.string, rt.null]),
owner: rt.string,
}),
]);

export const GetConfigureFindRequestRt = rt.partial({
/**
* The configuration plugin owner to filter the search by. If this is left empty the results will include all configurations
* that the user has permissions to access
*/
owner: rt.union([rt.array(rt.string), rt.string]),
});

export const CaseConfigureRequestParamsRt = rt.type({
configuration_id: rt.string,
});

export const CaseConfigurationsResponseRt = rt.array(CaseConfigureResponseRt);

export type ClosureType = rt.TypeOf<typeof ClosureTypeRT>;
export type CasesConfigure = rt.TypeOf<typeof CasesConfigureBasicRt>;
export type CasesConfigureRequest = rt.TypeOf<typeof CasesConfigureRequestRt>;
export type CasesConfigurePatch = rt.TypeOf<typeof CasesConfigurePatchRt>;
export type CasesConfigureAttributes = rt.TypeOf<typeof CaseConfigureAttributesRt>;
export type CasesConfigureResponse = rt.TypeOf<typeof CaseConfigureResponseRt>;
export type CasesConfigurationsResponse = rt.TypeOf<typeof CaseConfigurationsResponseRt>;

export type ESCasesConfigureAttributes = Omit<CasesConfigureAttributes, 'connector'> & {
connector: ESCaseConnector;
};

export type GetConfigureFindRequest = rt.TypeOf<typeof GetConfigureFindRequestRt>;
36 changes: 36 additions & 0 deletions x-pack/plugins/cases/common/api/cases/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

/**
* This field is used for authorization of the entities within the cases plugin. Each entity within Cases will have the owner field
* set to a string that represents the plugin that "owns" (i.e. the plugin that originally issued the POST request to
* create the entity) the entity.
*
* The Authorization class constructs a string composed of the operation being performed (createCase, getComment, etc),
* and the owner of the entity being acted upon or created. This string is then given to the Security plugin which
* checks to see if the user making the request has that particular string stored within it's privileges. If it does,
* then the operation succeeds, otherwise the operation fails.
*
* APIs that create/update an entity require that the owner field be passed in the body of the request.
* APIs that search for entities typically require that the owner be passed as a query parameter.
* APIs that specify an ID of an entity directly generally don't need to specify the owner field.
*
* For APIs that create/update an entity, the RBAC implementation checks to see if the user making the request has the
* correct privileges for performing that action (a create/update) for the specified owner.
* This check is done through the Security plugin's API.
*
* For APIs that search for entities, the RBAC implementation creates a filter for the saved objects query that limits
* the search to only owners that the user has access to. We also check that the objects returned by the saved objects
* API have the limited owner scope. If we find one that the user does not have permissions for, we throw a 403 error.
* The owner field that is passed in as a query parameter can be used to further limit the results. If a user attempts
* to pass an owner that they do not have access to, the owner is ignored.
*
* For APIs that retrieve/delete entities directly using their ID, the RBAC implementation requests the object first,
* and then checks to see if the user making the request has access to that operation and owner. If the user does, the
* operation continues, otherwise we throw a 403.
*/
export const OWNER_FIELD = 'owner';
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/api/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './comment';
export * from './status';
export * from './user_actions';
export * from './sub_case';
export * from './constants';
9 changes: 9 additions & 0 deletions x-pack/plugins/cases/common/api/cases/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ export const CasesStatusResponseRt = rt.type({
count_closed_cases: rt.number,
});

export const CasesStatusRequestRt = rt.partial({
/**
* The owner of the cases to retrieve the status stats from. If no owner is provided the stats for all cases
* that the user has access to will be returned.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
});

export type CasesStatusResponse = rt.TypeOf<typeof CasesStatusResponseRt>;
export type CasesStatusRequest = rt.TypeOf<typeof CasesStatusRequestRt>;
Loading

0 comments on commit b6c982c

Please sign in to comment.