Skip to content

Commit

Permalink
Public Role APIs (#20732)
Browse files Browse the repository at this point in the history
* Beginning to work on external role management APIs

* Refactoring GET tests and adding more permutations

* Adding test for excluding other resources

* Adding get role tests

* Splitting out the endpoints, or else it's gonna get overwhelming

* Splitting out the post and delete actions

* Beginning to work on POST and the tests

* Posting the updated role

* Adding update tests

* Modifying the UI to use the new public APIs

* Removing internal roles API

* Moving the rbac api integration setup tests to use the public role apis

* Testing field_security and query

* Adding create role tests

* We can't update the transient_metadata...

* Removing debugger

* Update and delete tests

* Returning a 204 when POSTing a Role.

* Switching POST to PUT and roles to role

* We don't need the rbacApplication client-side anymore

* Adding delete route tests

* Using not found instead of not acceptable, as that's more likely

* Only allowing us to PUT known Kibana privileges

* Removing transient_metadata

* Removing one letter variable names

* Using PUT instead of POST when saving roles

* Fixing broken tests
  • Loading branch information
kobelb committed Jul 13, 2018
1 parent 3e6c057 commit a597976
Show file tree
Hide file tree
Showing 18 changed files with 1,775 additions and 191 deletions.
6 changes: 2 additions & 4 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { resolve } from 'path';
import { getUserProvider } from './server/lib/get_user';
import { initAuthenticateApi } from './server/routes/api/v1/authenticate';
import { initUsersApi } from './server/routes/api/v1/users';
import { initRolesApi } from './server/routes/api/v1/roles';
import { initPublicRolesApi } from './server/routes/api/public/roles';
import { initIndicesApi } from './server/routes/api/v1/indices';
import { initLoginView } from './server/routes/views/login';
import { initLogoutView } from './server/routes/views/logout';
Expand Down Expand Up @@ -70,11 +70,9 @@ export const security = (kibana) => new kibana.Plugin({
injectDefaultVars: function (server) {
const config = server.config();

const { authorization } = server.plugins.security;
return {
secureCookies: config.get('xpack.security.secureCookies'),
sessionTimeout: config.get('xpack.security.sessionTimeout'),
rbacApplication: authorization.application,
};
}
},
Expand Down Expand Up @@ -146,7 +144,7 @@ export const security = (kibana) => new kibana.Plugin({
await initAuthenticator(server);
initAuthenticateApi(server);
initUsersApi(server);
initRolesApi(server);
initPublicRolesApi(server);
initIndicesApi(server);
initPrivilegesApi(server);
initLoginView(server, xpackMainPlugin);
Expand Down
11 changes: 10 additions & 1 deletion x-pack/plugins/security/public/services/shield_role.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
*/

import 'angular-resource';
import { omit } from 'lodash';
import angular from 'angular';
import { uiModules } from 'ui/modules';

const module = uiModules.get('security', ['ngResource']);
module.service('ShieldRole', ($resource, chrome) => {
return $resource(chrome.addBasePath('/api/security/v1/roles/:name'), {
return $resource(chrome.addBasePath('/api/security/role/:name'), {
name: '@name'
}, {
save: {
method: 'PUT',
transformRequest(data) {
return angular.toJson(omit(data, 'name', 'transient_metadata', '_unrecognized_applications'));
}
}
});
});
14 changes: 7 additions & 7 deletions x-pack/plugins/security/public/views/management/edit_role.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ <h1 class="kuiTitle">
<input
class="kuiCheckBox"
type="checkbox"
ng-checked="includes(role.cluster, privilege)"
ng-click="toggle(role.cluster, privilege)"
ng-checked="includes(role.elasticsearch.cluster, privilege)"
ng-click="toggle(role.elasticsearch.cluster, privilege)"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
/>
<span class="kuiOptionLabel">{{privilege}}</span>
Expand All @@ -116,12 +116,12 @@ <h1 class="kuiTitle">
Kibana Privileges
</label>

<div ng-repeat="(key, value) in kibanaPrivileges">
<div ng-repeat="(key, value) in kibanaPrivilegesViewModel">
<label>
<input
class="kuiCheckBox"
type="checkbox"
ng-model="kibanaPrivileges[key]"
ng-model="kibanaPrivilegesViewModel[key]"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
/>
<span class="kuiOptionLabel">{{key}}</span>
Expand All @@ -136,7 +136,7 @@ <h1 class="kuiTitle">
</label>
<ui-select
multiple
ng-model="role.run_as"
ng-model="role.elasticsearch.run_as"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
>
<ui-select-match placeholder="Add a user...">
Expand All @@ -152,7 +152,7 @@ <h1 class="kuiTitle">
<div class="kuiFormSection">
<kbn-index-privileges-form
is-new-role="editRole.isNewRole"
indices="role.indices"
indices="role.elasticsearch.indices"
index-patterns="indexPatterns"
privileges="privileges"
field-options="editRole.fieldOptions"
Expand All @@ -173,7 +173,7 @@ <h1 class="kuiTitle">
class="kuiButton kuiButton--primary"
ng-click="saveRole(role)"
ng-if="!role.metadata._reserved && isRoleEnabled(role)"
ng-disabled="form.$invalid || !areIndicesValid(role.indices)"
ng-disabled="form.$invalid || !areIndicesValid(role.elasticsearch.indices)"
data-test-subj="roleFormSaveButton"
>
Save
Expand Down
80 changes: 31 additions & 49 deletions x-pack/plugins/security/public/views/management/edit_role.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import _ from 'lodash';
import chrome from 'ui/chrome';
import routes from 'ui/routes';
import { fatalError, toastNotifications } from 'ui/notify';
import { toggle } from 'plugins/security/lib/util';
Expand All @@ -22,60 +21,41 @@ import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import { checkLicenseError } from 'plugins/security/lib/check_license_error';
import { EDIT_ROLES_PATH, ROLES_PATH } from './management_urls';
import { ALL_RESOURCE } from '../../../common/constants';

const getKibanaPrivileges = (applicationPrivileges, roleApplications, application) => {
const kibanaPrivileges = applicationPrivileges.reduce((acc, p) => {
acc[p.name] = false;
const getKibanaPrivilegesViewModel = (applicationPrivileges, roleKibanaPrivileges) => {
const viewModel = applicationPrivileges.reduce((acc, applicationPrivilege) => {
acc[applicationPrivilege.name] = false;
return acc;
}, {});

if (!roleApplications || roleApplications.length === 0) {
return kibanaPrivileges;
if (!roleKibanaPrivileges || roleKibanaPrivileges.length === 0) {
return viewModel;
}

// we're filtering out privileges for non-all resources incase the roles were created in a future version
const applications = roleApplications
.filter(roleApplication => roleApplication.application === application)
.filter(roleApplication => !roleApplication.resources.some(resource => resource !== ALL_RESOURCE));

const assigned = _.uniq(_.flatten(_.pluck(applications, 'privileges')));
assigned.forEach(a => {
const assignedPrivileges = _.uniq(_.flatten(_.pluck(roleKibanaPrivileges, 'privileges')));
assignedPrivileges.forEach(assignedPrivilege => {
// we don't want to display privileges that aren't in our expected list of privileges
if (a in kibanaPrivileges) {
kibanaPrivileges[a] = true;
if (assignedPrivilege in viewModel) {
viewModel[assignedPrivilege] = true;
}
});

return kibanaPrivileges;
return viewModel;
};

const getRoleApplications = (kibanaPrivileges, currentRoleApplications = [], application) => {
// we keep any other applications
const newRoleApplications = currentRoleApplications.filter(roleApplication => {
return roleApplication.application !== application;
});

const selectedPrivileges = Object.keys(kibanaPrivileges).filter(key => kibanaPrivileges[key]);
const getKibanaPrivileges = (kibanaPrivilegesViewModel) => {
const selectedPrivileges = Object.keys(kibanaPrivilegesViewModel).filter(key => kibanaPrivilegesViewModel[key]);

// if we have any selected privileges, add a single application entry
if (selectedPrivileges.length > 0) {
newRoleApplications.push({
application,
privileges: selectedPrivileges,
resources: [ALL_RESOURCE]
});
}

return newRoleApplications;
};

const getOtherApplications = (roleApplications, application) => {
if (!roleApplications || roleApplications.length === 0) {
return [];
return [
{
privileges: selectedPrivileges
}
];
}

return roleApplications.map(roleApplication => roleApplication.application).filter(app =>app !== application);
return [];
};

routes.when(`${EDIT_ROLES_PATH}/:name?`, {
Expand All @@ -97,10 +77,13 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
});
}
return new ShieldRole({
cluster: [],
indices: [],
run_as: [],
applications: []
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
_unrecognized_applications: []
});
},
applicationPrivileges(ApplicationPrivileges, kbnUrl, Promise, Private) {
Expand All @@ -126,7 +109,6 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
const Private = $injector.get('Private');
const confirmModal = $injector.get('confirmModal');
const shieldIndices = $injector.get('shieldIndices');
const rbacApplication = chrome.getInjected('rbacApplication');

$scope.role = $route.current.locals.role;
$scope.users = $route.current.locals.users;
Expand All @@ -135,8 +117,8 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {

const applicationPrivileges = $route.current.locals.applicationPrivileges;
const role = $route.current.locals.role;
$scope.kibanaPrivileges = getKibanaPrivileges(applicationPrivileges, role.applications, rbacApplication);
$scope.otherApplications = getOtherApplications(role.applications, rbacApplication);
$scope.kibanaPrivilegesViewModel = getKibanaPrivilegesViewModel(applicationPrivileges, role.kibana);
$scope.otherApplications = role._unrecognized_applications;

$scope.rolesHref = `#${ROLES_PATH}`;

Expand All @@ -158,10 +140,10 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
};

$scope.saveRole = (role) => {
role.indices = role.indices.filter((index) => index.names.length);
role.indices.forEach((index) => index.query || delete index.query);
role.elasticsearch.indices = role.elasticsearch.indices.filter((index) => index.names.length);
role.elasticsearch.indices.forEach((index) => index.query || delete index.query);

role.applications = getRoleApplications($scope.kibanaPrivileges, role.applications, rbacApplication);
role.kibana = getKibanaPrivileges($scope.kibanaPrivilegesViewModel);

return role.$save()
.then(() => toastNotifications.addSuccess('Updated role'))
Expand Down Expand Up @@ -199,7 +181,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
$scope.allowDocumentLevelSecurity = xpackInfo.get('features.security.allowRoleDocumentLevelSecurity');
$scope.allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity');

$scope.$watch('role.indices', (indices) => {
$scope.$watch('role.elasticsearch.indices', (indices) => {
if (!indices.length) $scope.addIndex(indices);
else indices.forEach($scope.fetchFieldOptions);
}, true);
Expand Down
33 changes: 33 additions & 0 deletions x-pack/plugins/security/server/routes/api/public/roles/delete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import _ from 'lodash';
import Joi from 'joi';
import { wrapError } from '../../../../lib/errors';

export function initDeleteRolesApi(server, callWithRequest, routePreCheckLicenseFn) {
server.route({
method: 'DELETE',
path: '/api/security/role/{name}',
handler(request, reply) {
const name = request.params.name;
return callWithRequest(request, 'shield.deleteRole', { name }).then(
() => reply().code(204),
_.flow(wrapError, reply));
},
config: {
validate: {
params: Joi.object()
.keys({
name: Joi.string()
.required(),
})
.required(),
},
pre: [routePreCheckLicenseFn]
}
});
}
Loading

0 comments on commit a597976

Please sign in to comment.