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

Dynamic types #19925

Merged
merged 2 commits into from
Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

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

This new way of creating privileges ends up with a set of actions that we didn't have before, for type "server":

"action:saved_objects/server/get",
"action:saved_objects/server/bulk_get",
"action:saved_objects/server/find"

I don't think this is technically hurting anything, but it's a delta from before. I'm sure this is just a limitation of the way that types are determined by parsing the document mapping, but I wanted to note the difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn’t appear that the server type is used anymore, so I didn’t include it in the “static” types, we should probably open another Issue to remove this type from the mapping’s entirely.

return savedObjectTypes
.map(type => actions.map(action => `action:saved_objects/${type}/${action}`))
.reduce((acc, types) => [...acc, ...types], []);
}
Loading