[ML] Moving to kibana capabilities (#64057)

* [ML] Moving to kibana capabilities

* fixing types

* renaming privilges

* renaming privileges to capabilities

* renaming resolvers

* correcting admin capabilities

* fixing includes

* removing any types

* renaming type

* readding spaces

* adding capabilities switcher

* updating comment

* removing unnecessary failing tests

* adding error to log
This commit is contained in:
James Gowdy 2020-04-24 12:08:19 +01:00 committed by GitHub
parent 6bb751540b
commit b3c7002799
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 690 additions and 1215 deletions

View file

@ -6,7 +6,7 @@
import { createAction } from 'redux-actions';
import { createAsyncAction } from './utils';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
import { MlCapabilitiesResponse } from '../../../../../../plugins/ml/common/types/capabilities';
import { AnomaliesTableRecord } from '../../../../../../plugins/ml/common/types/anomalies';
import {
CreateMLJobSuccess,
@ -27,7 +27,7 @@ export const createMLJobAction = createAsyncAction<
CreateMLJobSuccess | null
>('CREATE_ML_JOB');
export const getMLCapabilitiesAction = createAsyncAction<any, PrivilegesResponse>(
export const getMLCapabilitiesAction = createAsyncAction<any, MlCapabilitiesResponse>(
'GET_ML_CAPABILITIES'
);

View file

@ -8,7 +8,7 @@ import moment from 'moment';
import { apiService } from './utils';
import { AnomalyRecords, AnomalyRecordsParams } from '../actions';
import { API_URLS, ML_JOB_ID, ML_MODULE_ID } from '../../../common/constants';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
import { MlCapabilitiesResponse } from '../../../../../../plugins/ml/common/types/capabilities';
import {
CreateMLJobSuccess,
DeleteJobResults,
@ -20,7 +20,7 @@ import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_r
export const getMLJobId = (monitorId: string) => `${monitorId}_${ML_JOB_ID}`.toLowerCase();
export const getMLCapabilities = async (): Promise<PrivilegesResponse> => {
export const getMLCapabilities = async (): Promise<MlCapabilitiesResponse> => {
return await apiService.get(API_URLS.ML_CAPABILITIES);
};

View file

@ -17,7 +17,7 @@ import {
import { getAsyncInitialState, handleAsyncAction } from './utils';
import { IHttpFetchError } from '../../../../../../../target/types/core/public/http';
import { AsyncInitialState } from './types';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
import { MlCapabilitiesResponse } from '../../../../../../plugins/ml/common/types/capabilities';
import { CreateMLJobSuccess, DeleteJobResults } from '../actions/types';
import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_recognizer';
@ -26,7 +26,7 @@ export interface MLJobState {
createJob: AsyncInitialState<CreateMLJobSuccess>;
deleteJob: AsyncInitialState<DeleteJobResults>;
anomalies: AsyncInitialState<AnomalyRecords>;
mlCapabilities: AsyncInitialState<PrivilegesResponse>;
mlCapabilities: AsyncInitialState<MlCapabilitiesResponse>;
}
const initialState: MLJobState = {

View file

@ -4,4 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { MlLicense, LicenseStatus, MINIMUM_FULL_LICENSE, MINIMUM_LICENSE } from './ml_license';
export {
MlLicense,
LicenseStatus,
MINIMUM_FULL_LICENSE,
MINIMUM_LICENSE,
isFullLicense,
isMinimumLicense,
} from './ml_license';

View file

@ -38,8 +38,8 @@ export class MlLicense {
this._isSecurityEnabled = securityIsEnabled;
this._hasLicenseExpired = this._license.status === 'expired';
this._isMlEnabled = this._license.getFeature(PLUGIN_ID).isEnabled;
this._isMinimumLicense = this._license.check(PLUGIN_ID, MINIMUM_LICENSE).state === 'valid';
this._isFullLicense = this._license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid';
this._isMinimumLicense = isMinimumLicense(this._license);
this._isFullLicense = isFullLicense(this._license);
if (this._initialized === false && postInitFunctions !== undefined) {
postInitFunctions.forEach(f => f(this));
@ -74,3 +74,11 @@ export class MlLicense {
return this._isFullLicense;
}
}
export function isFullLicense(license: ILicense) {
return license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid';
}
export function isMinimumLicense(license: ILicense) {
return license.check(PLUGIN_ID, MINIMUM_LICENSE).state === 'valid';
}

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 { KibanaRequest } from 'kibana/server';
export const userMlCapabilities = {
// Anomaly Detection
canGetJobs: false,
canGetDatafeeds: false,
// Calendars
canGetCalendars: false,
// File Data Visualizer
canFindFileStructure: false,
// Filters
canGetFilters: false,
// Data Frame Analytics
canGetDataFrameAnalytics: false,
};
export const adminMlCapabilities = {
// Anomaly Detection
canCreateJob: false,
canDeleteJob: false,
canOpenJob: false,
canCloseJob: false,
canForecastJob: false,
canStartStopDatafeed: false,
canUpdateJob: false,
canUpdateDatafeed: false,
canPreviewDatafeed: false,
// Calendars
canCreateCalendar: false,
canDeleteCalendar: false,
// Filters
canCreateFilter: false,
canDeleteFilter: false,
// Data Frame Analytics
canDeleteDataFrameAnalytics: false,
canCreateDataFrameAnalytics: false,
canStartStopDataFrameAnalytics: false,
};
export type UserMlCapabilities = typeof userMlCapabilities;
export type AdminMlCapabilities = typeof adminMlCapabilities;
export type MlCapabilities = UserMlCapabilities & AdminMlCapabilities;
export const basicLicenseMlCapabilities = ['canFindFileStructure'] as Array<keyof MlCapabilities>;
export function getDefaultCapabilities(): MlCapabilities {
return {
...userMlCapabilities,
...adminMlCapabilities,
};
}
export interface MlCapabilitiesResponse {
capabilities: MlCapabilities;
upgradeInProgress: boolean;
isPlatinumOrTrialLicense: boolean;
mlFeatureEnabledInSpace: boolean;
}
export type ResolveMlCapabilities = (request: KibanaRequest) => Promise<MlCapabilities | null>;

View file

@ -1,75 +0,0 @@
/*
* 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.
*/
//
export interface Privileges {
// Anomaly Detection
canGetJobs: boolean;
canCreateJob: boolean;
canDeleteJob: boolean;
canOpenJob: boolean;
canCloseJob: boolean;
canForecastJob: boolean;
canGetDatafeeds: boolean;
canStartStopDatafeed: boolean;
canUpdateJob: boolean;
canUpdateDatafeed: boolean;
canPreviewDatafeed: boolean;
// Calendars
canGetCalendars: boolean;
canCreateCalendar: boolean;
canDeleteCalendar: boolean;
// Filters
canGetFilters: boolean;
canCreateFilter: boolean;
canDeleteFilter: boolean;
// File Data Visualizer
canFindFileStructure: boolean;
// Data Frame Analytics
canGetDataFrameAnalytics: boolean;
canDeleteDataFrameAnalytics: boolean;
canCreateDataFrameAnalytics: boolean;
canStartStopDataFrameAnalytics: boolean;
}
export function getDefaultPrivileges(): Privileges {
return {
// Anomaly Detection
canGetJobs: false,
canCreateJob: false,
canDeleteJob: false,
canOpenJob: false,
canCloseJob: false,
canForecastJob: false,
canGetDatafeeds: false,
canStartStopDatafeed: false,
canUpdateJob: false,
canUpdateDatafeed: false,
canPreviewDatafeed: false,
// Calendars
canGetCalendars: false,
canCreateCalendar: false,
canDeleteCalendar: false,
// Filters
canGetFilters: false,
canCreateFilter: false,
canDeleteFilter: false,
// File Data Visualizer
canFindFileStructure: false,
// Data Frame Analytics
canGetDataFrameAnalytics: false,
canDeleteDataFrameAnalytics: false,
canCreateDataFrameAnalytics: false,
canStartStopDataFrameAnalytics: false,
};
}
export interface PrivilegesResponse {
capabilities: Privileges;
upgradeInProgress: boolean;
isPlatinumOrTrialLicense: boolean;
mlFeatureEnabledInSpace: boolean;
}

View file

@ -8,19 +8,19 @@ import { i18n } from '@kbn/i18n';
import { hasLicenseExpired } from '../license';
import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges';
import { getPrivileges, getManageMlPrivileges } from './get_privileges';
import { MlCapabilities, getDefaultCapabilities } from '../../../common/types/capabilities';
import { getCapabilities, getManageMlCapabilities } from './get_capabilities';
import { ACCESS_DENIED_PATH } from '../management/management_urls';
let privileges: Privileges = getDefaultPrivileges();
// manage_ml requires all monitor and admin cluster privileges: https://github.com/elastic/elasticsearch/blob/664a29c8905d8ce9ba8c18aa1ed5c5de93a0eabc/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java#L53
export function checkGetManagementMlJobs() {
let _capabilities: MlCapabilities = getDefaultCapabilities();
export function checkGetManagementMlJobsResolver() {
return new Promise<{ mlFeatureEnabledInSpace: boolean }>((resolve, reject) => {
getManageMlPrivileges().then(
getManageMlCapabilities().then(
({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => {
privileges = capabilities;
// Loop through all privileges to ensure they are all set to true.
const isManageML = Object.values(privileges).every(p => p === true);
_capabilities = capabilities;
// Loop through all capabilities to ensure they are all set to true.
const isManageML = Object.values(_capabilities).every(p => p === true);
if (isManageML === true && isPlatinumOrTrialLicense === true) {
return resolve({ mlFeatureEnabledInSpace });
@ -33,17 +33,17 @@ export function checkGetManagementMlJobs() {
});
}
export function checkGetJobsPrivilege(): Promise<Privileges> {
export function checkGetJobsCapabilitiesResolver(): Promise<MlCapabilities> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities, isPlatinumOrTrialLicense }) => {
privileges = capabilities;
getCapabilities().then(({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
// the minimum privilege for using ML with a platinum or trial license is being able to get the transforms list.
// all other functionality is controlled by the return privileges object.
// all other functionality is controlled by the return capabilities object.
// if the license is basic (isPlatinumOrTrialLicense === false) then do not redirect,
// allow the promise to resolve as the separate license check will redirect then user to
// a basic feature
if (privileges.canGetJobs || isPlatinumOrTrialLicense === false) {
return resolve(privileges);
if (_capabilities.canGetJobs || isPlatinumOrTrialLicense === false) {
return resolve(_capabilities);
} else {
window.location.href = '#/access-denied';
return reject();
@ -52,15 +52,15 @@ export function checkGetJobsPrivilege(): Promise<Privileges> {
});
}
export function checkCreateJobsPrivilege(): Promise<Privileges> {
export function checkCreateJobsCapabilitiesResolver(): Promise<MlCapabilities> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities, isPlatinumOrTrialLicense }) => {
privileges = capabilities;
getCapabilities().then(({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
// if the license is basic (isPlatinumOrTrialLicense === false) then do not redirect,
// allow the promise to resolve as the separate license check will redirect then user to
// a basic feature
if (privileges.canCreateJob || isPlatinumOrTrialLicense === false) {
return resolve(privileges);
if (_capabilities.canCreateJob || isPlatinumOrTrialLicense === false) {
return resolve(_capabilities);
} else {
// if the user has no permission to create a job,
// redirect them back to the Transforms Management page
@ -71,14 +71,14 @@ export function checkCreateJobsPrivilege(): Promise<Privileges> {
});
}
export function checkFindFileStructurePrivilege(): Promise<Privileges> {
export function checkFindFileStructurePrivilegeResolver(): Promise<MlCapabilities> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities }) => {
privileges = capabilities;
getCapabilities().then(({ capabilities }) => {
_capabilities = capabilities;
// the minimum privilege for using ML with a basic license is being able to use the datavisualizer.
// all other functionality is controlled by the return privileges object
if (privileges.canFindFileStructure) {
return resolve(privileges);
// all other functionality is controlled by the return _capabilities object
if (_capabilities.canFindFileStructure) {
return resolve(_capabilities);
} else {
window.location.href = '#/access-denied';
return reject();
@ -89,14 +89,14 @@ export function checkFindFileStructurePrivilege(): Promise<Privileges> {
// check the privilege type and the license to see whether a user has permission to access a feature.
// takes the name of the privilege variable as specified in get_privileges.js
export function checkPermission(privilegeType: keyof Privileges) {
export function checkPermission(capability: keyof MlCapabilities) {
const licenseHasExpired = hasLicenseExpired();
return privileges[privilegeType] === true && licenseHasExpired !== true;
return _capabilities[capability] === true && licenseHasExpired !== true;
}
// create the text for the button's tooltips if the user's license has
// expired or if they don't have the privilege to press that button
export function createPermissionFailureMessage(privilegeType: keyof Privileges) {
export function createPermissionFailureMessage(privilegeType: keyof MlCapabilities) {
let message = '';
const licenseHasExpired = hasLicenseExpired();
if (licenseHasExpired) {

View file

@ -7,12 +7,12 @@
import { ml } from '../services/ml_api_service';
import { setUpgradeInProgress } from '../services/upgrade_service';
import { PrivilegesResponse } from '../../../common/types/privileges';
import { MlCapabilitiesResponse } from '../../../common/types/capabilities';
export function getPrivileges(): Promise<PrivilegesResponse> {
export function getCapabilities(): Promise<MlCapabilitiesResponse> {
return new Promise((resolve, reject) => {
ml.checkMlPrivileges()
.then((resp: PrivilegesResponse) => {
ml.checkMlCapabilities()
.then((resp: MlCapabilitiesResponse) => {
if (resp.upgradeInProgress === true) {
setUpgradeInProgress(true);
}
@ -24,10 +24,10 @@ export function getPrivileges(): Promise<PrivilegesResponse> {
});
}
export function getManageMlPrivileges(): Promise<PrivilegesResponse> {
export function getManageMlCapabilities(): Promise<MlCapabilitiesResponse> {
return new Promise((resolve, reject) => {
ml.checkManageMLPrivileges()
.then((resp: PrivilegesResponse) => {
ml.checkManageMLCapabilities()
.then((resp: MlCapabilitiesResponse) => {
if (resp.upgradeInProgress === true) {
setUpgradeInProgress(true);
}

View file

@ -8,14 +8,14 @@ import React from 'react';
import mockAnomaliesTableData from '../../explorer/__mocks__/mock_anomalies_table_data.json';
import { getColumns } from './anomalies_table_columns';
jest.mock('../../privilege/check_privilege', () => ({
jest.mock('../../capabilities/check_capabilities', () => ({
checkPermission: () => false,
}));
jest.mock('../../license', () => ({
hasLicenseExpired: () => false,
}));
jest.mock('../../privilege/get_privileges', () => ({
getPrivileges: () => {},
jest.mock('../../capabilities/get_capabilities', () => ({
getCapabilities: () => {},
}));
jest.mock('../../services/field_format_service', () => ({
getFieldFormat: () => {},

View file

@ -23,7 +23,7 @@ import { DetectorCell } from './detector_cell';
import { EntityCell } from '../entity_cell';
import { InfluencersCell } from './influencers_cell';
import { LinksMenu } from './links_menu';
import { checkPermission } from '../../privilege/check_privilege';
import { checkPermission } from '../../capabilities/check_capabilities';
import { mlFieldFormatService } from '../../services/field_format_service';
import { isRuleSupported } from '../../../../common/util/anomaly_utils';
import { formatValue } from '../../formatters/format_value';

View file

@ -17,7 +17,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
import { checkPermission } from '../../privilege/check_privilege';
import { checkPermission } from '../../capabilities/check_capabilities';
import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search';
import { isRuleSupported } from '../../../../common/util/anomaly_utils';
import { parseInterval } from '../../../../common/util/parse_interval';

View file

@ -30,7 +30,7 @@ import {
import { DetectorDescriptionList } from './components/detector_description_list';
import { ActionsSection } from './actions_section';
import { checkPermission } from '../../privilege/check_privilege';
import { checkPermission } from '../../capabilities/check_capabilities';
import { ConditionsSection } from './conditions_section';
import { ScopeSection } from './scope_section';
import { SelectRuleAction } from './select_rule_action';

View file

@ -45,7 +45,7 @@ jest.mock('../../services/job_service', () => ({
},
}));
jest.mock('../../services/ml_api_service', () => 'ml');
jest.mock('../../privilege/check_privilege', () => ({
jest.mock('../../capabilities/check_capabilities', () => ({
checkPermission: () => true,
}));

View file

@ -14,7 +14,7 @@ import React from 'react';
import { EuiCallOut, EuiCheckbox, EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui';
import { ScopeExpression } from './scope_expression';
import { checkPermission } from '../../privilege/check_privilege';
import { checkPermission } from '../../capabilities/check_capabilities';
import { getScopeFieldDefaults } from './utils';
import { FormattedMessage } from '@kbn/i18n/react';

View file

@ -11,7 +11,7 @@ jest.mock('../../services/job_service.js', () => 'mlJobService');
// The mock is hoisted to the top, so need to prefix the mock function
// with 'mock' so it can be used lazily.
const mockCheckPermission = jest.fn(() => true);
jest.mock('../../privilege/check_privilege', () => ({
jest.mock('../../capabilities/check_capabilities', () => ({
checkPermission: privilege => mockCheckPermission(privilege),
}));

View file

@ -7,13 +7,13 @@
import React from 'react';
import { render } from '@testing-library/react';
import * as CheckPrivilige from '../../../../../privilege/check_privilege';
import * as CheckPrivilige from '../../../../../capabilities/check_capabilities';
import { DeleteAction } from './action_delete';
import mockAnalyticsListItem from './__mocks__/analytics_list_item.json';
jest.mock('../../../../../privilege/check_privilege', () => ({
jest.mock('../../../../../capabilities/check_capabilities', () => ({
checkPermission: jest.fn(() => false),
createPermissionFailureMessage: jest.fn(),
}));

View file

@ -19,7 +19,7 @@ import { deleteAnalytics } from '../../services/analytics_service';
import {
checkPermission,
createPermissionFailureMessage,
} from '../../../../../privilege/check_privilege';
} from '../../../../../capabilities/check_capabilities';
import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common';

View file

@ -19,7 +19,7 @@ import { startAnalytics } from '../../services/analytics_service';
import {
checkPermission,
createPermissionFailureMessage,
} from '../../../../../privilege/check_privilege';
} from '../../../../../capabilities/check_capabilities';
import { DataFrameAnalyticsListRow, isCompletedAnalyticsJob } from './common';

View file

@ -12,7 +12,7 @@ import { DeepReadonly } from '../../../../../../../common/types/common';
import {
checkPermission,
createPermissionFailureMessage,
} from '../../../../../privilege/check_privilege';
} from '../../../../../capabilities/check_capabilities';
import {
getAnalysisType,

View file

@ -18,7 +18,7 @@ import {
} from '@elastic/eui';
import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common';
import { checkPermission } from '../../../../../privilege/check_privilege';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { getTaskStateBadge } from './columns';
import {

View file

@ -7,7 +7,7 @@
import React, { FC } from 'react';
import { EuiButton, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { createPermissionFailureMessage } from '../../../../../privilege/check_privilege';
import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
export const CreateAnalyticsButton: FC<CreateAnalyticsFormProps> = props => {

View file

@ -6,7 +6,7 @@
import { EuiComboBoxOptionOption } from '@elastic/eui';
import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/common';
import { checkPermission } from '../../../../../privilege/check_privilege';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../../ml_nodes_check';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';

View file

@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
import { ml } from '../../../../services/ml_api_service';
import { isFullLicense } from '../../../../license';
import { checkPermission } from '../../../../privilege/check_privilege';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import { useMlKibana } from '../../../../contexts/kibana';

View file

@ -33,7 +33,7 @@ import { NavigationMenu } from '../../components/navigation_menu';
import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types';
import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search';
import { isFullLicense } from '../../license';
import { checkPermission } from '../../privilege/check_privilege';
import { checkPermission } from '../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../ml_nodes_check/check_ml_nodes';
import { FullTimeRangeSelector } from '../../components/full_time_range_selector';
import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { checkPermission } from '../../../../privilege/check_privilege';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import { getIndexPatternNames } from '../../../../util/index_utils';

View file

@ -10,7 +10,7 @@ import React, { Component } from 'react';
import { EuiSpacer, EuiCallOut, EuiLoadingSpinner } from '@elastic/eui';
import { mlJobService } from '../../../../services/job_service';
import { checkPermission } from '../../../../privilege/check_privilege';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../common/util/job_utils';
import { MLJobEditor } from '../ml_job_editor';
import { FormattedMessage } from '@kbn/i18n/react';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { checkPermission } from '../../../../privilege/check_privilege';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import PropTypes from 'prop-types';
import React, { Component } from 'react';

View file

@ -22,7 +22,7 @@ import {
import { cloneDeep } from 'lodash';
import { ml } from '../../../../../services/ml_api_service';
import { checkPermission } from '../../../../../privilege/check_privilege';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { GroupList } from './group_list';
import { NewGroupInput } from './new_group_input';
import { mlMessageBarService } from '../../../../../components/messagebar';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { checkPermission } from '../../../../privilege/check_privilege';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import React from 'react';

View file

@ -20,7 +20,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import { checkGetManagementMlJobs } from '../../../../privilege/check_privilege';
import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities';
import { getDocLinks } from '../../../../util/dependency_cache';
// @ts-ignore undeclared module
@ -75,7 +75,7 @@ export const JobsListPage: FC<{ I18nContext: CoreStart['i18n']['Context'] }> = (
const check = async () => {
try {
const checkPrivilege = await checkGetManagementMlJobs();
const checkPrivilege = await checkGetManagementMlJobsResolver();
setInitialized(true);
setIsMlEnabledInSpace(checkPrivilege.mlFeatureEnabledInSpace);
} catch (e) {

View file

@ -6,7 +6,7 @@
import React, { Fragment, FC } from 'react';
import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui';
import { checkPermission } from '../privilege/check_privilege';
import { checkPermission } from '../capabilities/check_capabilities';
import { mlNodesAvailable } from '../ml_nodes_check/check_ml_nodes';
import { NavigationMenu } from '../components/navigation_menu';
import { OverviewSideBar } from './components/sidebar';

View file

@ -6,7 +6,7 @@
import { loadIndexPatterns, loadSavedSearches } from '../util/index_utils';
import { checkFullLicense } from '../license';
import { checkGetJobsPrivilege } from '../privilege/check_privilege';
import { checkGetJobsCapabilitiesResolver } from '../capabilities/check_capabilities';
import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes';
import { loadMlServerInfo } from '../services/ml_server_info';
@ -28,6 +28,6 @@ export const basicResolvers = ({ indexPatterns }: BasicResolverDependencies): Re
getMlNodeCount,
loadMlServerInfo,
loadIndexPatterns: () => loadIndexPatterns(indexPatterns),
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
loadSavedSearches,
});

View file

@ -16,7 +16,7 @@ import { useResolver } from '../../use_resolver';
import { DatavisualizerSelector } from '../../../datavisualizer';
import { checkBasicLicense } from '../../../license';
import { checkFindFileStructurePrivilege } from '../../../privilege/check_privilege';
import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities';
import { DATA_VISUALIZER_BREADCRUMB, ML_BREADCRUMB } from '../../breadcrumbs';
const breadcrumbs = [ML_BREADCRUMB, DATA_VISUALIZER_BREADCRUMB];
@ -30,7 +30,7 @@ export const selectorRoute: MlRoute = {
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkBasicLicense,
checkFindFileStructurePrivilege,
checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
});
return (
<PageLoader context={context}>

View file

@ -17,7 +17,7 @@ import { useResolver } from '../../use_resolver';
import { FileDataVisualizerPage } from '../../../datavisualizer/file_based';
import { checkBasicLicense } from '../../../license';
import { checkFindFileStructurePrivilege } from '../../../privilege/check_privilege';
import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities';
import { loadIndexPatterns } from '../../../util/index_utils';
import { DATA_VISUALIZER_BREADCRUMB, ML_BREADCRUMB } from '../../breadcrumbs';
@ -43,7 +43,7 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { context } = useResolver('', undefined, deps.config, {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
checkFindFileStructurePrivilege,
checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
});
return (
<PageLoader context={context}>

View file

@ -12,7 +12,7 @@ import { useResolver } from '../../use_resolver';
import { Page } from '../../../datavisualizer/index_based';
import { checkBasicLicense } from '../../../license';
import { checkGetJobsPrivilege } from '../../../privilege/check_privilege';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
import { DATA_VISUALIZER_BREADCRUMB, ML_BREADCRUMB } from '../../breadcrumbs';
@ -39,7 +39,7 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { context } = useResolver(index, savedSearchId, deps.config, {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
checkMlNodesAvailable,
});

View file

@ -13,7 +13,7 @@ import { Page, preConfiguredJobRedirect } from '../../../jobs/new_job/pages/inde
import { ANOMALY_DETECTION_BREADCRUMB, ML_BREADCRUMB } from '../../breadcrumbs';
import { checkBasicLicense } from '../../../license';
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkGetJobsPrivilege } from '../../../privilege/check_privilege';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
enum MODE {
@ -71,7 +71,7 @@ const PageWrapper: FC<IndexOrSearchPageProps> = ({ nextStepPath, deps, mode }) =
const dataVizResolvers = {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
checkMlNodesAvailable,
};

View file

@ -15,7 +15,7 @@ import { Page } from '../../../jobs/new_job/pages/new_job';
import { JOB_TYPE } from '../../../../../common/constants/new_job';
import { mlJobService } from '../../../services/job_service';
import { loadNewJobCapabilities } from '../../../services/new_job_capabilities_service';
import { checkCreateJobsPrivilege } from '../../../privilege/check_privilege';
import { checkCreateJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { ANOMALY_DETECTION_BREADCRUMB, ML_BREADCRUMB } from '../../breadcrumbs';
interface WizardPageProps extends PageProps {
@ -115,7 +115,7 @@ const PageWrapper: FC<WizardPageProps> = ({ location, jobType, deps }) => {
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context, results } = useResolver(index, savedSearchId, deps.config, {
...basicResolvers(deps),
privileges: checkCreateJobsPrivilege,
privileges: checkCreateJobsCapabilitiesResolver,
jobCaps: () => loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
});

View file

@ -13,7 +13,7 @@ import { useResolver } from '../use_resolver';
import { OverviewPage } from '../../overview';
import { checkFullLicense } from '../../license';
import { checkGetJobsPrivilege } from '../../privilege/check_privilege';
import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../ml_nodes_check';
import { loadMlServerInfo } from '../../services/ml_server_info';
import { useTimefilter } from '../../contexts/kibana';
@ -38,7 +38,7 @@ export const overviewRoute: MlRoute = {
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
getMlNodeCount,
loadMlServerInfo,
});

View file

@ -17,7 +17,10 @@ import { useResolver } from '../../use_resolver';
import { useTimefilter } from '../../../contexts/kibana';
import { checkFullLicense } from '../../../license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { CalendarsList } from '../../../settings/calendars';
import { SETTINGS, ML_BREADCRUMB } from '../../breadcrumbs';
@ -42,7 +45,7 @@ export const calendarListRoute: MlRoute = {
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
getMlNodeCount,
});

View file

@ -17,7 +17,10 @@ import { useResolver } from '../../use_resolver';
import { useTimefilter } from '../../../contexts/kibana';
import { checkFullLicense } from '../../../license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { NewCalendar } from '../../../settings/calendars';
import { SETTINGS, ML_BREADCRUMB } from '../../breadcrumbs';
@ -74,7 +77,7 @@ const PageWrapper: FC<NewCalendarPageProps> = ({ location, mode, deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
checkMlNodesAvailable,
});

View file

@ -17,7 +17,10 @@ import { useResolver } from '../../use_resolver';
import { useTimefilter } from '../../../contexts/kibana';
import { checkFullLicense } from '../../../license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { FilterLists } from '../../../settings/filter_lists';
@ -43,7 +46,7 @@ export const filterListRoute: MlRoute = {
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
getMlNodeCount,
});

View file

@ -17,7 +17,10 @@ import { useResolver } from '../../use_resolver';
import { useTimefilter } from '../../../contexts/kibana';
import { checkFullLicense } from '../../../license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { EditFilterList } from '../../../settings/filter_lists';
import { SETTINGS, ML_BREADCRUMB } from '../../breadcrumbs';
@ -74,7 +77,7 @@ const PageWrapper: FC<NewFilterPageProps> = ({ location, mode, deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
checkMlNodesAvailable,
});

View file

@ -16,7 +16,10 @@ import { useResolver } from '../../use_resolver';
import { useTimefilter } from '../../../contexts/kibana';
import { checkFullLicense } from '../../../license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { Settings } from '../../../settings';
import { ML_BREADCRUMB, SETTINGS } from '../../breadcrumbs';
@ -32,7 +35,7 @@ export const settingsRoute: MlRoute = {
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
checkGetJobsPrivilege,
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
getMlNodeCount,
});

View file

@ -15,7 +15,7 @@ import { jobs } from './jobs';
import { fileDatavisualizer } from './datavisualizer';
import { MlServerDefaults, MlServerLimits } from '../../../../common/types/ml_server_info';
import { PrivilegesResponse } from '../../../../common/types/privileges';
import { MlCapabilitiesResponse } from '../../../../common/types/capabilities';
import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars';
import {
Job,
@ -300,18 +300,17 @@ export const ml = {
});
},
checkMlPrivileges() {
return http<PrivilegesResponse>({
checkMlCapabilities() {
return http<MlCapabilitiesResponse>({
path: `${basePath()}/ml_capabilities`,
method: 'GET',
});
},
checkManageMLPrivileges() {
return http<PrivilegesResponse>({
checkManageMLCapabilities() {
return http<MlCapabilitiesResponse>({
path: `${basePath()}/ml_capabilities`,
method: 'GET',
query: { ignoreSpaces: true },
});
},

View file

@ -7,15 +7,15 @@
jest.mock('../../../components/navigation_menu', () => ({
NavigationMenu: () => <div id="mockNavigationMenu" />,
}));
jest.mock('../../../privilege/check_privilege', () => ({
jest.mock('../../../capabilities/check_capabilities', () => ({
checkPermission: () => true,
}));
jest.mock('../../../license', () => ({
hasLicenseExpired: () => false,
isFullLicense: () => false,
}));
jest.mock('../../../privilege/get_privileges', () => ({
getPrivileges: () => {},
jest.mock('../../../capabilities/get_capabilities', () => ({
getCapabilities: () => {},
}));
jest.mock('../../../ml_nodes_check/check_ml_nodes', () => ({
mlNodesAvailable: () => true,

View file

@ -13,15 +13,15 @@ import { CalendarsList } from './calendars_list';
jest.mock('../../../components/navigation_menu', () => ({
NavigationMenu: () => <div id="mockNavigationMenu" />,
}));
jest.mock('../../../privilege/check_privilege', () => ({
jest.mock('../../../capabilities/check_capabilities', () => ({
checkPermission: () => true,
}));
jest.mock('../../../license', () => ({
hasLicenseExpired: () => false,
isFullLicense: () => false,
}));
jest.mock('../../../privilege/get_privileges', () => ({
getPrivileges: () => {},
jest.mock('../../../capabilities/get_capabilities', () => ({
getCapabilities: () => {},
}));
jest.mock('../../../ml_nodes_check/check_ml_nodes', () => ({
mlNodesAvailable: () => true,

View file

@ -8,7 +8,7 @@
// The mock is hoisted to the top, so need to prefix the mock function
// with 'mock' so it can be used lazily.
const mockCheckPermission = jest.fn(() => true);
jest.mock('../../../../privilege/check_privilege', () => ({
jest.mock('../../../../capabilities/check_capabilities', () => ({
checkPermission: privilege => mockCheckPermission(privilege),
}));
jest.mock('../../../../services/ml_api_service', () => 'ml');

View file

@ -12,7 +12,7 @@ import { FilterLists } from './filter_lists';
jest.mock('../../../components/navigation_menu', () => ({
NavigationMenu: () => <div id="mockNavigationMenu" />,
}));
jest.mock('../../../privilege/check_privilege', () => ({
jest.mock('../../../capabilities/check_capabilities', () => ({
checkPermission: () => true,
}));

View file

@ -6,7 +6,7 @@
// Create a mock for the privilege check used within the table to
// enable/disable the 'New Filter' button.
jest.mock('../../../privilege/check_privilege', () => ({
jest.mock('../../../capabilities/check_capabilities', () => ({
checkPermission: () => true,
}));
jest.mock('../../../services/ml_api_service', () => 'ml');

View file

@ -32,7 +32,7 @@ import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import {
checkPermission,
createPermissionFailureMessage,
} from '../../../privilege/check_privilege';
} from '../../../capabilities/check_capabilities';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

View file

@ -0,0 +1,29 @@
/*
* 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 {
adminMlCapabilities,
userMlCapabilities,
MlCapabilities,
getDefaultCapabilities,
} from '../../../../common/types/capabilities';
export function getAdminCapabilities() {
const caps: any = {};
Object.keys(adminMlCapabilities).forEach(k => {
caps[k] = true;
});
return { ...getUserCapabilities(), ...caps } as MlCapabilities;
}
export function getUserCapabilities() {
const caps: any = {};
Object.keys(userMlCapabilities).forEach(k => {
caps[k] = true;
});
return { ...getDefaultCapabilities(), ...caps } as MlCapabilities;
}

View file

@ -0,0 +1,58 @@
/*
* 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 { cloneDeep } from 'lodash';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { CapabilitiesSwitcher, CoreSetup, Logger } from 'src/core/server';
import { ILicense } from '../../../../licensing/common/types';
import { isFullLicense, isMinimumLicense } from '../../../common/license';
import { MlCapabilities, basicLicenseMlCapabilities } from '../../../common/types/capabilities';
export const setupCapabilitiesSwitcher = (
coreSetup: CoreSetup,
license$: Observable<ILicense>,
logger: Logger
) => {
coreSetup.capabilities.registerSwitcher(getSwitcher(license$, logger));
};
function getSwitcher(license$: Observable<ILicense>, logger: Logger): CapabilitiesSwitcher {
return async (request, capabilities) => {
const isAnonymousRequest = !request.route.options.authRequired;
if (isAnonymousRequest) {
return capabilities;
}
try {
const license = await license$.pipe(take(1)).toPromise();
// full license, leave capabilities as they were
if (isFullLicense(license)) {
return capabilities;
}
const mlCaps = capabilities.ml as MlCapabilities;
const originalCapabilities = cloneDeep(mlCaps);
// not full licence, switch off all capabilities
Object.keys(mlCaps).forEach(k => {
mlCaps[k as keyof MlCapabilities] = false;
});
// for a basic license, reapply the original capabilities for the basic license features
if (isMinimumLicense(license)) {
basicLicenseMlCapabilities.forEach(c => (mlCaps[c] = originalCapabilities[c]));
}
return capabilities;
} catch (e) {
logger.debug(`Error updating capabilities for ML based on licensing: ${e}`);
return capabilities;
}
};
}

View file

@ -0,0 +1,255 @@
/*
* 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 { getAdminCapabilities, getUserCapabilities } from './__mocks__/ml_capabilities';
import { capabilitiesProvider } from './check_capabilities';
import { MlLicense } from '../../../common/license';
import { getDefaultCapabilities } from '../../../common/types/capabilities';
const mlLicense = {
isSecurityEnabled: () => true,
isFullLicense: () => true,
} as MlLicense;
const mlLicenseBasic = {
isSecurityEnabled: () => true,
isFullLicense: () => false,
} as MlLicense;
const mlIsEnabled = async () => true;
const mlIsNotEnabled = async () => false;
const callWithRequestNonUpgrade = async () => ({ upgrade_mode: false });
const callWithRequestUpgrade = async () => ({ upgrade_mode: true });
describe('check_capabilities', () => {
describe('getCapabilities() - right number of capabilities', () => {
test('kibana capabilities count', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestNonUpgrade,
getAdminCapabilities(),
mlLicense,
mlIsEnabled
);
const { capabilities } = await getCapabilities();
const count = Object.keys(capabilities).length;
expect(count).toBe(22);
done();
});
});
describe('getCapabilities() with security', () => {
test('ml_user capabilities only', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestNonUpgrade,
getUserCapabilities(),
mlLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full capabilities', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestNonUpgrade,
getAdminCapabilities(),
mlLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(true);
expect(capabilities.canDeleteJob).toBe(true);
expect(capabilities.canOpenJob).toBe(true);
expect(capabilities.canCloseJob).toBe(true);
expect(capabilities.canForecastJob).toBe(true);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(true);
expect(capabilities.canUpdateJob).toBe(true);
expect(capabilities.canUpdateDatafeed).toBe(true);
expect(capabilities.canPreviewDatafeed).toBe(true);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(true);
expect(capabilities.canDeleteCalendar).toBe(true);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(true);
expect(capabilities.canDeleteFilter).toBe(true);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(true);
expect(capabilities.canCreateDataFrameAnalytics).toBe(true);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(true);
done();
});
test('upgrade in progress with full capabilities', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestUpgrade,
getAdminCapabilities(),
mlLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('upgrade in progress with partial capabilities', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestUpgrade,
getUserCapabilities(),
mlLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full capabilities, ml disabled in space', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestNonUpgrade,
getDefaultCapabilities(),
mlLicense,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(false);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(false);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
});
test('full capabilities, basic license, ml disabled in space', async done => {
const { getCapabilities } = capabilitiesProvider(
callWithRequestNonUpgrade,
getDefaultCapabilities(),
mlLicenseBasic,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getCapabilities();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(false);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(false);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
});

View file

@ -0,0 +1,47 @@
/*
* 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 { IScopedClusterClient } from 'kibana/server';
import {
MlCapabilities,
adminMlCapabilities,
MlCapabilitiesResponse,
} from '../../../common/types/capabilities';
import { upgradeCheckProvider } from './upgrade';
import { MlLicense } from '../../../common/license';
export function capabilitiesProvider(
callAsCurrentUser: IScopedClusterClient['callAsCurrentUser'],
capabilities: MlCapabilities,
mlLicense: MlLicense,
isMlEnabledInSpace: () => Promise<boolean>
) {
const { isUpgradeInProgress } = upgradeCheckProvider(callAsCurrentUser);
async function getCapabilities(): Promise<MlCapabilitiesResponse> {
const upgradeInProgress = await isUpgradeInProgress();
const isPlatinumOrTrialLicense = mlLicense.isFullLicense();
const mlFeatureEnabledInSpace = await isMlEnabledInSpace();
if (upgradeInProgress === true) {
// if an upgrade is in progress, set all admin capabilities to false
disableAdminPrivileges(capabilities);
}
return {
capabilities,
upgradeInProgress,
isPlatinumOrTrialLicense,
mlFeatureEnabledInSpace,
};
}
return { getCapabilities };
}
function disableAdminPrivileges(capabilities: MlCapabilities) {
Object.keys(adminMlCapabilities).forEach(k => {
capabilities[k as keyof MlCapabilities] = false;
});
}

View file

@ -4,4 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { privilegesProvider, MlCapabilities } from './check_privileges';
export { capabilitiesProvider } from './check_capabilities';
export { setupCapabilitiesSwitcher } from './capabilities_switcher';

View file

@ -1,154 +0,0 @@
/*
* 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.
*/
export function callWithRequestProvider(testType: string) {
switch (testType) {
case 'partialPrivileges':
return partialPrivileges;
case 'fullPrivileges':
return fullPrivileges;
case 'upgradeWithFullPrivileges':
return upgradeWithFullPrivileges;
case 'upgradeWithPartialPrivileges':
return upgradeWithPartialPrivileges;
default:
return fullPrivileges;
}
}
const fullPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: fullClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: false };
default:
break;
}
};
const partialPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: partialClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: false };
default:
break;
}
};
const upgradeWithFullPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'elastic',
has_all_requested: true,
cluster: fullClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: true };
default:
break;
}
};
const upgradeWithPartialPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: partialClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: true };
default:
break;
}
};
const fullClusterPrivileges = {
'cluster:admin/xpack/ml/datafeeds/delete': true,
'cluster:admin/xpack/ml/datafeeds/update': true,
'cluster:admin/xpack/ml/job/forecast': true,
'cluster:monitor/xpack/ml/job/stats/get': true,
'cluster:admin/xpack/ml/filters/delete': true,
'cluster:admin/xpack/ml/datafeeds/preview': true,
'cluster:admin/xpack/ml/datafeeds/start': true,
'cluster:admin/xpack/ml/filters/put': true,
'cluster:admin/xpack/ml/datafeeds/stop': true,
'cluster:monitor/xpack/ml/calendars/get': true,
'cluster:admin/xpack/ml/filters/get': true,
'cluster:monitor/xpack/ml/datafeeds/get': true,
'cluster:admin/xpack/ml/filters/update': true,
'cluster:admin/xpack/ml/calendars/events/post': true,
'cluster:admin/xpack/ml/job/close': true,
'cluster:monitor/xpack/ml/datafeeds/stats/get': true,
'cluster:admin/xpack/ml/calendars/jobs/update': true,
'cluster:admin/xpack/ml/calendars/put': true,
'cluster:admin/xpack/ml/calendars/events/delete': true,
'cluster:admin/xpack/ml/datafeeds/put': true,
'cluster:admin/xpack/ml/job/open': true,
'cluster:admin/xpack/ml/job/delete': true,
'cluster:monitor/xpack/ml/job/get': true,
'cluster:admin/xpack/ml/job/put': true,
'cluster:admin/xpack/ml/job/update': true,
'cluster:admin/xpack/ml/calendars/delete': true,
'cluster:monitor/xpack/ml/findfilestructure': true,
};
// the same as ml_user role
const partialClusterPrivileges = {
'cluster:admin/xpack/ml/datafeeds/delete': false,
'cluster:admin/xpack/ml/datafeeds/update': false,
'cluster:admin/xpack/ml/job/forecast': false,
'cluster:monitor/xpack/ml/job/stats/get': true,
'cluster:admin/xpack/ml/filters/delete': false,
'cluster:admin/xpack/ml/datafeeds/preview': false,
'cluster:admin/xpack/ml/datafeeds/start': false,
'cluster:admin/xpack/ml/filters/put': false,
'cluster:admin/xpack/ml/datafeeds/stop': false,
'cluster:monitor/xpack/ml/calendars/get': true,
'cluster:admin/xpack/ml/filters/get': false,
'cluster:monitor/xpack/ml/datafeeds/get': true,
'cluster:admin/xpack/ml/filters/update': false,
'cluster:admin/xpack/ml/calendars/events/post': false,
'cluster:admin/xpack/ml/job/close': false,
'cluster:monitor/xpack/ml/datafeeds/stats/get': true,
'cluster:admin/xpack/ml/calendars/jobs/update': false,
'cluster:admin/xpack/ml/calendars/put': false,
'cluster:admin/xpack/ml/calendars/events/delete': false,
'cluster:admin/xpack/ml/datafeeds/put': false,
'cluster:admin/xpack/ml/job/open': false,
'cluster:admin/xpack/ml/job/delete': false,
'cluster:monitor/xpack/ml/job/get': true,
'cluster:admin/xpack/ml/job/put': false,
'cluster:admin/xpack/ml/job/update': false,
'cluster:admin/xpack/ml/calendars/delete': false,
'cluster:monitor/xpack/ml/findfilestructure': true,
};

View file

@ -1,515 +0,0 @@
/*
* 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 { callWithRequestProvider } from './__mocks__/call_with_request';
import { privilegesProvider } from './check_privileges';
import { mlPrivileges } from './privileges';
import { MlLicense } from '../../../common/license';
const mlLicenseWithSecurity = {
isSecurityEnabled: () => true,
isFullLicense: () => true,
} as MlLicense;
const mlLicenseWithOutSecurity = {
isSecurityEnabled: () => false,
isFullLicense: () => true,
} as MlLicense;
const mlLicenseWithOutSecurityBasicLicense = {
isSecurityEnabled: () => false,
isFullLicense: () => false,
} as MlLicense;
const mlLicenseWithSecurityBasicLicense = {
isSecurityEnabled: () => true,
isFullLicense: () => false,
} as MlLicense;
const mlIsEnabled = async () => true;
const mlIsNotEnabled = async () => false;
describe('check_privileges', () => {
describe('getPrivileges() - right number of capabilities', () => {
test('es capabilities count', async done => {
const count = mlPrivileges.cluster.length;
expect(count).toBe(27);
done();
});
test('kibana capabilities count', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsEnabled
);
const { capabilities } = await getPrivileges();
const count = Object.keys(capabilities).length;
expect(count).toBe(22);
done();
});
});
describe('getPrivileges() with security', () => {
test('ml_user capabilities only', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full capabilities', async done => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(true);
expect(capabilities.canDeleteJob).toBe(true);
expect(capabilities.canOpenJob).toBe(true);
expect(capabilities.canCloseJob).toBe(true);
expect(capabilities.canForecastJob).toBe(true);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(true);
expect(capabilities.canUpdateJob).toBe(true);
expect(capabilities.canUpdateDatafeed).toBe(true);
expect(capabilities.canPreviewDatafeed).toBe(true);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(true);
expect(capabilities.canDeleteCalendar).toBe(true);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(true);
expect(capabilities.canDeleteFilter).toBe(true);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(true);
expect(capabilities.canCreateDataFrameAnalytics).toBe(true);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(true);
done();
});
test('upgrade in progress with full capabilities', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('upgrade in progress with partial capabilities', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('ml_user capabilities with security with basic license', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full user with security with basic license', async done => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full capabilities, ml disabled in space', async done => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithSecurity,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(false);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(false);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
});
describe('getPrivileges() without security', () => {
test('ml_user capabilities only', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(true);
expect(capabilities.canDeleteJob).toBe(true);
expect(capabilities.canOpenJob).toBe(true);
expect(capabilities.canCloseJob).toBe(true);
expect(capabilities.canForecastJob).toBe(true);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(true);
expect(capabilities.canUpdateJob).toBe(true);
expect(capabilities.canUpdateDatafeed).toBe(true);
expect(capabilities.canPreviewDatafeed).toBe(true);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(true);
expect(capabilities.canDeleteCalendar).toBe(true);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(true);
expect(capabilities.canDeleteFilter).toBe(true);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(true);
expect(capabilities.canCreateDataFrameAnalytics).toBe(true);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(true);
done();
});
test('upgrade in progress with full capabilities', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('upgrade in progress with partial capabilities', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(true);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(true);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(true);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(true);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(true);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('ml_user capabilities without security with basic license', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('full user without security with basic license', async done => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(true);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(true);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
test('ml_user capabilities only, ml disabled in space', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
mlLicenseWithOutSecurity,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(mlFeatureEnabledInSpace).toBe(false);
expect(capabilities.canGetJobs).toBe(false);
expect(capabilities.canCreateJob).toBe(false);
expect(capabilities.canDeleteJob).toBe(false);
expect(capabilities.canOpenJob).toBe(false);
expect(capabilities.canCloseJob).toBe(false);
expect(capabilities.canForecastJob).toBe(false);
expect(capabilities.canGetDatafeeds).toBe(false);
expect(capabilities.canStartStopDatafeed).toBe(false);
expect(capabilities.canUpdateJob).toBe(false);
expect(capabilities.canUpdateDatafeed).toBe(false);
expect(capabilities.canPreviewDatafeed).toBe(false);
expect(capabilities.canGetCalendars).toBe(false);
expect(capabilities.canCreateCalendar).toBe(false);
expect(capabilities.canDeleteCalendar).toBe(false);
expect(capabilities.canGetFilters).toBe(false);
expect(capabilities.canCreateFilter).toBe(false);
expect(capabilities.canDeleteFilter).toBe(false);
expect(capabilities.canFindFileStructure).toBe(false);
expect(capabilities.canGetDataFrameAnalytics).toBe(false);
expect(capabilities.canDeleteDataFrameAnalytics).toBe(false);
expect(capabilities.canCreateDataFrameAnalytics).toBe(false);
expect(capabilities.canStartStopDataFrameAnalytics).toBe(false);
done();
});
});
});

View file

@ -1,269 +0,0 @@
/*
* 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 { IScopedClusterClient } from 'kibana/server';
import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges';
import { upgradeCheckProvider } from './upgrade';
import { MlLicense } from '../../../common/license';
import { mlPrivileges } from './privileges';
type ClusterPrivilege = Record<string, boolean>;
export interface MlCapabilities {
capabilities: Privileges;
upgradeInProgress: boolean;
isPlatinumOrTrialLicense: boolean;
mlFeatureEnabledInSpace: boolean;
}
export function privilegesProvider(
callAsCurrentUser: IScopedClusterClient['callAsCurrentUser'],
mlLicense: MlLicense,
isMlEnabledInSpace: () => Promise<boolean>,
ignoreSpaces: boolean = false
) {
const { isUpgradeInProgress } = upgradeCheckProvider(callAsCurrentUser);
async function getPrivileges(): Promise<MlCapabilities> {
// get the default privileges, forced to be false.
const privileges = getDefaultPrivileges();
const upgradeInProgress = await isUpgradeInProgress();
const isSecurityEnabled = mlLicense.isSecurityEnabled();
const isPlatinumOrTrialLicense = mlLicense.isFullLicense();
const mlFeatureEnabledInSpace = await isMlEnabledInSpace();
const setGettingPrivileges = isPlatinumOrTrialLicense
? setFullGettingPrivileges
: setBasicGettingPrivileges;
const setActionPrivileges = isPlatinumOrTrialLicense
? setFullActionPrivileges
: setBasicActionPrivileges;
if (mlFeatureEnabledInSpace === false && ignoreSpaces === false) {
// if ML isn't enabled in the current space,
// return with the default privileges (all false)
return {
capabilities: privileges,
upgradeInProgress,
isPlatinumOrTrialLicense,
mlFeatureEnabledInSpace,
};
}
if (isSecurityEnabled === false) {
if (upgradeInProgress === true) {
// if security is disabled and an upgrade in is progress,
// force all "getting" privileges to be true
// leaving all "setting" privileges to be the default false
setGettingPrivileges({}, privileges, true);
} else {
// if no upgrade is in progress,
// get all privileges forced to true
setGettingPrivileges({}, privileges, true);
setActionPrivileges({}, privileges, true);
}
} else {
// security enabled
// load all ml privileges for this user.
const { cluster } = await callAsCurrentUser('ml.privilegeCheck', { body: mlPrivileges });
setGettingPrivileges(cluster, privileges);
if (upgradeInProgress === false) {
// if an upgrade is in progress, don't apply the "setting"
// privileges. leave them to be the default false.
setActionPrivileges(cluster, privileges);
}
}
return {
capabilities: privileges,
upgradeInProgress,
isPlatinumOrTrialLicense,
mlFeatureEnabledInSpace,
};
}
return { getPrivileges };
}
function setFullGettingPrivileges(
cluster: ClusterPrivilege = {},
privileges: Privileges,
forceTrue = false
) {
// Anomaly Detection
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/job/get'] &&
cluster['cluster:monitor/xpack/ml/job/stats/get'])
) {
privileges.canGetJobs = true;
}
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/datafeeds/get'] &&
cluster['cluster:monitor/xpack/ml/datafeeds/stats/get'])
) {
privileges.canGetDatafeeds = true;
}
// Calendars
if (forceTrue || cluster['cluster:monitor/xpack/ml/calendars/get']) {
privileges.canGetCalendars = true;
}
// Filters
if (forceTrue || cluster['cluster:admin/xpack/ml/filters/get']) {
privileges.canGetFilters = true;
}
// File Data Visualizer
if (forceTrue || cluster['cluster:monitor/xpack/ml/findfilestructure']) {
privileges.canFindFileStructure = true;
}
// Data Frame Analytics
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/job/get'] &&
cluster['cluster:monitor/xpack/ml/job/stats/get'])
) {
privileges.canGetDataFrameAnalytics = true;
}
}
function setFullActionPrivileges(
cluster: ClusterPrivilege = {},
privileges: Privileges,
forceTrue = false
) {
// Anomaly Detection
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/put'] &&
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/put'])
) {
privileges.canCreateJob = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/job/update']) {
privileges.canUpdateJob = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/job/open']) {
privileges.canOpenJob = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/job/close']) {
privileges.canCloseJob = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/job/forecast']) {
privileges.canForecastJob = true;
}
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/delete'] &&
cluster['cluster:admin/xpack/ml/datafeeds/delete'])
) {
privileges.canDeleteJob = true;
}
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/start'] &&
cluster['cluster:admin/xpack/ml/datafeeds/stop'])
) {
privileges.canStartStopDatafeed = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/datafeeds/update']) {
privileges.canUpdateDatafeed = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/datafeeds/preview']) {
privileges.canPreviewDatafeed = true;
}
// Calendars
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/calendars/put'] &&
cluster['cluster:admin/xpack/ml/calendars/jobs/update'] &&
cluster['cluster:admin/xpack/ml/calendars/events/post'])
) {
privileges.canCreateCalendar = true;
}
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/calendars/delete'] &&
cluster['cluster:admin/xpack/ml/calendars/events/delete'])
) {
privileges.canDeleteCalendar = true;
}
// Filters
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/filters/put'] &&
cluster['cluster:admin/xpack/ml/filters/update'])
) {
privileges.canCreateFilter = true;
}
if (forceTrue || cluster['cluster:admin/xpack/ml/filters/delete']) {
privileges.canDeleteFilter = true;
}
// Data Frame Analytics
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/put'] &&
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/put'])
) {
privileges.canCreateDataFrameAnalytics = true;
}
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/delete'] &&
cluster['cluster:admin/xpack/ml/datafeeds/delete'])
) {
privileges.canDeleteDataFrameAnalytics = true;
}
if (
forceTrue ||
(cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/start'] &&
cluster['cluster:admin/xpack/ml/datafeeds/stop'])
) {
privileges.canStartStopDataFrameAnalytics = true;
}
}
function setBasicGettingPrivileges(
cluster: ClusterPrivilege = {},
privileges: Privileges,
forceTrue = false
) {
// File Data Visualizer
if (forceTrue || cluster['cluster:monitor/xpack/ml/findfilestructure']) {
privileges.canFindFileStructure = true;
}
}
function setBasicActionPrivileges(
cluster: ClusterPrivilege = {},
privileges: Privileges,
forceTrue = false
) {}

View file

@ -1,37 +0,0 @@
/*
* 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.
*/
export const mlPrivileges = {
cluster: [
'cluster:monitor/xpack/ml/job/get',
'cluster:monitor/xpack/ml/job/stats/get',
'cluster:monitor/xpack/ml/datafeeds/get',
'cluster:monitor/xpack/ml/datafeeds/stats/get',
'cluster:monitor/xpack/ml/calendars/get',
'cluster:admin/xpack/ml/job/put',
'cluster:admin/xpack/ml/job/delete',
'cluster:admin/xpack/ml/job/update',
'cluster:admin/xpack/ml/job/open',
'cluster:admin/xpack/ml/job/close',
'cluster:admin/xpack/ml/job/forecast',
'cluster:admin/xpack/ml/datafeeds/put',
'cluster:admin/xpack/ml/datafeeds/delete',
'cluster:admin/xpack/ml/datafeeds/start',
'cluster:admin/xpack/ml/datafeeds/stop',
'cluster:admin/xpack/ml/datafeeds/update',
'cluster:admin/xpack/ml/datafeeds/preview',
'cluster:admin/xpack/ml/calendars/put',
'cluster:admin/xpack/ml/calendars/delete',
'cluster:admin/xpack/ml/calendars/jobs/update',
'cluster:admin/xpack/ml/calendars/events/post',
'cluster:admin/xpack/ml/calendars/events/delete',
'cluster:admin/xpack/ml/filters/put',
'cluster:admin/xpack/ml/filters/get',
'cluster:admin/xpack/ml/filters/update',
'cluster:admin/xpack/ml/filters/delete',
'cluster:monitor/xpack/ml/findfilestructure',
],
};

View file

@ -7,14 +7,18 @@
import { i18n } from '@kbn/i18n';
import {
CoreSetup,
CoreStart,
Plugin,
IScopedClusterClient,
KibanaRequest,
Logger,
PluginInitializerContext,
ICustomClusterClient,
CapabilitiesStart,
} from 'kibana/server';
import { PluginsSetup, RouteInitialization } from './types';
import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app';
import { MlCapabilities } from '../common/types/capabilities';
import { elasticsearchJsPlugin } from './client/elasticsearch_ml';
import { initMlTelemetry } from './lib/telemetry';
@ -41,6 +45,8 @@ import { systemRoutes } from './routes/system';
import { MlLicense } from '../common/license';
import { MlServerLicense } from './lib/license';
import { createSharedServices, SharedServices } from './shared_services';
import { userMlCapabilities, adminMlCapabilities } from '../common/types/capabilities';
import { setupCapabilitiesSwitcher } from './lib/capabilities';
declare module 'kibana/server' {
interface RequestHandlerContext {
@ -59,6 +65,7 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
private log: Logger;
private version: string;
private mlLicense: MlServerLicense;
private capabilities: CapabilitiesStart | null = null;
constructor(ctx: PluginInitializerContext) {
this.log = ctx.logger.get();
@ -67,6 +74,9 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
}
public setup(coreSetup: CoreSetup, plugins: PluginsSetup): MlPluginSetup {
const userMlCapabilitiesKeys = Object.keys(userMlCapabilities);
const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities);
plugins.features.registerFeature({
id: PLUGIN_ID,
name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', {
@ -93,7 +103,7 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
all: [],
read: [],
},
ui: [],
ui: userMlCapabilitiesKeys,
},
},
{
@ -105,7 +115,7 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
all: [],
read: [],
},
ui: [],
ui: [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys],
},
},
],
@ -116,6 +126,9 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
(mlLicense: MlLicense) => initSampleDataSets(mlLicense, plugins),
]);
// initialize capabilities switcher to add license filter to ml capabilities
setupCapabilitiesSwitcher(coreSetup, plugins.licensing.license$, this.log);
// Can access via router's handler function 'context' parameter - context.ml.mlClient
const mlClient = coreSetup.elasticsearch.createClient(PLUGIN_ID, {
plugins: [elasticsearchJsPlugin],
@ -132,6 +145,14 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
mlLicense: this.mlLicense,
};
const resolveMlCapabilities = async (request: KibanaRequest) => {
if (this.capabilities === null) {
return null;
}
const capabilities = await this.capabilities.resolveCapabilities(request);
return capabilities.ml as MlCapabilities;
};
annotationRoutes(routeInit, plugins.security);
calendars(routeInit);
dataFeedRoutes(routeInit);
@ -144,24 +165,27 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
indicesRoutes(routeInit);
jobAuditMessagesRoutes(routeInit);
jobRoutes(routeInit);
jobServiceRoutes(routeInit);
jobServiceRoutes(routeInit, { resolveMlCapabilities });
notificationRoutes(routeInit);
resultsServiceRoutes(routeInit);
jobValidationRoutes(routeInit, this.version);
systemRoutes(routeInit, {
spaces: plugins.spaces,
cloud: plugins.cloud,
resolveMlCapabilities,
});
initMlServerLog({ log: this.log });
initMlTelemetry(coreSetup, plugins.usageCollection);
return {
...createSharedServices(this.mlLicense, plugins.spaces, plugins.cloud),
...createSharedServices(this.mlLicense, plugins.spaces, plugins.cloud, resolveMlCapabilities),
mlClient,
};
}
public start(): MlPluginStart {}
public start(coreStart: CoreStart): MlPluginStart {
this.capabilities = coreStart.capabilities;
}
public stop() {
this.mlLicense.unsubscribe();

View file

@ -50,6 +50,7 @@
"GetPartitionFieldsValues",
"Modules",
"DataRecognizer",
"RecognizeIndex",
"GetModule",
"SetupModule",
@ -100,7 +101,7 @@
"SystemRoutes",
"HasPrivileges",
"MlCapabilities",
"MlCapabilitiesResponse",
"MlNodeCount",
"MlInfo",
"MlEsSearch",

View file

@ -6,9 +6,9 @@
import Boom from 'boom';
import { schema } from '@kbn/config-schema';
import { IScopedClusterClient } from 'kibana/server';
import { KibanaRequest } from 'kibana/server';
import { wrapError } from '../client/error_wrapper';
import { RouteInitialization } from '../types';
import { RouteInitialization, JobServiceRouteDeps } from '../types';
import {
categorizationFieldExamplesSchema,
chartSchema,
@ -27,24 +27,17 @@ import { categorizationExamplesProvider } from '../models/job_service/new_job';
/**
* Routes for job service
*/
export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) {
async function hasPermissionToCreateJobs(
callAsCurrentUser: IScopedClusterClient['callAsCurrentUser']
) {
if (mlLicense.isSecurityEnabled() === false) {
return true;
export function jobServiceRoutes(
{ router, mlLicense }: RouteInitialization,
{ resolveMlCapabilities }: JobServiceRouteDeps
) {
async function hasPermissionToCreateJobs(request: KibanaRequest) {
const mlCapabilities = await resolveMlCapabilities(request);
if (mlCapabilities === null) {
throw new Error('resolveMlCapabilities is not defined');
}
const resp = await callAsCurrentUser('ml.privilegeCheck', {
body: {
cluster: [
'cluster:admin/xpack/ml/job/put',
'cluster:admin/xpack/ml/job/open',
'cluster:admin/xpack/ml/datafeeds/put',
],
},
});
return resp.has_all_requested;
return mlCapabilities.canCreateJob;
}
/**
@ -595,7 +588,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) {
try {
// due to the use of the _analyze endpoint which is called by the kibana user,
// basic job creation privileges are required to use this endpoint
if ((await hasPermissionToCreateJobs(context.ml!.mlClient.callAsCurrentUser)) === false) {
if ((await hasPermissionToCreateJobs(request)) === false) {
throw Boom.forbidden(
'Insufficient privileges, the machine_learning_admin role is required.'
);

View file

@ -10,7 +10,7 @@ import { Request } from 'hapi';
import { RequestHandlerContext } from 'kibana/server';
import { wrapError } from '../client/error_wrapper';
import { mlLog } from '../client/log';
import { privilegesProvider } from '../lib/check_privileges';
import { capabilitiesProvider } from '../lib/capabilities';
import { spacesUtilsProvider } from '../lib/spaces_utils';
import { RouteInitialization, SystemRouteDeps } from '../types';
@ -19,7 +19,7 @@ import { RouteInitialization, SystemRouteDeps } from '../types';
*/
export function systemRoutes(
{ router, mlLicense }: RouteInitialization,
{ spaces, cloud }: SystemRouteDeps
{ spaces, cloud, resolveMlCapabilities }: SystemRouteDeps
) {
async function getNodeCount(context: RequestHandlerContext) {
const filterPath = 'nodes.*.attributes';
@ -103,35 +103,35 @@ export function systemRoutes(
* @apiGroup SystemRoutes
*
* @api {get} /api/ml/ml_capabilities Check ML capabilities
* @apiName MlCapabilities
* @apiName MlCapabilitiesResponse
* @apiDescription Checks ML capabilities
*/
router.get(
{
path: '/api/ml/ml_capabilities',
validate: {
query: schema.object({
ignoreSpaces: schema.maybe(schema.string()),
}),
},
validate: false,
},
mlLicense.basicLicenseAPIGuard(async (context, request, response) => {
try {
const ignoreSpaces = request.query && request.query.ignoreSpaces === 'true';
// if spaces is disabled force isMlEnabledInSpace to be true
const { isMlEnabledInSpace } =
spaces !== undefined
? spacesUtilsProvider(spaces, (request as unknown) as Request)
: { isMlEnabledInSpace: async () => true };
const { getPrivileges } = privilegesProvider(
const mlCapabilities = await resolveMlCapabilities(request);
if (mlCapabilities === null) {
return response.customError(wrapError(new Error('resolveMlCapabilities is not defined')));
}
const { getCapabilities } = capabilitiesProvider(
context.ml!.mlClient.callAsCurrentUser,
mlCapabilities,
mlLicense,
isMlEnabledInSpace,
ignoreSpaces
isMlEnabledInSpace
);
return response.ok({
body: await getPrivileges(),
body: await getCapabilities(),
});
} catch (error) {
return response.customError(wrapError(error));

View file

@ -4,23 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { APICaller } from 'kibana/server';
import { APICaller, KibanaRequest } from 'kibana/server';
import { SearchResponse, SearchParams } from 'elasticsearch';
import { MlServerLicense } from '../../lib/license';
import { CloudSetup } from '../../../../cloud/server';
import { LicenseCheck } from '../license_checks';
import { spacesUtilsProvider, RequestFacade } from '../../lib/spaces_utils';
import { spacesUtilsProvider } from '../../lib/spaces_utils';
import { SpacesPluginSetup } from '../../../../spaces/server';
import { privilegesProvider, MlCapabilities } from '../../lib/check_privileges';
import { capabilitiesProvider } from '../../lib/capabilities';
import { MlInfoResponse } from '../../../common/types/ml_server_info';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities';
export interface MlSystemProvider {
mlSystemProvider(
callAsCurrentUser: APICaller,
request: RequestFacade
request: KibanaRequest
): {
mlCapabilities(ignoreSpaces?: boolean): Promise<MlCapabilities>;
mlCapabilities(): Promise<MlCapabilitiesResponse>;
mlInfo(): Promise<MlInfoResponse>;
mlSearch<T>(searchParams: SearchParams): Promise<SearchResponse<T>>;
};
@ -31,12 +32,13 @@ export function getMlSystemProvider(
isFullLicense: LicenseCheck,
mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined,
cloud: CloudSetup | undefined
cloud: CloudSetup | undefined,
resolveMlCapabilities: ResolveMlCapabilities
): MlSystemProvider {
return {
mlSystemProvider(callAsCurrentUser: APICaller, request: RequestFacade) {
mlSystemProvider(callAsCurrentUser: APICaller, request: KibanaRequest) {
return {
mlCapabilities(ignoreSpaces?: boolean) {
async mlCapabilities() {
isMinimumLicense();
const { isMlEnabledInSpace } =
@ -44,13 +46,18 @@ export function getMlSystemProvider(
? spacesUtilsProvider(spaces, request)
: { isMlEnabledInSpace: async () => true };
const { getPrivileges } = privilegesProvider(
const mlCapabilities = await resolveMlCapabilities(request);
if (mlCapabilities === null) {
throw new Error('resolveMlCapabilities is not defined');
}
const { getCapabilities } = capabilitiesProvider(
callAsCurrentUser,
mlCapabilities,
mlLicense,
isMlEnabledInSpace,
ignoreSpaces
isMlEnabledInSpace
);
return getPrivileges();
return getCapabilities();
},
async mlInfo(): Promise<MlInfoResponse> {
isMinimumLicense();

View file

@ -17,6 +17,7 @@ import {
AnomalyDetectorsProvider,
getAnomalyDetectorsProvider,
} from './providers/anomaly_detectors';
import { ResolveMlCapabilities } from '../../common/types/capabilities';
export type SharedServices = JobServiceProvider &
AnomalyDetectorsProvider &
@ -27,14 +28,22 @@ export type SharedServices = JobServiceProvider &
export function createSharedServices(
mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined,
cloud: CloudSetup
cloud: CloudSetup,
resolveMlCapabilities: ResolveMlCapabilities
): SharedServices {
const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense);
return {
...getJobServiceProvider(isFullLicense),
...getAnomalyDetectorsProvider(isFullLicense),
...getMlSystemProvider(isMinimumLicense, isFullLicense, mlLicense, spaces, cloud),
...getMlSystemProvider(
isMinimumLicense,
isFullLicense,
mlLicense,
spaces,
cloud,
resolveMlCapabilities
),
...getModulesProvider(isFullLicense),
...getResultsServiceProvider(isFullLicense),
};

View file

@ -13,6 +13,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../features/serve
import { LicensingPluginSetup } from '../../licensing/server';
import { SpacesPluginSetup } from '../../spaces/server';
import { MlServerLicense } from './lib/license';
import { ResolveMlCapabilities } from '../common/types/capabilities';
export interface LicenseCheckResult {
isAvailable: boolean;
@ -26,6 +27,11 @@ export interface LicenseCheckResult {
export interface SystemRouteDeps {
cloud: CloudSetup;
spaces?: SpacesPluginSetup;
resolveMlCapabilities: ResolveMlCapabilities;
}
export interface JobServiceRouteDeps {
resolveMlCapabilities: ResolveMlCapabilities;
}
export interface PluginsSetup {