Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Transforms: Fixes privileges check #163687

Merged
merged 1 commit into from Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -12,6 +12,11 @@ import { cloneDeep } from 'lodash';
import { APP_INDEX_PRIVILEGES } from '../constants';
import { Privileges } from '../types/privileges';

export interface PrivilegesAndCapabilities {
privileges: Privileges;
capabilities: Capabilities;
}

export interface TransformCapabilities {
canGetTransform: boolean;
canDeleteTransform: boolean;
Expand Down Expand Up @@ -89,7 +94,7 @@ export const getPrivilegesAndCapabilities = (
clusterPrivileges: Record<string, boolean>,
hasOneIndexWithAllPrivileges: boolean,
hasAllPrivileges: boolean
) => {
): PrivilegesAndCapabilities => {
const privilegesResult: Privileges = {
hasAllPrivileges: true,
missingPrivileges: {
Expand Down
5 changes: 0 additions & 5 deletions x-pack/plugins/transform/public/__mocks__/shared_imports.ts
Expand Up @@ -8,11 +8,6 @@
// actual mocks
export const expandLiteralStrings = jest.fn();
export const XJsonMode = jest.fn();
export const useRequest = jest.fn(() => ({
isLoading: false,
error: null,
data: undefined,
}));
export const getSavedSearch = jest.fn();

// just passing through the reimports
Expand Down
33 changes: 17 additions & 16 deletions x-pack/plugins/transform/public/app/app.tsx
Expand Up @@ -7,14 +7,13 @@

import React, { useContext, FC } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Router, Routes, Route } from '@kbn/shared-ux-router';

import { ScopedHistory } from '@kbn/core/public';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { EuiErrorBoundary } from '@elastic/eui';

import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { ScopedHistory } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';

import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';

import { addInternalBasePath } from '../../common/constants';
Expand All @@ -23,7 +22,6 @@ import { SectionError } from './components';
import { SECTION_SLUG } from './common/constants';
import { AuthorizationContext, AuthorizationProvider } from './lib/authorization';
import { AppDependencies } from './app_dependencies';

import { CloneTransformSection } from './sections/clone_transform';
import { CreateTransformSection } from './sections/create_transform';
import { TransformManagementSection } from './sections/transform_management';
Expand Down Expand Up @@ -63,20 +61,23 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => {

export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => {
const I18nContext = appDependencies.i18n.Context;
const queryClient = new QueryClient();

render(
<EuiErrorBoundary>
<KibanaThemeProvider theme$={appDependencies.theme.theme$}>
<KibanaContextProvider services={appDependencies}>
<AuthorizationProvider
privilegesEndpoint={{ path: addInternalBasePath(`privileges`), version: '1' }}
>
<I18nContext>
<App history={appDependencies.history} />
</I18nContext>
</AuthorizationProvider>
</KibanaContextProvider>
</KibanaThemeProvider>
<QueryClientProvider client={queryClient}>
<KibanaThemeProvider theme$={appDependencies.theme.theme$}>
<KibanaContextProvider services={appDependencies}>
<AuthorizationProvider
privilegesEndpoint={{ path: addInternalBasePath(`privileges`), version: '1' }}
>
<I18nContext>
<App history={appDependencies.history} />
</I18nContext>
</AuthorizationProvider>
</KibanaContextProvider>
</KibanaThemeProvider>
</QueryClientProvider>
</EuiErrorBoundary>,
element
);
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/transform/public/app/hooks/index.ts
Expand Up @@ -12,4 +12,3 @@ export { useResetTransforms } from './use_reset_transform';
export { useScheduleNowTransforms } from './use_schedule_now_transform';
export { useStartTransforms } from './use_start_transform';
export { useStopTransforms } from './use_stop_transform';
export { useRequest } from './use_request';
15 changes: 0 additions & 15 deletions x-pack/plugins/transform/public/app/hooks/use_request.ts

This file was deleted.

Expand Up @@ -6,16 +6,20 @@
*/

import React, { createContext } from 'react';
import { useQuery } from '@tanstack/react-query';

import { Privileges } from '../../../../../common/types/privileges';
import type { IHttpFetchError } from '@kbn/core-http-browser';

import { useRequest } from '../../../hooks';
import type { Privileges } from '../../../../../common/types/privileges';

import {
TransformCapabilities,
type PrivilegesAndCapabilities,
type TransformCapabilities,
INITIAL_CAPABILITIES,
} from '../../../../../common/privilege/has_privilege_factory';

import { useAppDependencies } from '../../../app_dependencies';

interface Authorization {
isLoading: boolean;
apiError: Error | null;
Expand All @@ -41,22 +45,36 @@ interface Props {
}

export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => {
const { http } = useAppDependencies();

const { path, version } = privilegesEndpoint;

const {
isLoading,
error,
data: privilegesData,
} = useRequest({
path,
version,
method: 'get',
});
} = useQuery<PrivilegesAndCapabilities, IHttpFetchError>(
['transform-privileges-and-capabilities'],
async ({ signal }) => {
return await http.fetch<PrivilegesAndCapabilities>(path, {
version,
method: 'GET',
signal,
});
}
);

const value = {
isLoading,
privileges: isLoading ? { ...initialValue.privileges } : privilegesData.privileges,
capabilities: isLoading ? { ...INITIAL_CAPABILITIES } : privilegesData.capabilities,
apiError: error ? (error as Error) : null,
privileges:
isLoading || privilegesData === undefined
? { ...initialValue.privileges }
: privilegesData.privileges,
capabilities:
isLoading || privilegesData === undefined
? { ...INITIAL_CAPABILITIES }
: privilegesData.capabilities,
apiError: error ? error : null,
};

return (
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/transform/public/shared_imports.ts
Expand Up @@ -6,8 +6,6 @@
*/

export { XJsonMode } from '@kbn/ace';
export type { UseRequestConfig } from '@kbn/es-ui-shared-plugin/public';
export { useRequest } from '@kbn/es-ui-shared-plugin/public';

export type { GetMlSharedImportsReturnType } from '@kbn/ml-plugin/public';
export { getMlSharedImports } from '@kbn/ml-plugin/public';
Expand Down
100 changes: 49 additions & 51 deletions x-pack/plugins/transform/server/routes/api/privileges.ts
Expand Up @@ -5,15 +5,17 @@
* 2.0.
*/

import type { IScopedClusterClient } from '@kbn/core/server';
import type { IKibanaResponse, IScopedClusterClient } from '@kbn/core/server';
import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types';
import { getPrivilegesAndCapabilities } from '../../../common/privilege/has_privilege_factory';
import {
getPrivilegesAndCapabilities,
type PrivilegesAndCapabilities,
} from '../../../common/privilege/has_privilege_factory';
import {
addInternalBasePath,
APP_CLUSTER_PRIVILEGES,
APP_INDEX_PRIVILEGES,
} from '../../../common/constants';
import type { Privileges } from '../../../common/types/privileges';

import type { RouteDependencies } from '../../types';

Expand All @@ -23,64 +25,60 @@ export function registerPrivilegesRoute({ router, license }: RouteDependencies)
path: addInternalBasePath('privileges'),
access: 'internal',
})
.addVersion(
.addVersion<undefined, undefined, undefined>(
{
version: '1',
validate: false,
},
license.guardApiRoute(async (ctx, req, res) => {
const privilegesResult: Privileges = {
hasAllPrivileges: true,
missingPrivileges: {
cluster: [],
index: [],
},
};

if (license.getStatus().isSecurityEnabled === false) {
// If security isn't enabled, let the user use app.
return res.ok({ body: privilegesResult });
}
license.guardApiRoute<undefined, undefined, undefined>(
async (ctx, req, res): Promise<IKibanaResponse<PrivilegesAndCapabilities>> => {
if (license.getStatus().isSecurityEnabled === false) {
// If security isn't enabled, let the user use app.
return res.ok({
body: getPrivilegesAndCapabilities({}, true, true),
});
}

const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client;
const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client;

const esClusterPrivilegesReq: Promise<SecurityHasPrivilegesResponse> =
esClient.asCurrentUser.security.hasPrivileges({
body: {
cluster: APP_CLUSTER_PRIVILEGES,
},
});
const [esClusterPrivileges, userPrivileges] = await Promise.all([
// Get cluster privileges
esClusterPrivilegesReq,
// // Get all index privileges the user has
esClient.asCurrentUser.security.getUserPrivileges(),
]);
const esClusterPrivilegesReq: Promise<SecurityHasPrivilegesResponse> =
esClient.asCurrentUser.security.hasPrivileges({
body: {
cluster: APP_CLUSTER_PRIVILEGES,
},
});
const [esClusterPrivileges, userPrivileges] = await Promise.all([
// Get cluster privileges
esClusterPrivilegesReq,
// // Get all index privileges the user has
esClient.asCurrentUser.security.getUserPrivileges(),
]);

const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges;
const { indices } = userPrivileges;
const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges;
const { indices } = userPrivileges;

// Check if they have all the required index privileges for at least one index
const hasOneIndexWithAllPrivileges =
indices.find(({ privileges }: { privileges: string[] }) => {
if (privileges.includes('all')) {
return true;
}
// Check if they have all the required index privileges for at least one index
const hasOneIndexWithAllPrivileges =
indices.find(({ privileges }: { privileges: string[] }) => {
if (privileges.includes('all')) {
return true;
}

const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) =>
privileges.includes(privilege)
);
const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) =>
privileges.includes(privilege)
);

return indexHasAllPrivileges;
}) !== undefined;
return indexHasAllPrivileges;
}) !== undefined;

return res.ok({
body: getPrivilegesAndCapabilities(
cluster,
hasOneIndexWithAllPrivileges,
hasAllPrivileges
),
});
})
return res.ok({
body: getPrivilegesAndCapabilities(
cluster,
hasOneIndexWithAllPrivileges,
hasAllPrivileges
),
});
}
)
);
}