Skip to content

Commit

Permalink
[ML] Moving to kibana capabilities (#64057)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
jgowdyelastic authored Apr 24, 2020
1 parent 6bb7515 commit b3c7002
Show file tree
Hide file tree
Showing 68 changed files with 690 additions and 1,215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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'
);

Expand Down
4 changes: 2 additions & 2 deletions x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 = {
Expand Down
9 changes: 8 additions & 1 deletion x-pack/plugins/ml/common/license/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
12 changes: 10 additions & 2 deletions x-pack/plugins/ml/common/license/ml_license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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';
}
66 changes: 66 additions & 0 deletions x-pack/plugins/ml/common/types/capabilities.ts
Original file line number Diff line number Diff line change
@@ -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>;
75 changes: 0 additions & 75 deletions x-pack/plugins/ml/common/types/privileges.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {},
Expand Down
Loading

0 comments on commit b3c7002

Please sign in to comment.