Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps][Cases] User suggestion API #137346

Merged

Conversation

jonathan-buttner
Copy link
Contributor

@jonathan-buttner jonathan-buttner commented Jul 27, 2022

This PR adds the internal user profiles suggestion API to the cases backend. This API is mostly a pass through to the security plugin's API. It does construct the required privileges that a user profile must meet to be a valid result. I have this set to cases update and cases read (these are specific to cases, not comments etc). My thought process was that an assigned user should be able to change the status of a case (close it) and read it at a minimum.

It also adds the api ['casesSuggestAssignees'] tags to Stack Cases, Security Solution, and Observability.

@jonathan-buttner jonathan-buttner added release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Feature:Cases Cases feature v8.5.0 labels Jul 27, 2022
supertest: SuperTest.SuperTest<SuperTest.Test>;
users?: User[];
}) => {
for (const user of users) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be done in a Promise.all because the order needs to be consistent for the results returned from the suggest API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add more context why it works like that: by design suggest API, everything else being equal, is supposed to rank users who logged in recently higher than those who didn't login for a while.

@jonathan-buttner jonathan-buttner marked this pull request as ready for review July 29, 2022 14:38
@jonathan-buttner jonathan-buttner requested review from a team as code owners July 29, 2022 14:38
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops-cases (Feature:Cases)

@jonathan-buttner jonathan-buttner requested review from azasypkin, cnasikas and a team July 29, 2022 15:12
Copy link
Member

@cnasikas cnasikas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR! Thanks! 🚀

Copy link
Contributor

@michaelolo24 michaelolo24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security solution changes! LGTM!

@jonathan-buttner
Copy link
Contributor Author

@elasticmachine merge upstream

@jonathan-buttner
Copy link
Contributor Author

@elasticmachine merge upstream

@azasypkin
Copy link
Member

ACK: will review PR tomorrow!

@jonathan-buttner
Copy link
Contributor Author

@elasticmachine merge upstream

Copy link
Member

@azasypkin azasypkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks great! Just a few nits and questions.

name: rt.string,
owners: rt.array(rt.string),
}),
rt.partial({ size: rt.number }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would you mind limiting size here? The suggest API will throw if you pass size greater than 100 (to prevent DDoS since privileges check can be expensive) and your endpoint might end up throwing 500 Internal Server Error, but it's better to have controlled 400 Bad Request for this case (assuming io-ts validation results to 400 like built-in kbn-config-schema).

const { spaces } = this.options;

const securityPluginFields = {
securityPluginSetup: this.options.securityPluginSetup,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to myself: we need to expose license on the start contract too so that you can rely just on a single contract.

Comment on lines +26 to +27
securityPluginSetup?: SecurityPluginSetup;
securityPluginStart?: SecurityPluginStart;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I remember you were considering making security as a required dependency, is there any reason you decided not to do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I found that the security plugin can still be disabled. The functionality to disable it was moved into Elasticsearch. When we initially developed the cases RBAC we intentionally allowed the security plugin to be disabled. It was something that Larry suggested we support. So I didn't want to deviate from that in this PR. I think some users want to play around with Kibana without security enable and it'd be nice for them to still have access to cases in that situation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I found that the security plugin can still be disabled. The functionality to disable it was moved into Elasticsearch.

Yeah, but you'll still get security contract even if Security is disabled in Elasticsearch and can replace securityPluginStart?: SecurityPluginStart; with securityPluginStart: SecurityPluginStart; to have less "undefined" checks (since you need to rely on the availabilty of the security feature via license check anyway). But it's minor, just wanted to point this out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, good to know. I'll create an issue to fix that up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.options = options;
}

public async suggest(request: KibanaRequest): ReturnType<UserProfileServiceStart['suggest']> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be a little bit more readable (requires import type { UserProfile } from '@kbn/security-plugin/common';)

Suggested change
public async suggest(request: KibanaRequest): ReturnType<UserProfileServiceStart['suggest']> {
public async suggest(request: KibanaRequest): Promise<UserProfile[]> {

Alternatively you can change line 65 to return [] as UserProfile[]; and remove return type signature completely since it can be inferred automatically, up to you.

Comment on lines 84 to 86
message: `Failed to retrieve suggested user profiles in service for name: ${name} owners: [${owners.join(
','
)}]`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional nit: join(',') is actually done automatically when array is used in the template string, we usually use join explicitly only if we want another separator (e.g. comma + space).

Suggested change
message: `Failed to retrieve suggested user profiles in service for name: ${name} owners: [${owners.join(
','
)}]`,
message: `Failed to retrieve suggested user profiles in service for name: ${name} owners: [${owners}]`,

);
}

private static buildRequiredPrivileges(owners: string[], security: SecurityPluginStart) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: would you mind adding a JSDoc here explaining what privileges exactly we'd like potential assignees to have and why (who\what are these owners and why having only getCase isn't enough to be assigned to the case)? It'd help other Cases-noobs like me to quickly understand the "privilege model" here 🙂

secAllUser,
} from './common/users';

export default ({ getService }: FtrProviderContext): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏅 Thanks for adding all these API integration tests!

Comment on lines 143 to 144
// TODO: unskip when security plugin implements fix for size issue
it.skip('limits the results to one, when the size is specified as one', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should be fixed now!

Suggested change
// TODO: unskip when security plugin implements fix for size issue
it.skip('limits the results to one, when the size is specified as one', async () => {
it('limits the results to one, when the size is specified as one', async () => {

supertest: SuperTest.SuperTest<SuperTest.Test>;
users?: User[];
}) => {
for (const user of users) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add more context why it works like that: by design suggest API, everything else being equal, is supposed to rank users who logged in recently higher than those who didn't login for a while.

@cnasikas cnasikas mentioned this pull request Aug 2, 2022
2 tasks
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
cases 492 493 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
cases 122.4KB 122.5KB +94.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@jonathan-buttner jonathan-buttner merged commit 5861f67 into elastic:main Aug 2, 2022
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Aug 2, 2022
@jonathan-buttner jonathan-buttner deleted the cases-user-suggestions-be branch August 2, 2022 15:47
@cnasikas cnasikas mentioned this pull request Sep 6, 2022
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting Feature:Cases Cases feature release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v8.5.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants