Merge remote-tracking branch 'upstream/granular-app-privileges' into fc/enable-saved-object-management

This commit is contained in:
kobelb 2019-02-15 15:44:31 -08:00
commit 68447ca556
41 changed files with 1548 additions and 128 deletions

View file

@ -143,7 +143,12 @@ export default function (kibana) {
),
{ auth: false }
)
)
),
uiCapabilities: {
dev_tools: {
show: true
},
}
};
},

View file

@ -26,6 +26,7 @@ require('ui-bootstrap-custom');
require('ui/modules').get('kibana', ['sense.ui.bootstrap']);
require('ui/tooltip');
require('ui/autoload/styles');
require('ui/capabilities/route_setup');
require('./src/controllers/sense_controller');
require('./src/directives/sense_history');
@ -35,6 +36,7 @@ require('./src/directives/sense_welcome');
uiRoutes.when('/dev_tools/console', {
requireUICapability: 'dev_tools.show',
controller: 'SenseController',
template,
});

View file

@ -34,6 +34,7 @@ import {
TimelionPageProvider,
SharePageProvider,
TimePickerPageProvider,
ErrorPageProvider,
} from './page_objects';
import {
@ -96,6 +97,7 @@ export default async function ({ readConfigFile }) {
timelion: TimelionPageProvider,
share: SharePageProvider,
timePicker: TimePickerPageProvider,
error: ErrorPageProvider
},
services: {
es: commonConfig.get('services.es'),

View file

@ -0,0 +1,39 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from 'expect.js';
export function ErrorPageProvider({ getService }) {
const find = getService('find');
class ErrorPage {
async expectNotFound() {
const el = await find.byCssSelector('body>pre');
const messageText = await el.getVisibleText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
}
}
return new ErrorPage();
}

View file

@ -33,3 +33,4 @@ export { VisualBuilderPageProvider } from './visual_builder_page';
export { TimelionPageProvider } from './timelion_page';
export { SharePageProvider } from './share_page';
export { TimePickerPageProvider } from './time_picker';
export { ErrorPageProvider } from './error_page';

View file

@ -67,7 +67,7 @@ export function graph(kibana) {
all: ['graph-workspace'],
read: ['config', 'index-pattern'],
},
ui: [],
ui: ['save', 'delete'],
},
read: {
savedObject: {

View file

@ -43,6 +43,7 @@ import {
import {
getOutlinkEncoders,
} from './services/outlink_encoders';
import { uiCapabilities } from 'ui/capabilities';
const app = uiModules.get('app/graph');
@ -792,35 +793,43 @@ app.controller('graphuiPlugin', function ($scope, $route, $http, kbnUrl, Private
}),
run: function () {canWipeWorkspace(function () {kbnUrl.change('/home', {}); }); },
});
if (!$scope.allSavingDisabled) {
$scope.topNavMenu.push({
key: 'save',
label: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledLabel', {
defaultMessage: 'Save',
}),
description: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', {
defaultMessage: 'Save Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledTooltip', {
defaultMessage: 'Save this workspace',
}),
disableButton: function () {return $scope.selectedFields.length === 0;},
template: require('./templates/save_workspace.html')
});
}else {
$scope.topNavMenu.push({
key: 'save',
label: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledLabel', {
defaultMessage: 'Save',
}),
description: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledAriaLabel', {
defaultMessage: 'Save Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', {
defaultMessage: 'No changes to saved workspaces are permitted by the current save policy',
}),
disableButton: true
});
// if saving is disabled using uiCapabilities, we don't want to render the save
// button so it's consistent with all of the other applications
if (uiCapabilities.graph.save) {
// allSavingDisabled is based on the xpack.graph.savePolicy, we'll maintain this functionality
if (!$scope.allSavingDisabled) {
$scope.topNavMenu.push({
key: 'save',
label: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledLabel', {
defaultMessage: 'Save',
}),
description: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', {
defaultMessage: 'Save Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledTooltip', {
defaultMessage: 'Save this workspace',
}),
disableButton: function () {return $scope.selectedFields.length === 0;},
template: require('./templates/save_workspace.html'),
testId: 'graphSaveButton',
});
} else {
$scope.topNavMenu.push({
key: 'save',
label: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledLabel', {
defaultMessage: 'Save',
}),
description: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledAriaLabel', {
defaultMessage: 'Save Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', {
defaultMessage: 'No changes to saved workspaces are permitted by the current save policy',
}),
disableButton: true,
testId: 'graphSaveButton',
});
}
}
$scope.topNavMenu.push({
key: 'open',
@ -833,65 +842,74 @@ app.controller('graphuiPlugin', function ($scope, $route, $http, kbnUrl, Private
tooltip: i18n('xpack.graph.topNavMenu.loadWorkspaceTooltip', {
defaultMessage: 'Load a saved workspace',
}),
template: require('./templates/load_workspace.html')
template: require('./templates/load_workspace.html'),
testId: 'graphOpenButton',
});
if (!$scope.allSavingDisabled) {
$scope.topNavMenu.push({
key: 'delete',
disableButton: function () {
return $route.current.locals === undefined || $route.current.locals.savedWorkspace === undefined;
},
label: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledLabel', {
defaultMessage: 'Delete',
}),
description: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaLabel', {
defaultMessage: 'Delete Saved Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaTooltip', {
defaultMessage: 'Delete this workspace',
}),
run: function () {
const title = $route.current.locals.savedWorkspace.title;
function doDelete() {
$route.current.locals.SavedWorkspacesProvider.delete($route.current.locals.savedWorkspace.id);
kbnUrl.change('/home', {});
// if deleting is disabled using uiCapabilities, we don't want to render the delete
// button so it's consistent with all of the other applications
if (uiCapabilities.graph.delete) {
toastNotifications.addSuccess(
i18n('xpack.graph.topNavMenu.deleteWorkspaceNotification', {
defaultMessage: `Deleted '{workspaceTitle}'`,
values: { workspaceTitle: title },
})
// allSavingDisabled is based on the xpack.graph.savePolicy, we'll maintain this functionality
if (!$scope.allSavingDisabled) {
$scope.topNavMenu.push({
key: 'delete',
disableButton: function () {
return $route.current.locals === undefined || $route.current.locals.savedWorkspace === undefined;
},
label: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledLabel', {
defaultMessage: 'Delete',
}),
description: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaLabel', {
defaultMessage: 'Delete Saved Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaTooltip', {
defaultMessage: 'Delete this workspace',
}),
testId: 'graphDeleteButton',
run: function () {
const title = $route.current.locals.savedWorkspace.title;
function doDelete() {
$route.current.locals.SavedWorkspacesProvider.delete($route.current.locals.savedWorkspace.id);
kbnUrl.change('/home', {});
toastNotifications.addSuccess(
i18n('xpack.graph.topNavMenu.deleteWorkspaceNotification', {
defaultMessage: `Deleted '{workspaceTitle}'`,
values: { workspaceTitle: title },
})
);
}
const confirmModalOptions = {
onConfirm: doDelete,
confirmButtonText: i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmButtonLabel', {
defaultMessage: 'Delete workspace',
}),
};
confirmModal(
i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmText', {
defaultMessage: 'Are you sure you want to delete the workspace {title} ?',
values: { title },
}),
confirmModalOptions
);
}
const confirmModalOptions = {
onConfirm: doDelete,
confirmButtonText: i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmButtonLabel', {
defaultMessage: 'Delete workspace',
}),
};
confirmModal(
i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmText', {
defaultMessage: 'Are you sure you want to delete the workspace {title} ?',
values: { title },
}),
confirmModalOptions
);
}
});
}else {
$scope.topNavMenu.push({
key: 'delete',
disableButton: true,
label: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledLabel', {
defaultMessage: 'Delete',
}),
description: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledAriaLabel', {
defaultMessage: 'Delete Saved Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledTooltip', {
defaultMessage: 'No changes to saved workspaces are permitted by the current save policy',
}),
});
});
}else {
$scope.topNavMenu.push({
key: 'delete',
disableButton: true,
label: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledLabel', {
defaultMessage: 'Delete',
}),
description: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledAriaLabel', {
defaultMessage: 'Delete Saved Workspace',
}),
tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledTooltip', {
defaultMessage: 'No changes to saved workspaces are permitted by the current save policy',
}),
testId: 'graphDeleteButton',
});
}
}
$scope.topNavMenu.push({
key: 'settings',

View file

@ -5,14 +5,17 @@
*/
import routes from 'ui/routes';
import 'ui/capabilities/route_setup';
import { toastNotifications } from 'ui/notify';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import template from './grokdebugger_route.html';
import './directives/grokdebugger';
routes
.when('/dev_tools/grokdebugger', {
template: template,
requireUICapability: 'dev_tools.show',
resolve: {
licenseCheckResults(Private) {
const xpackInfo = Private(XPackInfoProvider);

View file

@ -8,6 +8,7 @@
// K5 imports
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
import 'ui/capabilities/route_setup';
import { notify } from 'ui/notify';
// License
@ -30,6 +31,7 @@ import { defaultQuery } from './templates/default_query';
uiRoutes.when('/dev_tools/searchprofiler', {
template: template,
requireUICapability: 'dev_tools.show',
controller: ($scope, i18n) => {
$scope.registerLicenseLinkLabel = i18n('xpack.searchProfiler.registerLicenseLinkLabel',
{ defaultMessage: 'register a license' });

View file

@ -1,4 +1,4 @@
<kbn-dev-tools-app class="prfDevTool">
<kbn-dev-tools-app class="prfDevTool" data-test-subj="searchProfiler">
<div class="prfDevTool__container kuiViewContent kuiViewContentItem" ng-controller="profileViz">
<div class="prfDevTool__wrapper">
<div class="prfDevTool__main">

View file

@ -1,2 +1 @@
@import './manage_spaces';
@import './components/confirm_delete_modal';

View file

@ -1,9 +0,0 @@
.spcManagePage__content {
max-width: 640px;
margin-left: auto;
margin-right: auto;
flex-grow: 0;
}
.spcGridPage {
max-width: map-get($euiBreakpoints, 'xl');
}

View file

@ -4,8 +4,9 @@ exports[`SecureSpaceMessage doesn't render if UI Capabilities does not allow sec
exports[`SecureSpaceMessage renders if user profile allows security to be managed 1`] = `
<Fragment>
<EuiSpacer
size="l"
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiText
className="eui-textCenter"

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import { uiCapabilities } from 'ui/capabilities';
@ -13,7 +13,7 @@ export const SecureSpaceMessage = ({}) => {
if (uiCapabilities.spaces.manage) {
return (
<Fragment>
<EuiSpacer />
<EuiHorizontalRule />
<EuiText className="eui-textCenter">
<p>
<FormattedMessage

View file

@ -14,7 +14,6 @@ import {
EuiPopover,
EuiPopoverProps,
EuiSpacer,
EuiText,
EuiTextArea,
EuiTitle,
} from '@elastic/eui';
@ -186,24 +185,20 @@ export class CustomizeSpace extends Component<Props, State> {
};
public getPanelDescription = () => {
return (
<EuiText>
{this.props.editingExistingSpace ? (
<p>
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable"
defaultMessage="The url identifier cannot be changed."
/>
</p>
) : (
<p>
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable"
defaultMessage="Note the URL identifier. You cannot change it after you create the space."
/>
</p>
)}
</EuiText>
return this.props.editingExistingSpace ? (
<p>
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable"
defaultMessage="The url identifier cannot be changed."
/>
</p>
) : (
<p>
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable"
defaultMessage="Note the URL identifier. You cannot change it after you create the space."
/>
</p>
);
};

View file

@ -9,7 +9,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPageContent,
EuiPageContentBody,
EuiSpacer,
EuiText,
@ -114,12 +113,10 @@ class ManageSpacePageUI extends Component<Props, State> {
const content = this.state.isLoading ? this.getLoadingIndicator() : this.getForm();
return (
<div className="spcManagePage">
<EuiPageContent className="spcManagePage__content">
<EuiPageContentBody>{content}</EuiPageContentBody>
</EuiPageContent>
<Fragment>
<EuiPageContentBody>{content}</EuiPageContentBody>
{this.maybeGetSecureSpacesMessage()}
</div>
</Fragment>
);
}

View file

@ -116,7 +116,7 @@ const kibanaFeatures: Feature[] = [
all: [],
read: ['config'],
},
ui: [],
ui: ['show'],
},
read: {
api: ['console/execute'],
@ -124,7 +124,7 @@ const kibanaFeatures: Feature[] = [
all: [],
read: ['config'],
},
ui: [],
ui: ['show'],
},
},
privilegesTooltip: i18n.translate('xpack.main.featureRegistry.devToolsPrivilegesTooltip', {

View file

@ -0,0 +1,218 @@
/*
* 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 { SecurityService, SpacesService } from 'x-pack/test/common/services';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
// tslint:disable:no-default-export
export default function securityTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const supertest = getService('supertestWithoutAuth');
const security: SecurityService = getService('security');
const spaces: SpacesService = getService('spaces');
describe('/api/console/proxy', () => {
it('cannot be accessed by an anonymous user', async () => {
await supertest
.post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.set('kbn-xsrf', 'xxx')
.send()
.expect(401);
});
it('can be accessed by kibana_user role', async () => {
const username = 'kibana_user';
const roleName = 'kibana_user';
try {
const password = `${username}-password`;
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
await supertest
.post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(username, password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
} finally {
await security.user.delete(username);
}
});
it('can be accessed by global all role', async () => {
const username = 'global_all';
const roleName = 'global_all';
try {
const password = `${username}-password`;
await security.role.create(roleName, {
kibana: [
{
base: ['all'],
spaces: ['*'],
},
],
});
await security.user.create(username, {
password,
roles: [roleName],
});
await supertest
.post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(username, password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
}
});
it('can be accessed by global read role', async () => {
const username = 'global_read';
const roleName = 'global_read';
try {
const password = `${username}-password`;
await security.role.create(roleName, {
kibana: [
{
base: ['read'],
spaces: ['*'],
},
],
});
await security.user.create(username, {
password,
roles: [roleName],
});
await supertest
.post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(username, password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
}
});
// this could be any role which doesn't have access to the dev_tools feature
it(`can't be accessed by a user with dashboard all access`, async () => {
const username = 'dashboard_all';
const roleName = 'dashboard_all';
try {
const password = `${username}-password`;
await security.role.create(roleName, {
kibana: [
{
feature: {
dashboard: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create(username, {
password,
roles: [roleName],
});
await supertest
.post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(username, password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(404);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
}
});
describe('spaces', () => {
// the following tests create a user_1 which has dev_tools all access to space_1 and dashboard access to space_2
const space1Id = 'space_1';
const user1 = {
username: 'user_1',
roleName: 'user_1',
password: 'user_1-password',
};
const space2Id = 'space_2';
before(async () => {
await spaces.create({
id: space1Id,
name: space1Id,
disabledFeatures: [],
});
await security.role.create(user1.roleName, {
kibana: [
{
feature: {
dev_tools: ['all'],
},
spaces: [space1Id],
},
{
feature: {
dashboard: ['all'],
},
spaces: [space2Id],
},
],
});
await security.user.create(user1.username, {
password: user1.password,
roles: [user1.roleName],
});
await spaces.create({
id: space2Id,
name: space2Id,
disabledFeatures: [],
});
});
after(async () => {
await spaces.delete(space1Id);
await spaces.delete(space2Id);
await security.role.delete(user1.roleName);
await security.user.delete(user1.username);
});
it('user_1 can access dev_tools in space_1', async () => {
await supertest
.post(`/s/${space1Id}/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(user1.username, user1.password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
});
it(`user_1 can't access dev_tools in space_2`, async () => {
await supertest
.post(`/s/${space2Id}/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`)
.auth(user1.username, user1.password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(404);
});
});
});
}

View file

@ -0,0 +1,16 @@
/*
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
// tslint:disable:no-default-export
export default function consoleApiIntegrationTests({
loadTestFile,
}: KibanaFunctionalTestDefaultProviders) {
describe('console', () => {
loadTestFile(require.resolve('./feature_controls'));
});
}

View file

@ -16,6 +16,7 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./kibana'));
loadTestFile(require.resolve('./infra'));
loadTestFile(require.resolve('./beats'));
loadTestFile(require.resolve('./console'));
loadTestFile(require.resolve('./management'));
loadTestFile(require.resolve('./uptime'));
});

View file

@ -195,6 +195,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'saved_object:config/bulk_get',
'saved_object:config/get',
'saved_object:config/find',
'ui:dev_tools/show',
],
read: [
'login:',
@ -208,6 +209,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'saved_object:config/bulk_get',
'saved_object:config/get',
'saved_object:config/find',
'ui:dev_tools/show',
],
},
advancedSettings: {
@ -331,6 +333,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'saved_object:index-pattern/bulk_get',
'saved_object:index-pattern/get',
'saved_object:index-pattern/find',
'ui:graph/save',
'ui:graph/delete',
],
read: [
'login:',
@ -602,6 +606,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'ui:catalogue/searchprofiler',
'ui:catalogue/grokdebugger',
'ui:navLinks/kibana:dev_tools',
'ui:dev_tools/show',
'ui:catalogue/advanced_settings',
'ui:management/kibana/settings',
'saved_object:config/create',
@ -633,6 +638,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'saved_object:graph-workspace/bulk_create',
'saved_object:graph-workspace/update',
'saved_object:graph-workspace/delete',
'ui:graph/save',
'ui:graph/delete',
'app:monitoring',
'ui:catalogue/monitoring',
'ui:navLinks/monitoring',
@ -709,6 +716,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'ui:catalogue/searchprofiler',
'ui:catalogue/grokdebugger',
'ui:navLinks/kibana:dev_tools',
'ui:dev_tools/show',
'ui:catalogue/advanced_settings',
'ui:management/kibana/settings',
'ui:catalogue/index_patterns',
@ -806,6 +814,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'ui:catalogue/searchprofiler',
'ui:catalogue/grokdebugger',
'ui:navLinks/kibana:dev_tools',
'ui:dev_tools/show',
'ui:catalogue/advanced_settings',
'ui:management/kibana/settings',
'saved_object:config/create',
@ -837,6 +846,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'saved_object:graph-workspace/bulk_create',
'saved_object:graph-workspace/update',
'saved_object:graph-workspace/delete',
'ui:graph/save',
'ui:graph/delete',
'app:monitoring',
'ui:catalogue/monitoring',
'ui:navLinks/monitoring',
@ -913,6 +924,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'ui:catalogue/searchprofiler',
'ui:catalogue/grokdebugger',
'ui:navLinks/kibana:dev_tools',
'ui:dev_tools/show',
'ui:catalogue/advanced_settings',
'ui:management/kibana/settings',
'ui:catalogue/index_patterns',

View file

@ -12,6 +12,11 @@ import {
InfraOpsGraphQLProvider
} from './services';
import {
SecurityServiceProvider,
SpacesServiceProvider,
} from '../common/services';
export default async function ({ readConfigFile }) {
const kibanaAPITestsConfig = await readConfigFile(require.resolve('../../../test/api_integration/config.js'));
@ -32,6 +37,8 @@ export default async function ({ readConfigFile }) {
usageAPI: UsageAPIProvider,
kibanaServer: kibanaCommonConfig.get('services.kibanaServer'),
chance: kibanaAPITestsConfig.get('services.chance'),
security: SecurityServiceProvider,
spaces: SpacesServiceProvider,
},
esArchiver: xPackFunctionalTestsConfig.get('esArchiver'),
junit: {

View file

@ -0,0 +1,205 @@
/*
* 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 expect from 'expect.js';
import { SecurityService } from 'x-pack/test/common/services';
import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) {
const esArchiver = getService('esArchiver');
const security: SecurityService = getService('security');
const PageObjects = getPageObjects(['common', 'console', 'security']);
const appsMenu = getService('appsMenu');
const testSubjects = getService('testSubjects');
const grokDebugger = getService('grokDebugger');
describe('security', () => {
before(async () => {
await esArchiver.load('empty_kibana');
// ensure we're logged out so we can login as the appropriate users
await PageObjects.security.forceLogout();
});
after(async () => {
// logout, so the other tests don't accidentally run as the custom users we're testing below
await PageObjects.security.forceLogout();
});
describe('global dev_tools all privileges', () => {
before(async () => {
await security.role.create('global_dev_tools_all_role', {
kibana: [
{
feature: {
dev_tools: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create('global_dev_tools_all_user', {
password: 'global_dev_tools_all_user-password',
roles: ['global_dev_tools_all_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'global_dev_tools_all_user',
'global_dev_tools_all_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('global_dev_tools_all_role');
await security.user.delete('global_dev_tools_all_user');
});
it('shows Dev Tools navlink', async () => {
const navLinks = await appsMenu.readLinks();
expect(navLinks.map((link: Record<string, string>) => link.text)).to.eql([
'Dev Tools',
'Management',
]);
});
it(`can navigate to console`, async () => {
await PageObjects.common.navigateToApp('console');
await testSubjects.existOrFail('console');
});
it(`can navigate to search profiler`, async () => {
await PageObjects.common.navigateToApp('searchProfiler');
await testSubjects.existOrFail('searchProfiler');
});
it(`can navigate to grok debugger`, async () => {
await PageObjects.common.navigateToApp('grokDebugger');
await grokDebugger.assertExists();
});
});
describe('global dev_tools read-only privileges', () => {
before(async () => {
await security.role.create('global_dev_tools_read_role', {
kibana: [
{
feature: {
dev_tools: ['read'],
},
spaces: ['*'],
},
],
});
await security.user.create('global_dev_tools_read_user', {
password: 'global_dev_tools_read_user-password',
roles: ['global_dev_tools_read_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'global_dev_tools_read_user',
'global_dev_tools_read_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('global_dev_tools_read_role');
await security.user.delete('global_dev_tools_read_user');
});
it(`shows 'Dev Tools' navlink`, async () => {
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).to.eql(['Dev Tools', 'Management']);
});
it(`can navigate to console`, async () => {
await PageObjects.common.navigateToApp('console');
await testSubjects.existOrFail('console');
});
it(`can navigate to search profiler`, async () => {
await PageObjects.common.navigateToApp('searchProfiler');
await testSubjects.existOrFail('searchProfiler');
});
it(`can navigate to grok debugger`, async () => {
await PageObjects.common.navigateToApp('grokDebugger');
await grokDebugger.assertExists();
});
});
describe('no dev_tools privileges', () => {
before(async () => {
await security.role.create('no_dev_tools_privileges_role', {
kibana: [
{
feature: {
discover: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create('no_dev_tools_privileges_user', {
password: 'no_dev_tools_privileges_user-password',
roles: ['no_dev_tools_privileges_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'no_dev_tools_privileges_user',
'no_dev_tools_privileges_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('no_dev_tools_privileges_role');
await security.user.delete('no_dev_tools_privileges_user');
});
it(`doesn't show 'Dev Tools' navLink`, async () => {
const navLinks = await appsMenu.readLinks();
expect(navLinks.map((navLink: any) => navLink.text)).to.not.contain(['Dev Tools']);
});
it(`navigating to console redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('console', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
it(`navigating to search profiler redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('searchProfiler', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
it(`navigating to grok debugger redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('grokDebugger', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
});
});
}

View file

@ -0,0 +1,112 @@
/*
* 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 expect from 'expect.js';
import { SpacesService } from 'x-pack/test/common/services';
import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) {
const esArchiver = getService('esArchiver');
const spacesService: SpacesService = getService('spaces');
const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']);
const appsMenu = getService('appsMenu');
const testSubjects = getService('testSubjects');
const grokDebugger = getService('grokDebugger');
describe('spaces', () => {
before(async () => {
await esArchiver.load('empty_kibana');
});
after(async () => {
await esArchiver.unload('empty_kibana');
});
describe('space with no features disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: [],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it(`shows 'Dev Tools' navlink`, async () => {
await PageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).to.contain('Dev Tools');
});
it(`can navigate to console`, async () => {
await PageObjects.common.navigateToApp('console');
await testSubjects.existOrFail('console');
});
it(`can navigate to search profiler`, async () => {
await PageObjects.common.navigateToApp('searchProfiler');
await testSubjects.existOrFail('searchProfiler');
});
it(`can navigate to grok debugger`, async () => {
await PageObjects.common.navigateToApp('grokDebugger');
await grokDebugger.assertExists();
});
});
describe('space with dev_tools disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: ['dev_tools'],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it(`doesn't show 'Dev Tools' navlink`, async () => {
await PageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).not.to.contain('Dev Tools');
});
it(`navigating to console redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('console', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
it(`navigating to search profiler redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('searchProfiler', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
it(`navigating to grok debugger redirect to homepage`, async () => {
await PageObjects.common.navigateToUrl('grokDebugger', '', {
ensureCurrentUrl: false,
});
await testSubjects.existOrFail('homeApp', 10000);
});
});
});
}

View file

@ -0,0 +1,14 @@
/*
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) {
describe('feature controls', () => {
loadTestFile(require.resolve('./dev_tools_security'));
loadTestFile(require.resolve('./dev_tools_spaces'));
});
}

View file

@ -0,0 +1,15 @@
/*
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
// tslint:disable:no-default-export
export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) {
describe('console', function() {
this.tags('ciGroup3');
loadTestFile(require.resolve('./feature_controls'));
});
}

View file

@ -0,0 +1,194 @@
/*
* 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 expect from 'expect.js';
import { SecurityService } from 'x-pack/test/common/services';
import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) {
const esArchiver = getService('esArchiver');
const security: SecurityService = getService('security');
const PageObjects = getPageObjects(['common', 'graph', 'security', 'error']);
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
describe('security', () => {
before(async () => {
esArchiver.load('empty_kibana');
// ensure we're logged out so we can login as the appropriate users
await PageObjects.security.logout();
});
after(async () => {
// logout, so the other tests don't accidentally run as the custom users we're testing below
await PageObjects.security.logout();
});
describe('global graph all privileges', () => {
before(async () => {
await security.role.create('global_graph_all_role', {
elasticsearch: {
indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }],
},
kibana: [
{
feature: {
graph: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create('global_graph_all_user', {
password: 'global_graph_all_user-password',
roles: ['global_graph_all_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'global_graph_all_user',
'global_graph_all_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('global_graph_all_role');
await security.user.delete('global_graph_all_user');
});
it('shows graph navlink', async () => {
const navLinks = await appsMenu.readLinks();
expect(navLinks.map((link: Record<string, string>) => link.text)).to.eql([
'Graph',
'Management',
]);
});
it('shows save button', async () => {
await PageObjects.common.navigateToApp('graph');
await testSubjects.existOrFail('graphSaveButton');
});
it('shows delete button', async () => {
await PageObjects.common.navigateToApp('graph');
await testSubjects.existOrFail('graphDeleteButton');
});
});
describe('global graph read-only privileges', () => {
before(async () => {
await security.role.create('global_graph_read_role', {
elasticsearch: {
indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }],
},
kibana: [
{
feature: {
graph: ['read'],
},
spaces: ['*'],
},
],
});
await security.user.create('global_graph_read_user', {
password: 'global_graph_read_user-password',
roles: ['global_graph_read_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'global_graph_read_user',
'global_graph_read_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('global_graph_read_role');
await security.user.delete('global_graph_read_user');
});
it('shows graph navlink', async () => {
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).to.eql(['Graph', 'Management']);
});
it(`doesn't show save button`, async () => {
await PageObjects.common.navigateToApp('graph');
await testSubjects.existOrFail('graphOpenButton');
await testSubjects.missingOrFail('graphSaveButton');
});
it(`doesn't show delete button`, async () => {
await PageObjects.common.navigateToApp('graph');
await testSubjects.existOrFail('graphOpenButton');
await testSubjects.missingOrFail('graphSaveButton');
});
});
describe('no graph privileges', () => {
before(async () => {
await security.role.create('no_graph_privileges_role', {
elasticsearch: {
indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }],
},
kibana: [
{
feature: {
dashboard: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create('no_graph_privileges_user', {
password: 'no_graph_privileges_user-password',
roles: ['no_graph_privileges_role'],
full_name: 'test user',
});
await PageObjects.security.login(
'no_graph_privileges_user',
'no_graph_privileges_user-password',
{
expectSpaceSelector: false,
}
);
});
after(async () => {
await security.role.delete('no_graph_privileges_role');
await security.user.delete('no_graph_privileges_user');
});
it(`doesn't show graph navlink`, async () => {
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).not.to.contain('Graph');
});
it(`navigating to app displays a 404`, async () => {
await PageObjects.common.navigateToUrl('graph', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
});
});
});
}

View file

@ -0,0 +1,93 @@
/*
* 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 expect from 'expect.js';
import { SpacesService } from 'x-pack/test/common/services';
import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) {
const esArchiver = getService('esArchiver');
const spacesService: SpacesService = getService('spaces');
const PageObjects = getPageObjects(['common', 'graph', 'security', 'error']);
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
describe('spaces', () => {
before(async () => {
await esArchiver.load('empty_kibana');
});
describe('space with no features disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: [],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it('shows graph navlink', async () => {
await PageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).to.contain('Graph');
});
it('shows save button', async () => {
await PageObjects.common.navigateToApp('graph', {
basePath: '/s/custom_space',
});
await testSubjects.existOrFail('graphSaveButton');
});
it('shows delete button', async () => {
await PageObjects.common.navigateToApp('graph', {
basePath: '/s/custom_space',
});
await testSubjects.existOrFail('graphDeleteButton');
});
});
describe('space with Graph disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: ['graph'],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it(`doesn't show graph navlink`, async () => {
await PageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).not.to.contain('Graph');
});
it(`navigating to app shows 404`, async () => {
await PageObjects.common.navigateToUrl('graph', '', {
basePath: '/s/custom_space',
shouldLoginIfPrompted: false,
ensureCurrentUrl: false,
});
await PageObjects.error.expectNotFound();
});
});
});
}

View file

@ -0,0 +1,14 @@
/*
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers';
// tslint:disable:no-default-export
export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) {
describe('feature controls', () => {
loadTestFile(require.resolve('./graph_security'));
loadTestFile(require.resolve('./graph_spaces'));
});
}

View file

@ -8,6 +8,7 @@ export default function ({ loadTestFile }) {
describe('graph app', function () {
this.tags('ciGroup1');
loadTestFile(require.resolve('./feature_controls'));
loadTestFile(require.resolve('./graph'));
});
}

View file

@ -102,6 +102,7 @@ export default async function ({ readConfigFile }) {
resolve(__dirname, './apps/visualize'),
resolve(__dirname, './apps/uptime'),
resolve(__dirname, './apps/saved_objects_management'),
resolve(__dirname, './apps/dev_tools')
],
// define the name and providers for services that should be
@ -215,6 +216,10 @@ export default async function ({ readConfigFile }) {
pathname: '/app/kibana',
hash: '/dev_tools/grokdebugger',
},
searchProfiler: {
pathname: '/app/kibana',
hash: '/dev_tools/searchprofiler',
},
spaceSelector: {
pathname: '/',
},

View file

@ -97,6 +97,22 @@ export function SecurityPageProvider({ getService, getPageObjects }) {
));
}
async forceLogout() {
log.debug('SecurityPage.forceLogout');
if (await find.existsByDisplayedByCssSelector('.login-form', 100)) {
log.debug('Already on the login page, not forcing anything');
return;
}
log.debug('Redirecting to /logout to force the logout');
const url = PageObjects.common.getHostPort() + '/logout';
await browser.get(url);
log.debug('Waiting on the login form to appear');
await retry.waitForWithTimeout('login form', config.get('timeouts.waitFor') * 5, async () => (
await find.existsByDisplayedByCssSelector('.login-form')
));
}
async clickRolesSection() {
await testSubjects.click('roles');
}

View file

@ -0,0 +1,88 @@
/*
* 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 expect from 'expect.js';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import {
GetUICapabilitiesFailureReason,
UICapabilitiesService,
} from '../../common/services/ui_capabilities';
import { UserAtSpaceScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function devToolsTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('dev_tools', () => {
UserAtSpaceScenarios.forEach(scenario => {
it(`${scenario.id}`, async () => {
const { user, space } = scenario;
const uiCapabilities = await uiCapabilitiesService.get(
{ username: user.username, password: user.password },
space.id
);
switch (scenario.id) {
// these users have a read/write view of Dev Tools
case 'superuser at everything_space':
case 'global_all at everything_space':
case 'dual_privileges_all at everything_space':
case 'everything_space_all at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: true,
});
break;
// these users have a read only view of Dev Tools
// for the time being, this is functionally equivalent to the read/write view
case 'global_read at everything_space':
case 'dual_privileges_read at everything_space':
case 'everything_space_read at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: true,
});
break;
// the nothing_space has no features enabled, so even if we have
// privileges to perform these actions, we won't be able to
case 'superuser at nothing_space':
case 'global_all at nothing_space':
case 'global_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
case 'nothing_space_all at nothing_space':
case 'nothing_space_read at nothing_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: false,
});
break;
// if we don't have access at the space itself, we're
// redirected to the space selector and the ui capabilities
// are lagely irrelevant because they won't be consumed
case 'no_kibana_privileges at everything_space':
case 'no_kibana_privileges at nothing_space':
case 'legacy_all at everything_space':
case 'legacy_all at nothing_space':
case 'everything_space_all at nothing_space':
case 'everything_space_read at nothing_space':
case 'nothing_space_all at everything_space':
case 'nothing_space_read at everything_space':
expect(uiCapabilities.success).to.be(false);
expect(uiCapabilities.failureReason).to.be(
GetUICapabilitiesFailureReason.RedirectedToRoot
);
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -0,0 +1,95 @@
/*
* 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 expect from 'expect.js';
import { UICapabilities } from 'ui/capabilities';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import {
GetUICapabilitiesFailureReason,
UICapabilitiesService,
} from '../../common/services/ui_capabilities';
import { UserAtSpaceScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function graphTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('graph', () => {
UserAtSpaceScenarios.forEach(scenario => {
it(`${scenario.id}`, async () => {
const { user, space } = scenario;
const uiCapabilities = await uiCapabilitiesService.get(
{ username: user.username, password: user.password },
space.id
);
const capabilities: UICapabilities = uiCapabilities.value as UICapabilities;
switch (scenario.id) {
// these users have a read/write view of Graph
case 'superuser at everything_space':
case 'global_all at everything_space':
case 'dual_privileges_all at everything_space':
case 'everything_space_all at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities.graph).to.eql({
save: true,
delete: true,
});
expect(capabilities.catalogue.graph).to.eql(true);
break;
// these users have a read only view of Graph
case 'global_read at everything_space':
case 'dual_privileges_read at everything_space':
case 'everything_space_read at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities.graph).to.eql({
save: false,
delete: false,
});
expect(capabilities.catalogue.graph).to.eql(true);
break;
// the nothing_space has no features enabled, so even if we have
// privileges to perform these actions, we won't be able to
case 'superuser at nothing_space':
case 'global_all at nothing_space':
case 'global_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
case 'nothing_space_all at nothing_space':
case 'nothing_space_read at nothing_space':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities.graph).to.eql({
save: false,
delete: false,
});
expect(capabilities.catalogue.graph).to.eql(false);
break;
// if we don't have access at the space itself, we're
// redirected to the space selector and the ui capabilities
// are lagely irrelevant because they won't be consumed
case 'no_kibana_privileges at everything_space':
case 'no_kibana_privileges at nothing_space':
case 'legacy_all at everything_space':
case 'legacy_all at nothing_space':
case 'everything_space_all at nothing_space':
case 'everything_space_read at nothing_space':
case 'nothing_space_all at everything_space':
case 'nothing_space_read at everything_space':
expect(uiCapabilities.success).to.be(false);
expect(uiCapabilities.failureReason).to.be(
GetUICapabilitiesFailureReason.RedirectedToRoot
);
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -57,7 +57,9 @@ export default function uiCapabilitesTests({
loadTestFile(require.resolve('./advanced_settings'));
loadTestFile(require.resolve('./canvas'));
loadTestFile(require.resolve('./dashboard'));
loadTestFile(require.resolve('./dev_tools'));
loadTestFile(require.resolve('./discover'));
loadTestFile(require.resolve('./graph'));
loadTestFile(require.resolve('./maps'));
loadTestFile(require.resolve('./nav_links'));
loadTestFile(require.resolve('./saved_objects_management'));

View file

@ -0,0 +1,66 @@
/*
* 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 expect from 'expect.js';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import {
GetUICapabilitiesFailureReason,
UICapabilitiesService,
} from '../../common/services/ui_capabilities';
import { UserScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function devToolsTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('dev_tools', () => {
UserScenarios.forEach(scenario => {
it(`${scenario.fullName}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get({
username: scenario.username,
password: scenario.password,
});
switch (scenario.username) {
// these users have a read/write view of Dev Tools
case 'superuser':
case 'all':
case 'dual_privileges_all':
case 'dev_tools_all':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: true,
});
break;
// these users have a read-only view of Dev Tools
// for the time being, this is functionally equivalent to the read/write view
case 'dual_privileges_read':
case 'dev_tools_all':
case 'dev_tools_read':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: true,
});
break;
// these users have no access to even get the ui capabilities
case 'legacy_all':
case 'no_kibana_privileges':
expect(uiCapabilities.success).to.be(false);
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
break;
// all other users can't do anything with Dashboard
default:
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: false,
});
}
});
});
});
}

View file

@ -0,0 +1,71 @@
/*
* 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 expect from 'expect.js';
import { UICapabilities } from 'ui/capabilities';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import {
GetUICapabilitiesFailureReason,
UICapabilitiesService,
} from '../../common/services/ui_capabilities';
import { UserScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function graphTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('graph', () => {
UserScenarios.forEach(scenario => {
it(`${scenario.fullName}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get({
username: scenario.username,
password: scenario.password,
});
const capabilities: UICapabilities = uiCapabilities.value as UICapabilities;
switch (scenario.username) {
// these users have a read/write view of Graph
case 'superuser':
case 'all':
case 'dual_privileges_all':
case 'graph_all':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities!.graph).to.eql({
save: true,
delete: true,
});
break;
// these users have a read-only view of Graph
case 'dual_privileges_read':
case 'graph_read':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities!.graph).to.eql({
save: false,
delete: false,
});
break;
// these users have no access to even get the ui capabilities
case 'no_kibana_privileges':
case 'legacy_all':
expect(uiCapabilities.success).to.be(false);
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
break;
// all other users can't do anything with Graph
default:
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities!.graph).to.eql({
save: false,
delete: false,
});
}
});
});
});
}

View file

@ -46,7 +46,9 @@ export default function uiCapabilitesTests({
loadTestFile(require.resolve('./advanced_settings'));
loadTestFile(require.resolve('./canvas'));
loadTestFile(require.resolve('./dashboard'));
loadTestFile(require.resolve('./dev_tools'));
loadTestFile(require.resolve('./discover'));
loadTestFile(require.resolve('./graph'));
loadTestFile(require.resolve('./maps'));
loadTestFile(require.resolve('./nav_links'));
loadTestFile(require.resolve('./saved_objects_management'));

View file

@ -0,0 +1,56 @@
/*
* 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 expect from 'expect.js';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
import { SpaceScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function devToolsTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('dev_tools', () => {
SpaceScenarios.forEach(scenario => {
it(`${scenario.name}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
switch (scenario.id) {
case 'everything_space':
case 'apm_disabled_space':
case 'advanced_settings_disabled_space':
case 'canvas_disabled_space':
case 'dashboard_disabled_space':
case 'discover_disabled_space':
case 'graph_disabled_space':
case 'maps_disabled_space':
case 'infrastructure_disabled_space':
case 'logs_disabled_space':
case 'ml_disabled_space':
case 'monitoring_disabled_space':
case 'timelion_disabled_space':
case 'uptime_disabled_space':
case 'visualize_disabled_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: true,
});
break;
case 'nothing_space':
case 'dev_tools_disabled_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('dev_tools');
expect(uiCapabilities.value!.dev_tools).to.eql({
show: false,
});
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -0,0 +1,60 @@
/*
* 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 expect from 'expect.js';
import { UICapabilities } from 'ui/capabilities';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
import { SpaceScenarios } from '../scenarios';
// tslint:disable:no-default-export
export default function graphTests({ getService }: KibanaFunctionalTestDefaultProviders) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('graph', () => {
SpaceScenarios.forEach(scenario => {
it(`${scenario.name}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
const capabilities: UICapabilities = uiCapabilities.value as UICapabilities;
switch (scenario.id) {
case 'everything_space':
case 'apm_disabled_space':
case 'advanced_settings_disabled_space':
case 'canvas_disabled_space':
case 'dashboard_disabled_space':
case 'dev_tools_disabled_space':
case 'discover_disabled_space':
case 'maps_disabled_space':
case 'infrastructure_disabled_space':
case 'logs_disabled_space':
case 'ml_disabled_space':
case 'monitoring_disabled_space':
case 'timelion_disabled_space':
case 'uptime_disabled_space':
case 'visualize_disabled_space':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities!.graph).to.eql({
save: true,
delete: true,
});
break;
case 'nothing_space':
case 'graph_disabled_space':
expect(uiCapabilities.success).to.be(true);
expect(capabilities).to.have.property('graph');
expect(capabilities!.graph).to.eql({
delete: false,
save: false,
});
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -33,7 +33,9 @@ export default function uiCapabilitesTests({
loadTestFile(require.resolve('./advanced_settings'));
loadTestFile(require.resolve('./canvas'));
loadTestFile(require.resolve('./dashboard'));
loadTestFile(require.resolve('./dev_tools'));
loadTestFile(require.resolve('./discover'));
loadTestFile(require.resolve('./graph'));
loadTestFile(require.resolve('./maps'));
loadTestFile(require.resolve('./nav_links'));
loadTestFile(require.resolve('./saved_objects_management'));