-
Notifications
You must be signed in to change notification settings - Fork 8.1k
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
Public Role APIs #20732
Public Role APIs #20732
Changes from 11 commits
c2474b2
50e4fc1
366df0f
a03ac1b
55f59b3
25ddb7b
35a7def
c9a64c6
70862b2
da8d5db
0abe3b6
7c22748
e9f8a73
3b1ddc3
cff2b89
5ee5d23
2072348
3e9e8bd
fa03390
2566043
77ab30b
694ed25
4096ae0
941cf2b
7bcaac7
f00cb3e
b599fda
8b7cb3b
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 |
---|---|---|
|
@@ -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'; | ||
|
@@ -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) => { | ||
const getKibanaPrivilegesViewModel = (applicationPrivileges, roleKibanaPrivileges) => { | ||
const viewModel = applicationPrivileges.reduce((acc, p) => { | ||
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: avoid one letter variables |
||
acc[p.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'))); | ||
const assigned = _.uniq(_.flatten(_.pluck(roleKibanaPrivileges, 'privileges'))); | ||
assigned.forEach(a => { | ||
// we don't want to display privileges that aren't in our expected list of privileges | ||
if (a in kibanaPrivileges) { | ||
kibanaPrivileges[a] = true; | ||
if (a in viewModel) { | ||
viewModel[a] = 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?`, { | ||
|
@@ -98,10 +78,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) { | ||
|
@@ -128,7 +111,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; | ||
|
@@ -137,8 +119,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}`; | ||
|
||
|
@@ -162,10 +144,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')) | ||
|
@@ -203,7 +185,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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* 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 { wrapError } from '../../../../lib/errors'; | ||
|
||
export function initDeleteRolesApi(server, callWithRequest, routePreCheckLicenseFn) { | ||
server.route({ | ||
method: 'DELETE', | ||
path: '/api/security/roles/{name}', | ||
handler(request, reply) { | ||
const name = request.params.name; | ||
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. Edit - I'm withdrawing this comment, but leaving it here for posterity.
{"statusCode":404,"error":"Not Found","message":"Not Found"}
|
||
return callWithRequest(request, 'shield.deleteRole', { name }).then( | ||
() => reply().code(204), | ||
_.flow(wrapError, reply)); | ||
}, | ||
config: { | ||
pre: [routePreCheckLicenseFn] | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* 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 Boom from 'boom'; | ||
import { ALL_RESOURCE } from '../../../../../common/constants'; | ||
import { wrapError } from '../../../../lib/errors'; | ||
|
||
export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, application) { | ||
|
||
const transformKibanaApplicationsFromEs = (roleApplications) => { | ||
return roleApplications | ||
.filter(roleApplication => roleApplication.application === application) | ||
.filter(roleApplication => roleApplication.resources.length > 0) | ||
.filter(roleApplication => roleApplication.resources.every(resource => resource === ALL_RESOURCE)) | ||
.map(roleApplication => ({ privileges: roleApplication.privileges })); | ||
}; | ||
|
||
const transformUnrecognizedApplicationsFromEs = (roleApplications) => { | ||
return _.uniq(roleApplications | ||
.filter(roleApplication => roleApplication.application !== application) | ||
.map(roleApplication => roleApplication.application)); | ||
}; | ||
|
||
const transformRoleFromEs = (role, name) => { | ||
return { | ||
name, | ||
metadata: role.metadata, | ||
transient_metadata: role.transient_metadata, | ||
elasticsearch: { | ||
cluster: role.cluster, | ||
indices: role.indices, | ||
run_as: role.run_as, | ||
}, | ||
kibana: transformKibanaApplicationsFromEs(role.applications), | ||
_unrecognized_applications: transformUnrecognizedApplicationsFromEs(role.applications), | ||
}; | ||
}; | ||
|
||
const transformRolesFromEs = (roles) => { | ||
return _.map(roles, (role, name) => transformRoleFromEs(role, name)); | ||
}; | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/api/security/roles', | ||
handler(request, reply) { | ||
return callWithRequest(request, 'shield.getRole').then( | ||
(response) => { | ||
return reply(transformRolesFromEs(response)); | ||
}, | ||
_.flow(wrapError, reply) | ||
); | ||
}, | ||
config: { | ||
pre: [routePreCheckLicenseFn] | ||
} | ||
}); | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/api/security/roles/{name}', | ||
handler(request, reply) { | ||
const name = request.params.name; | ||
return callWithRequest(request, 'shield.getRole', { name }).then( | ||
(response) => { | ||
if (response[name]) return reply(transformRoleFromEs(response[name], name)); | ||
return reply(Boom.notFound()); | ||
}, | ||
_.flow(wrapError, reply)); | ||
}, | ||
config: { | ||
pre: [routePreCheckLicenseFn] | ||
} | ||
}); | ||
} |
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.
Do we have a precedent for versioning public APIs within Kibana yet? If not, what are your thoughts on introducing a
v1
prefix or similar?My concern is that, in general, these APIs will become as fluid as the current plugin API over time, with potential breaking changes within minor/patch releases.