Skip to content

Commit

Permalink
Dynamic types (#19925)
Browse files Browse the repository at this point in the history
No more hard-coded types! This will make it so that plugins that register their own mappings just transparently work.
  • Loading branch information
kobelb authored and legrego committed Jun 15, 2018
1 parent e02c5bb commit 9cdf641
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import { getRootPropertiesObjects } from '../../mappings';
import { SavedObjectsRepository, ScopedSavedObjectsClientProvider, SavedObjectsRepositoryProvider } from './lib';
import { SavedObjectsClient } from './saved_objects_client';

Expand Down Expand Up @@ -58,15 +59,16 @@ export function createSavedObjectsService(server) {
}
};

const mappings = server.getKibanaIndexMappingsDsl();
const repositoryProvider = new SavedObjectsRepositoryProvider({
index: server.config().get('kibana.index'),
mappings: server.getKibanaIndexMappingsDsl(),
mappings,
onBeforeWrite,
});

const scopedClientProvider = new ScopedSavedObjectsClientProvider({
index: server.config().get('kibana.index'),
mappings: server.getKibanaIndexMappingsDsl(),
mappings,
onBeforeWrite,
defaultClientFactory({
request,
Expand All @@ -81,6 +83,7 @@ export function createSavedObjectsService(server) {
});

return {
types: Object.keys(getRootPropertiesObjects(mappings)),
SavedObjectsClient,
SavedObjectsRepository,
getSavedObjectsRepository: (...args) =>
Expand Down
6 changes: 1 addition & 5 deletions 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, getRootPropertiesObjects } from '../../../mappings';
import { getRootType } from '../../../mappings';
import { getSearchDsl } from './search_dsl';
import { trimIdPrefix } from './trim_id_prefix';
import { includedFields } from './included_fields';
Expand Down Expand Up @@ -406,10 +406,6 @@ export class SavedObjectsRepository {
};
}

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

async _writeToCluster(method, params) {
try {
await this._onBeforeWrite();
Expand Down
77 changes: 0 additions & 77 deletions src/server/saved_objects/service/lib/repository.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,83 +632,6 @@ describe('SavedObjectsRepository', () => {
});
});

describe('#getTypes', () => {
it(`returns no types if mappings have no types`, () => {
const mappings = {
doc: {
properties: {
'updated_at': {
type: 'date'
},
}
}
};

savedObjectsRepository = new SavedObjectsRepository({
index: '.kibana-test',
mappings,
callCluster: callAdminCluster,
onBeforeWrite
});

const types = savedObjectsRepository.getTypes();
expect(types).toEqual([]);
});

it(`returns single type defined in mappings`, () => {
const mappings = {
doc: {
properties: {
'updated_at': {
type: 'date'
},
'index-pattern': {
properties: {}
}
}
}
};

savedObjectsRepository = new SavedObjectsRepository({
index: '.kibana-test',
mappings,
callCluster: callAdminCluster,
onBeforeWrite
});

const types = savedObjectsRepository.getTypes();
expect(types).toEqual(['index-pattern']);
});

it(`returns multiple types defined in mappings`, () => {
const mappings = {
doc: {
properties: {
'updated_at': {
type: 'date'
},
'index-pattern': {
properties: {}
},
'visualization': {
properties: {}
},
}
}
};

savedObjectsRepository = new SavedObjectsRepository({
index: '.kibana-test',
mappings,
callCluster: callAdminCluster,
onBeforeWrite
});

const types = savedObjectsRepository.getTypes();
expect(types).toEqual(['index-pattern', 'visualization']);
});
});

describe('onBeforeWrite', () => {
it('blocks calls to callCluster of requests', async () => {
onBeforeWrite.returns(delay(500));
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const security = (kibana) => new kibana.Plugin({
errors: savedObjects.SavedObjectsClient.errors,
hasPrivileges,
auditLogger,
savedObjectTypes: savedObjects.types,
});
});

Expand Down
15 changes: 13 additions & 2 deletions x-pack/plugins/security/server/lib/authorization/has_privileges.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ const hasApplicationPrivileges = async (callWithRequest, request, kibanaVersion,
};
};

const hasLegacyPrivileges = async (deprecationLogger, callWithRequest, request, kibanaVersion, application, kibanaIndex, privileges) => {
const hasLegacyPrivileges = async (
savedObjectTypes,
deprecationLogger,
callWithRequest,
request,
kibanaVersion,
application,
kibanaIndex,
privileges
) => {
const privilegeCheck = await callWithRequest(request, 'shield.hasPrivileges', {
body: {
index: [{
Expand Down Expand Up @@ -72,7 +81,7 @@ const hasLegacyPrivileges = async (deprecationLogger, callWithRequest, request,
// if they have the read privilege, then we only grant them the read actions
if (privilegeCheck.index[kibanaIndex].read) {
logDeprecation();
const privilegeMap = buildPrivilegeMap(application, kibanaVersion);
const privilegeMap = buildPrivilegeMap(savedObjectTypes, application, kibanaVersion);
const implicitPrivileges = createPrivileges(name => privilegeMap.read.actions.includes(name));

return {
Expand All @@ -96,6 +105,7 @@ export function hasPrivilegesWithServer(server) {
const kibanaVersion = config.get('pkg.version');
const application = config.get('xpack.security.rbac.application');
const kibanaIndex = config.get('kibana.index');
const savedObjectTypes = server.savedObjects.types;
const deprecationLogger = (msg) => server.log(['warning', 'deprecated', 'security'], msg);

return function hasPrivilegesWithRequest(request) {
Expand All @@ -114,6 +124,7 @@ export function hasPrivilegesWithServer(server) {

if (!privilegesCheck.privileges[loginPrivilege]) {
privilegesCheck = await hasLegacyPrivileges(
savedObjectTypes,
deprecationLogger,
callWithRequest,
request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jest.mock('../../../../../server/lib/get_client_shield', () => ({
const defaultKibanaIndex = 'default-kibana-index';
const defaultVersion = 'default-version';
const defaultApplication = 'default-application';
const savedObjectTypes = ['foo-type', 'bar-type'];

const createMockServer = ({ settings = {} } = {}) => {
const mockServer = {
Expand All @@ -35,6 +36,10 @@ const createMockServer = ({ settings = {} } = {}) => {
return key in settings ? settings[key] : defaultSettings[key];
});

mockServer.savedObjects = {
types: savedObjectTypes
};

return mockServer;
};

Expand Down Expand Up @@ -82,7 +87,7 @@ const expectDeprecationLogged = (mockServer) => {
};

test(`returns success of true if they have all application privileges`, async () => {
const privilege = 'action:saved_objects/config/get';
const privilege = `action:saved_objects/${savedObjectTypes[0]}/get`;
const username = 'foo-username';
const mockServer = createMockServer();
const mockCallWithRequest = createMockCallWithRequest([
Expand Down Expand Up @@ -124,8 +129,8 @@ test(`returns success of true if they have all application privileges`, async ()
});

test(`returns success of false if they have only one application privilege`, async () => {
const privilege1 = 'action:saved_objects/config/get';
const privilege2 = 'action:saved_objects/config/create';
const privilege1 = `action:saved_objects/${savedObjectTypes[0]}/get`;
const privilege2 = `action:saved_objects/${savedObjectTypes[0]}/create`;
const username = 'foo-username';
const mockServer = createMockServer();
const mockCallWithRequest = createMockCallWithRequest([
Expand Down Expand Up @@ -168,7 +173,7 @@ test(`returns success of false if they have only one application privilege`, asy
});

test(`throws error if missing version privilege and has login privilege`, async () => {
const privilege = 'action:saved_objects/config/get';
const privilege = `action:saved_objects/${savedObjectTypes[0]}/get`;
const mockServer = createMockServer();
createMockCallWithRequest([
mockApplicationPrivilegeResponse({
Expand All @@ -189,7 +194,7 @@ test(`throws error if missing version privilege and has login privilege`, async
});

test(`uses application privileges if the user has the login privilege`, async () => {
const privilege = 'action:saved_objects/config/get';
const privilege = `action:saved_objects/${savedObjectTypes[0]}/get`;
const username = 'foo-username';
const mockServer = createMockServer();
const callWithRequest = createMockCallWithRequest([
Expand Down Expand Up @@ -230,7 +235,7 @@ test(`uses application privileges if the user has the login privilege`, async ()
});

test(`returns success of false using application privileges if the user has the login privilege`, async () => {
const privilege = 'action:saved_objects/config/get';
const privilege = `action:saved_objects/${savedObjectTypes[0]}/get`;
const username = 'foo-username';
const mockServer = createMockServer();
const callWithRequest = createMockCallWithRequest([
Expand Down Expand Up @@ -272,7 +277,7 @@ test(`returns success of false using application privileges if the user has the

describe('legacy fallback with no application privileges', () => {
test(`returns success of false if the user has no legacy privileges`, async () => {
const privilege = 'action:saved_objects/config/get';
const privilege = `action:saved_objects/${savedObjectTypes[0]}/get`;
const username = 'foo-username';
const mockServer = createMockServer();
const callWithRequest = createMockCallWithRequest([
Expand Down Expand Up @@ -443,7 +448,7 @@ describe('legacy fallback with no application privileges', () => {
});

test(`returns success of false if the user has the read privilege on kibana index but one privilege isn't a read action`, async () => {
const privilegeMap = buildPrivilegeMap(defaultApplication, defaultVersion);
const privilegeMap = buildPrivilegeMap(savedObjectTypes, defaultApplication, defaultVersion);

const actions = privilegeMap.read.actions.filter(a => a !== getVersionPrivilege(defaultVersion) && a !== getLoginPrivilege());
for (const action of actions) {
Expand Down Expand Up @@ -506,7 +511,7 @@ describe('legacy fallback with no application privileges', () => {
});

test(`returns success of true if the user has the read privilege on kibana index and the privilege is a read action`, async () => {
const privilegeMap = buildPrivilegeMap(defaultApplication, defaultVersion);
const privilegeMap = buildPrivilegeMap(savedObjectTypes, defaultApplication, defaultVersion);
for (const action of privilegeMap.read.actions) {
const privilege = action;
const username = 'foo-username';
Expand Down Expand Up @@ -566,7 +571,7 @@ describe('legacy fallback with no application privileges', () => {
});

test(`returns success of true if the user has the read privilege on kibana index and all privileges are read actions`, async () => {
const privilegeMap = buildPrivilegeMap(defaultApplication, defaultVersion);
const privilegeMap = buildPrivilegeMap(savedObjectTypes, defaultApplication, defaultVersion);
const privileges = privilegeMap.read.actions;
const username = 'foo-username';
const mockServer = createMockServer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ export async function registerPrivilegesWithCluster(server) {
const config = server.config();
const kibanaVersion = config.get('pkg.version');
const application = config.get('xpack.security.rbac.application');
const savedObjectTypes = server.savedObjects.types;

const expectedPrivileges = {
[application]: buildPrivilegeMap(application, kibanaVersion)
[application]: buildPrivilegeMap(savedObjectTypes, application, kibanaVersion)
};

server.log(['security', 'debug'], `Registering Kibana Privileges with Elasticsearch for ${application}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ jest.mock('./privileges', () => ({
buildPrivilegeMap: jest.fn(),
}));

const registerPrivilegesWithClusterTest = (description, { settings = {}, expectedPrivileges, existingPrivileges, assert }) => {
const registerPrivilegesWithClusterTest = (description, {
settings = {},
savedObjectTypes,
expectedPrivileges,
existingPrivileges,
assert
}) => {
const registerMockCallWithInternalUser = () => {
const callWithInternalUser = jest.fn();
getClient.mockReturnValue({
Expand Down Expand Up @@ -43,6 +49,10 @@ const registerPrivilegesWithClusterTest = (description, { settings = {}, expecte
return key in settings ? settings[key] : defaultSettings[key];
});

mockServer.savedObjects = {
types: savedObjectTypes
};

return mockServer;
};

Expand Down Expand Up @@ -110,13 +120,17 @@ const registerPrivilegesWithClusterTest = (description, { settings = {}, expecte
});
};

registerPrivilegesWithClusterTest(`passes application and kibanaVersion to buildPrivilegeMap`, {
registerPrivilegesWithClusterTest(`passes saved object types, application and kibanaVersion to buildPrivilegeMap`, {
settings: {
'pkg.version': 'foo-version',
'xpack.security.rbac.application': 'foo-application',
},
savedObjectTypes: [
'foo-type',
'bar-type',
],
assert: ({ mocks }) => {
expect(mocks.buildPrivilegeMap).toHaveBeenCalledWith('foo-application', 'foo-version');
expect(mocks.buildPrivilegeMap).toHaveBeenCalledWith(['foo-type', 'bar-type'], 'foo-application', 'foo-version');
},
});

Expand Down
13 changes: 6 additions & 7 deletions x-pack/plugins/security/server/lib/privileges/privileges.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function getLoginPrivilege() {
return `action:login`;
}

export function buildPrivilegeMap(application, kibanaVersion) {
const readSavedObjectsPrivileges = buildSavedObjectsReadPrivileges();
export function buildPrivilegeMap(savedObjectTypes, application, kibanaVersion) {
const readSavedObjectsPrivileges = buildSavedObjectsReadPrivileges(savedObjectTypes);

const privilegeActions = {};

Expand All @@ -38,14 +38,13 @@ export function buildPrivilegeMap(application, kibanaVersion) {
return privilegeActions;
}

function buildSavedObjectsReadPrivileges() {
function buildSavedObjectsReadPrivileges(savedObjectTypes) {
const readActions = ['get', 'bulk_get', 'find'];
return buildSavedObjectsPrivileges(readActions);
return buildSavedObjectsPrivileges(savedObjectTypes, readActions);
}

function buildSavedObjectsPrivileges(actions) {
const objectTypes = ['config', 'dashboard', 'graph-workspace', 'index-pattern', 'search', 'timelion-sheet', 'url', 'visualization'];
return objectTypes
function buildSavedObjectsPrivileges(savedObjectTypes, actions) {
return savedObjectTypes
.map(type => actions.map(action => `action:saved_objects/${type}/${action}`))
.reduce((acc, types) => [...acc, ...types], []);
}
Loading

0 comments on commit 9cdf641

Please sign in to comment.