-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
SavedObjectClient.find filtering #19708
Changes from 8 commits
9da30a1
128008f
4282864
1550797
acba46f
fa8e9a3
d2f2a59
8bad6a3
34ef450
790cd5f
35c9520
ee98045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,10 @@ | |
|
||
import { get, uniq } from 'lodash'; | ||
|
||
const getPrivilege = (type, action) => { | ||
return `action:saved_objects/${type}/${action}`; | ||
}; | ||
|
||
export class SecureSavedObjectsClient { | ||
constructor(options) { | ||
const { | ||
|
@@ -51,11 +55,32 @@ export class SecureSavedObjectsClient { | |
} | ||
|
||
async find(options = {}) { | ||
await this._performAuthorizationCheck(options.type, 'find', { | ||
options, | ||
}); | ||
const action = 'find'; | ||
|
||
return await this._repository.find(options); | ||
// when we have the type or types, it makes our life easy | ||
if (options.type) { | ||
await this._performAuthorizationCheck(options.type, action, { options }); | ||
return await this._repository.find(options); | ||
} | ||
|
||
// otherwise, we have to filter for only their authorized types | ||
const types = this._repository.getTypes(); | ||
const typesToPrivilegesMap = new Map(types.map(type => [type, getPrivilege(type, action)])); | ||
const result = await this._hasSavedObjectPrivileges(Array.from(typesToPrivilegesMap.values())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: it's not immediately clear what this variable represents when using it in subsequent lines |
||
const authorizedTypes = Array.from(typesToPrivilegesMap.entries()) | ||
.filter(([ , privilege]) => !result.missing.includes(privilege)) | ||
.map(([type]) => type); | ||
|
||
if (authorizedTypes.length === 0) { | ||
this._auditLogger.savedObjectsAuthorizationFailure(result.username, action, types, result.missing, { options }); | ||
throw this.errors.decorateForbiddenError(new Error(`Not authorized to find any types`)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: This error message could lead someone to believe that a user was searching for a saved object type, rather than saved objects of a particular type/types |
||
} | ||
this._auditLogger.savedObjectsAuthorizationSuccess(result.username, action, authorizedTypes, { options }); | ||
|
||
return await this._repository.find({ | ||
...options, | ||
type: authorizedTypes | ||
}); | ||
} | ||
|
||
async bulkGet(objects = []) { | ||
|
@@ -89,15 +114,8 @@ export class SecureSavedObjectsClient { | |
|
||
async _performAuthorizationCheck(typeOrTypes, action, args) { | ||
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; | ||
const actions = types.map(type => `action:saved_objects/${type}/${action}`); | ||
|
||
let result; | ||
try { | ||
result = await this._hasPrivileges(actions); | ||
} catch(error) { | ||
const { reason } = get(error, 'body.error', {}); | ||
throw this.errors.decorateGeneralError(error, reason); | ||
} | ||
const privileges = types.map(type => getPrivilege(type, action)); | ||
const result = await this._hasSavedObjectPrivileges(privileges); | ||
|
||
if (result.success) { | ||
this._auditLogger.savedObjectsAuthorizationSuccess(result.username, action, types, args); | ||
|
@@ -107,4 +125,13 @@ export class SecureSavedObjectsClient { | |
throw this.errors.decorateForbiddenError(new Error(msg)); | ||
} | ||
} | ||
|
||
async _hasSavedObjectPrivileges(privileges) { | ||
try { | ||
return await this._hasPrivileges(privileges); | ||
} catch(error) { | ||
const { reason } = get(error, 'body.error', {}); | ||
throw this.errors.decorateGeneralError(error, reason); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is a pretty trivial wrapper around a well-tested function, but what do you think about adding a test or two for this function, since it is part of the Repository's public interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some basic tests here, I've been going back and forth where this should "live" as I also want to use it https:/elastic/kibana/blob/rbac-phase-1/x-pack/plugins/security/server/lib/privileges/privileges.js#L48 but we don't have access to the Repository there, nor do we need to access the actual saved objects themselves.
I've been pondering whether we should createa "kibana mappings service" that uses the mappings defined in src/server/mappings applied against the mappings that we've discovered via the various plugins, but I've been postponing doing so...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tests! I think a mappings service would absolutely be worthwhile, but I don't know that we need to work on that as part of RBAC Phase 1