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

SavedObjectClient.find filtering #19708

Merged
merged 12 commits into from
Jun 7, 2018
6 changes: 5 additions & 1 deletion src/server/saved_objects/service/lib/repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import uuid from 'uuid';

import { getRootType } from '../../../mappings';
import { getRootType, getRootPropertiesObjects } from '../../../mappings';
import { getSearchDsl } from './search_dsl';
import { trimIdPrefix } from './trim_id_prefix';
import { includedFields } from './included_fields';
Expand Down Expand Up @@ -406,6 +406,10 @@ export class SavedObjectsRepository {
};
}

getTypes() {
Copy link
Member

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?

Copy link
Contributor Author

@kobelb kobelb Jun 7, 2018

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://github.com/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...

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 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

return Object.keys(getRootPropertiesObjects(this._mappings));
}

async _writeToCluster(method, params) {
try {
await this._onBeforeWrite();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()));
Copy link
Member

Choose a reason for hiding this comment

The 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`));
Copy link
Member

Choose a reason for hiding this comment

The 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 = []) {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
}
Loading