mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Create navigation cards for serverless access management (#176761)
Closes #174953
## Summary
Adds "Access" section to serverless management page, with cards for
custom roles, organization members, and API keys. These new cards are
gated by the `roleManagementEnabled` feature flag (see #176200).
<img width="1339" alt="Screenshot 2024-03-11 at 10 17 06 PM"
src="f2bb02f3
-4154-4f2a-b07f-4c0013429a0c">
### API keys card
Access to this card is gated by API key privileges - any user with
permission to access the API keys management page will see this card.
### Custom roles card
Access to this card is gated by both the feature flag and role
privileges - any user with permission to access the Roles management
page will see this card if the feature flag is enabled.
### Organization members card
Access to this card is gated by only the feature flag. **Currently there
is no way to query if a user has access to manage the cloud
organization.**
### Implementation Notes:
- Previously, only the serverless search solution offered a link to the
API keys management page from the left navigation bar and the landing
page. This PR will provide access to the API keys management page in all
3 serverless solutions, via the management cards page, given the user
has the minimum API key permissions required.
- In order to check the value of the feature flag from outside of the
security plugin, I have exposed an authz service from the security
plugin (following the paradigm of the authc service). This can be
removed once the feature flag is no longer needed.
- The `Organization members` card is an "extension" navigation card
because it is not tied to an actual application. It provides a link to
the cloud organization. This is implemented in the serverless plugin,
alongside a `getNavigationCards` helper function, to be commonly located
for use in the three serverless solutions plugins. Due to dependency
restrictions, each solution plugin passes the feature flag value from
the security plugin to this function - a complication that will be
removed once the feature flag is no longer needed.
## Manual Testing
1. In the `kibana.dev.yml` file, add the following settings. This
enables the role management feature flag, and provides cloud URLs for
the `Manage organization members` card.
```
xpack.security.roleManagementEnabled: true
xpack.cloud.base_url: 'https://cloud.elastic.co'
xpack.cloud.organization_url: '/account/members'
```
2. Add a test user without access to API keys to the serverless search
`roles.yml` file. Example: Copy the viewer role, and remove the
`manage_own_api_key` cluster privilege.
```
tester:
cluster: ['read_pipeline']
indices:
- names:
- '*'
privileges:
- 'read'
- 'view_index_metadata'
applications:
- application: 'kibana-.kibana'
privileges:
- 'read'
resources:
- '*'
```
3. Start Elasticsearch and Kibana in serverless mode and SSL enabled (to
access the test user selector). Examples:
```
yarn es --serverless=es --ssl
yarn start --serverless=es --ssl
```
4. Navigate to Kibana (use `https` as SSL is enabled), and log in as the
`Admin` test user.
5. Navigate to the Management page using the side navigation bar. Verify
the three new cards are rendered in a new `Access` section, and that
each functions correctly by navigating the user the appropriate
application, or to the cloud organization page (in the case of the
Manage organization members card).
6. Switch to a user without access to view or update roles (e.g.
`viewer` in the serverless search solution). Verify that the API keys
and Org members cards are present, but not the Custom roles card.
7. Switch to a user without access to the API keys management page (the
test role added in step 2 for the search solution). Verify that the API
keys card is not present.
8. Disable the `xpack.security.roleManagementEnabled` feature flag.
Switch to the `admin` test user, and verify that the `Access` section
contains only the API keys card
9. Switch to a user without access to the API keys management page.
Verify that the `Access` section does not render at all.
10. Repeat testing with other solutions (security, observability). Keep
in mind that you may have to add additional test roles to the
`roles.yml` file if you want to test conditions for steps 6 and 7
independently.
## Automated Testing
See
`x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts`,
which can be run from
-
`x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts`
-
`x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts`
-
`x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts`.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
59b1f8ae94
commit
d27ada2e29
43 changed files with 428 additions and 106 deletions
|
@ -7,6 +7,5 @@
|
|||
*/
|
||||
|
||||
export type { AppId, CardsNavigationComponentProps } from './src';
|
||||
|
||||
export { appIds } from './src';
|
||||
export { CardsNavigation } from './src';
|
||||
export { appCategories, type CardNavExtensionDefinition } from './src/types';
|
||||
export { appIds, CardsNavigation } from './src';
|
||||
|
|
|
@ -109,6 +109,13 @@ const getEnabledAppsByCategory = (
|
|||
}),
|
||||
apps: getAppsForCategory(appCategories.DATA, filteredApps),
|
||||
},
|
||||
{
|
||||
id: appCategories.ACCESS,
|
||||
title: i18n.translate('management.landing.withCardNavigation.accessTitle', {
|
||||
defaultMessage: 'Access',
|
||||
}),
|
||||
apps: getAppsForCategory(appCategories.ACCESS, filteredApps),
|
||||
},
|
||||
{
|
||||
id: appCategories.ALERTS,
|
||||
title: i18n.translate('management.landing.withCardNavigation.alertsTitle', {
|
||||
|
|
|
@ -119,14 +119,6 @@ export const appDefinitions: Record<AppId, AppDefinition> = {
|
|||
icon: 'tag',
|
||||
},
|
||||
|
||||
[AppIds.API_KEYS]: {
|
||||
category: appCategories.OTHER,
|
||||
description: i18n.translate('management.landing.withCardNavigation.apiKeysDescription', {
|
||||
defaultMessage: 'Allow programmatic access to your project data and capabilities.',
|
||||
}),
|
||||
icon: 'lockOpen',
|
||||
},
|
||||
|
||||
[AppIds.SERVERLESS_SETTINGS]: {
|
||||
category: appCategories.OTHER,
|
||||
description: i18n.translate('management.landing.withCardNavigation.settingsDescription', {
|
||||
|
@ -135,10 +127,19 @@ export const appDefinitions: Record<AppId, AppDefinition> = {
|
|||
icon: 'gear',
|
||||
},
|
||||
|
||||
// Access section
|
||||
[AppIds.API_KEYS]: {
|
||||
category: appCategories.ACCESS,
|
||||
description: i18n.translate('management.landing.withCardNavigation.apiKeysDescription', {
|
||||
defaultMessage: 'Allow programmatic access to your project data and capabilities.',
|
||||
}),
|
||||
icon: 'lockOpen',
|
||||
},
|
||||
[AppIds.ROLES]: {
|
||||
category: appCategories.OTHER,
|
||||
category: appCategories.ACCESS,
|
||||
description: i18n.translate('management.landing.withCardNavigation.rolesDescription', {
|
||||
defaultMessage: 'Allow custom roles to be created for users.',
|
||||
defaultMessage:
|
||||
'Create roles unique to this project and combine the exact set of privileges that your users need.',
|
||||
}),
|
||||
icon: 'usersRolesApp',
|
||||
},
|
||||
|
|
|
@ -20,7 +20,6 @@ export enum AppIds {
|
|||
SAVED_OBJECTS = 'objects',
|
||||
TAGS = 'tags',
|
||||
FILES_MANAGEMENT = 'filesManagement',
|
||||
API_KEYS = 'api_keys',
|
||||
DATA_VIEWS = 'dataViews',
|
||||
REPORTING = 'reporting',
|
||||
CONNECTORS = 'triggersActionsConnectors',
|
||||
|
@ -28,6 +27,7 @@ export enum AppIds {
|
|||
MAINTENANCE_WINDOWS = 'maintenanceWindows',
|
||||
SERVERLESS_SETTINGS = 'settings',
|
||||
ROLES = 'roles',
|
||||
API_KEYS = 'api_keys',
|
||||
}
|
||||
|
||||
// Create new type that is a union of all the appId values
|
||||
|
@ -35,6 +35,7 @@ export type AppId = `${AppIds}`;
|
|||
|
||||
export const appCategories = {
|
||||
DATA: 'data',
|
||||
ACCESS: 'access',
|
||||
ALERTS: 'alerts',
|
||||
CONTENT: 'content',
|
||||
OTHER: 'other',
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
export type { AuthenticationServiceStart, AuthenticationServiceSetup } from './src/authentication';
|
||||
export type { AuthorizationServiceStart, AuthorizationServiceSetup } from './src/authorization';
|
||||
export type { UserMenuLink, SecurityNavControlServiceStart } from './src/nav_control';
|
||||
export type { SecurityPluginSetup, SecurityPluginStart } from './src/plugin';
|
||||
export type {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface AuthorizationServiceSetup {
|
||||
/**
|
||||
* Determines if role management is enabled.
|
||||
*/
|
||||
isRoleManagementEnabled: () => boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start has the same contract as Setup for now.
|
||||
*/
|
||||
export type AuthorizationServiceStart = AuthorizationServiceSetup;
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { AuthorizationServiceSetup, AuthorizationServiceStart } from './authorization_service';
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { SecurityLicense } from '@kbn/security-plugin-types-common';
|
||||
import type { AuthenticationServiceSetup, AuthenticationServiceStart } from './authentication';
|
||||
import type { AuthorizationServiceSetup, AuthorizationServiceStart } from './authorization';
|
||||
import type { SecurityNavControlServiceStart } from './nav_control';
|
||||
import type { UserProfileAPIClient } from './user_profile';
|
||||
|
||||
|
@ -15,6 +16,10 @@ export interface SecurityPluginSetup {
|
|||
* Exposes authentication information about the currently logged in user.
|
||||
*/
|
||||
authc: AuthenticationServiceSetup;
|
||||
/**
|
||||
* Exposes authorization configuration.
|
||||
*/
|
||||
authz: AuthorizationServiceSetup;
|
||||
/**
|
||||
* Exposes information about the available security features under the current license.
|
||||
*/
|
||||
|
@ -30,6 +35,10 @@ export interface SecurityPluginStart {
|
|||
* Exposes authentication information about the currently logged in user.
|
||||
*/
|
||||
authc: AuthenticationServiceStart;
|
||||
/**
|
||||
* Exposes authorization configuration.
|
||||
*/
|
||||
authz: AuthorizationServiceStart;
|
||||
/**
|
||||
* A set of methods to work with Kibana user profiles.
|
||||
*/
|
||||
|
|
|
@ -33,4 +33,4 @@
|
|||
"remoteClusters"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import type {
|
||||
AuthenticationServiceSetup,
|
||||
AuthenticationServiceStart,
|
||||
AuthorizationServiceSetup,
|
||||
AuthorizationServiceStart,
|
||||
} from '@kbn/security-plugin-types-public';
|
||||
|
||||
export const authenticationMock = {
|
||||
|
@ -20,3 +22,12 @@ export const authenticationMock = {
|
|||
areAPIKeysEnabled: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const authorizationMock = {
|
||||
createSetup: (): jest.Mocked<AuthorizationServiceSetup> => ({
|
||||
isRoleManagementEnabled: jest.fn(),
|
||||
}),
|
||||
createStart: (): jest.Mocked<AuthorizationServiceStart> => ({
|
||||
isRoleManagementEnabled: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AuthorizationServiceSetup } from '@kbn/security-plugin-types-public';
|
||||
|
||||
import type { ConfigType } from '../config';
|
||||
|
||||
interface SetupParams {
|
||||
config: ConfigType;
|
||||
}
|
||||
|
||||
export class AuthorizationService {
|
||||
public setup({ config }: SetupParams): AuthorizationServiceSetup {
|
||||
const isRoleManagementEnabled = () => config.roleManagementEnabled;
|
||||
|
||||
return { isRoleManagementEnabled };
|
||||
}
|
||||
}
|
8
x-pack/plugins/security/public/authorization/index.ts
Normal file
8
x-pack/plugins/security/public/authorization/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AuthorizationService } from './authorization_service';
|
|
@ -25,6 +25,8 @@ export { ALL_SPACES_ID } from '../common/constants';
|
|||
export type {
|
||||
AuthenticationServiceStart,
|
||||
AuthenticationServiceSetup,
|
||||
AuthorizationServiceStart,
|
||||
AuthorizationServiceSetup,
|
||||
SecurityNavControlServiceStart,
|
||||
UserMenuLink,
|
||||
UserProfileBulkGetParams,
|
||||
|
|
|
@ -51,6 +51,7 @@ describe('ManagementService', () => {
|
|||
fatalErrors,
|
||||
authc,
|
||||
management: managementSetup,
|
||||
buildFlavor: 'traditional',
|
||||
});
|
||||
|
||||
expect(mockSection.registerApp).toHaveBeenCalledTimes(4);
|
||||
|
@ -111,6 +112,7 @@ describe('ManagementService', () => {
|
|||
fatalErrors,
|
||||
authc,
|
||||
management: managementSetup,
|
||||
buildFlavor: 'traditional',
|
||||
});
|
||||
|
||||
// Only API Keys app should be registered
|
||||
|
@ -181,6 +183,7 @@ describe('ManagementService', () => {
|
|||
fatalErrors,
|
||||
authc: securityMock.createSetup().authc,
|
||||
management: managementSetup,
|
||||
buildFlavor: 'traditional',
|
||||
});
|
||||
|
||||
const getMockedApp = (id: string) => {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { Subscription } from 'rxjs';
|
||||
|
||||
import type { BuildFlavor } from '@kbn/config';
|
||||
import type { Capabilities, FatalErrorsSetup, StartServicesAccessor } from '@kbn/core/public';
|
||||
import type {
|
||||
ManagementApp,
|
||||
|
@ -35,6 +36,7 @@ interface SetupParams {
|
|||
authc: AuthenticationServiceSetup;
|
||||
fatalErrors: FatalErrorsSetup;
|
||||
getStartServices: StartServicesAccessor<PluginStartDependencies>;
|
||||
buildFlavor: BuildFlavor;
|
||||
}
|
||||
|
||||
interface StartParams {
|
||||
|
@ -55,7 +57,7 @@ export class ManagementService {
|
|||
this.roleMappingManagementEnabled = config.ui?.roleMappingManagementEnabled !== false;
|
||||
}
|
||||
|
||||
setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) {
|
||||
setup({ getStartServices, management, authc, license, fatalErrors, buildFlavor }: SetupParams) {
|
||||
this.license = license;
|
||||
this.securitySection = management.sections.section.security;
|
||||
|
||||
|
@ -65,7 +67,7 @@ export class ManagementService {
|
|||
|
||||
if (this.roleManagementEnabled) {
|
||||
this.securitySection.registerApp(
|
||||
rolesManagementApp.create({ fatalErrors, license, getStartServices })
|
||||
rolesManagementApp.create({ fatalErrors, license, getStartServices, buildFlavor })
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ async function mountApp(basePath: string, pathname: string) {
|
|||
getStartServices: jest
|
||||
.fn()
|
||||
.mockResolvedValue([coreStart, { data: {}, features: featuresStart }]),
|
||||
buildFlavor: 'traditional',
|
||||
})
|
||||
.mount({
|
||||
basePath,
|
||||
|
@ -70,6 +71,7 @@ describe('rolesManagementApp', () => {
|
|||
license: licenseMock.create(),
|
||||
fatalErrors,
|
||||
getStartServices: getStartServices as any,
|
||||
buildFlavor: 'traditional',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import type { BuildFlavor } from '@kbn/config';
|
||||
import type { FatalErrorsSetup, StartServicesAccessor } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -29,14 +30,20 @@ interface CreateParams {
|
|||
fatalErrors: FatalErrorsSetup;
|
||||
license: SecurityLicense;
|
||||
getStartServices: StartServicesAccessor<PluginStartDependencies>;
|
||||
buildFlavor: BuildFlavor;
|
||||
}
|
||||
|
||||
export const rolesManagementApp = Object.freeze({
|
||||
id: 'roles',
|
||||
create({ license, fatalErrors, getStartServices }: CreateParams) {
|
||||
const title = i18n.translate('xpack.security.management.rolesTitle', {
|
||||
defaultMessage: 'Roles',
|
||||
});
|
||||
create({ license, fatalErrors, getStartServices, buildFlavor }: CreateParams) {
|
||||
const title =
|
||||
buildFlavor === 'serverless'
|
||||
? i18n.translate('xpack.security.management.rolesTitleServerless', {
|
||||
defaultMessage: 'Custom Roles',
|
||||
})
|
||||
: i18n.translate('xpack.security.management.rolesTitle', {
|
||||
defaultMessage: 'Roles',
|
||||
});
|
||||
return {
|
||||
id: this.id,
|
||||
order: 20,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { authenticationMock } from './authentication/index.mock';
|
||||
import { authenticationMock, authorizationMock } from './authentication/index.mock';
|
||||
import { navControlServiceMock } from './nav_control/index.mock';
|
||||
import { getUiApiMock } from './ui_api/index.mock';
|
||||
import { licenseMock } from '../common/licensing/index.mock';
|
||||
|
@ -17,12 +17,14 @@ import { mockAuthenticatedUser } from '../common/model/authenticated_user.mock';
|
|||
function createSetupMock() {
|
||||
return {
|
||||
authc: authenticationMock.createSetup(),
|
||||
authz: authorizationMock.createStart(),
|
||||
license: licenseMock.create(),
|
||||
};
|
||||
}
|
||||
function createStartMock() {
|
||||
return {
|
||||
authc: authenticationMock.createStart(),
|
||||
authz: authorizationMock.createStart(),
|
||||
navControlService: navControlServiceMock.createStart(),
|
||||
userProfiles: {
|
||||
getCurrent: jest.fn(),
|
||||
|
|
|
@ -36,6 +36,7 @@ describe('Security Plugin', () => {
|
|||
)
|
||||
).toEqual({
|
||||
authc: { getCurrentUser: expect.any(Function), areAPIKeysEnabled: expect.any(Function) },
|
||||
authz: { isRoleManagementEnabled: expect.any(Function) },
|
||||
license: {
|
||||
isLicenseAvailable: expect.any(Function),
|
||||
isEnabled: expect.any(Function),
|
||||
|
@ -75,6 +76,7 @@ describe('Security Plugin', () => {
|
|||
management: managementSetupMock,
|
||||
fatalErrors: coreSetupMock.fatalErrors,
|
||||
getStartServices: coreSetupMock.getStartServices,
|
||||
buildFlavor: expect.stringMatching(new RegExp('^serverless|traditional$')),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,6 +112,9 @@ describe('Security Plugin', () => {
|
|||
"areAPIKeysEnabled": [Function],
|
||||
"getCurrentUser": [Function],
|
||||
},
|
||||
"authz": Object {
|
||||
"isRoleManagementEnabled": [Function],
|
||||
},
|
||||
"navControlService": Object {
|
||||
"addUserMenuLinks": [Function],
|
||||
"getUserMenuLinks$": [Function],
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { BuildFlavor } from '@kbn/config';
|
||||
import type {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
|
@ -22,6 +23,8 @@ import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/pu
|
|||
import type {
|
||||
AuthenticationServiceSetup,
|
||||
AuthenticationServiceStart,
|
||||
AuthorizationServiceSetup,
|
||||
AuthorizationServiceStart,
|
||||
SecurityPluginSetup,
|
||||
SecurityPluginStart as SecurityPluginStartWithoutDeprecatedMembers,
|
||||
} from '@kbn/security-plugin-types-public';
|
||||
|
@ -32,6 +35,7 @@ import { accountManagementApp, UserProfileAPIClient } from './account_management
|
|||
import { AnalyticsService } from './analytics';
|
||||
import { AnonymousAccessService } from './anonymous_access';
|
||||
import { AuthenticationService } from './authentication';
|
||||
import { AuthorizationService } from './authorization';
|
||||
import { buildSecurityApi } from './build_security_api';
|
||||
import type { SecurityApiClients } from './components';
|
||||
import type { ConfigType } from './config';
|
||||
|
@ -72,6 +76,7 @@ export class SecurityPlugin
|
|||
private readonly config: ConfigType;
|
||||
private sessionTimeout?: SessionTimeout;
|
||||
private readonly authenticationService = new AuthenticationService();
|
||||
private readonly authorizationService = new AuthorizationService();
|
||||
private readonly navControlService;
|
||||
private readonly securityLicenseService = new SecurityLicenseService();
|
||||
private readonly managementService: ManagementService;
|
||||
|
@ -79,16 +84,17 @@ export class SecurityPlugin
|
|||
private readonly anonymousAccessService = new AnonymousAccessService();
|
||||
private readonly analyticsService = new AnalyticsService();
|
||||
private authc!: AuthenticationServiceSetup;
|
||||
private authz!: AuthorizationServiceSetup;
|
||||
private securityApiClients!: SecurityApiClients;
|
||||
private buildFlavor: BuildFlavor;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.buildFlavor = initializerContext.env.packageInfo.buildFlavor;
|
||||
|
||||
this.config = this.initializerContext.config.get<ConfigType>();
|
||||
this.securityCheckupService = new SecurityCheckupService(this.config, localStorage);
|
||||
this.navControlService = new SecurityNavControlService(
|
||||
initializerContext.env.packageInfo.buildFlavor
|
||||
);
|
||||
this.managementService = new ManagementService(
|
||||
this.initializerContext.config.get<ConfigType>()
|
||||
);
|
||||
this.navControlService = new SecurityNavControlService(this.buildFlavor);
|
||||
this.managementService = new ManagementService(this.config);
|
||||
}
|
||||
|
||||
public setup(
|
||||
|
@ -107,6 +113,10 @@ export class SecurityPlugin
|
|||
http: core.http,
|
||||
});
|
||||
|
||||
this.authz = this.authorizationService.setup({
|
||||
config: this.config,
|
||||
});
|
||||
|
||||
this.securityApiClients = {
|
||||
userProfiles: new UserProfileAPIClient(core.http),
|
||||
users: new UserAPIClient(core.http),
|
||||
|
@ -142,6 +152,7 @@ export class SecurityPlugin
|
|||
authc: this.authc,
|
||||
fatalErrors: core.fatalErrors,
|
||||
getStartServices: core.getStartServices,
|
||||
buildFlavor: this.buildFlavor,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -168,6 +179,7 @@ export class SecurityPlugin
|
|||
|
||||
return {
|
||||
authc: this.authc,
|
||||
authz: this.authz,
|
||||
license,
|
||||
};
|
||||
}
|
||||
|
@ -205,6 +217,7 @@ export class SecurityPlugin
|
|||
uiApi: getUiApi({ core }),
|
||||
navControlService: this.navControlService.start({ core, authc: this.authc }),
|
||||
authc: this.authc as AuthenticationServiceStart,
|
||||
authz: this.authz as AuthorizationServiceStart,
|
||||
userProfiles: {
|
||||
getCurrent: this.securityApiClients.userProfiles.getCurrent.bind(
|
||||
this.securityApiClients.userProfiles
|
||||
|
|
|
@ -24,27 +24,31 @@ export const enableManagementCardsLanding = (services: Services) => {
|
|||
const { management, application } = services;
|
||||
|
||||
services.getProjectNavLinks$().subscribe((projectNavLinks) => {
|
||||
const extendCardNavDefinitions = projectNavLinks.reduce<
|
||||
Record<string, CardNavExtensionDefinition>
|
||||
>((acc, projectNavLink) => {
|
||||
if (SecurityManagementCards.has(projectNavLink.id)) {
|
||||
const { appId, deepLinkId, path } = getNavigationPropsFromId(projectNavLink.id);
|
||||
const cardNavDefinitions = projectNavLinks.reduce<Record<string, CardNavExtensionDefinition>>(
|
||||
(acc, projectNavLink) => {
|
||||
if (SecurityManagementCards.has(projectNavLink.id)) {
|
||||
const { appId, deepLinkId, path } = getNavigationPropsFromId(projectNavLink.id);
|
||||
|
||||
acc[projectNavLink.id] = {
|
||||
category: SecurityManagementCards.get(projectNavLink.id) ?? 'other',
|
||||
title: projectNavLink.title,
|
||||
description: projectNavLink.description ?? '',
|
||||
icon: projectNavLink.landingIcon ?? '',
|
||||
href: application.getUrlForApp(appId, { deepLinkId, path }),
|
||||
skipValidation: true,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
acc[projectNavLink.id] = {
|
||||
category: SecurityManagementCards.get(projectNavLink.id) ?? 'other',
|
||||
title: projectNavLink.title,
|
||||
description: projectNavLink.description ?? '',
|
||||
icon: projectNavLink.landingIcon ?? '',
|
||||
href: application.getUrlForApp(appId, { deepLinkId, path }),
|
||||
skipValidation: true,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
extendCardNavDefinitions,
|
||||
extendCardNavDefinitions: services.serverless.getNavigationCards(
|
||||
services.security.authz.isRoleManagementEnabled(),
|
||||
cardNavDefinitions
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -46,6 +46,6 @@
|
|||
"@kbn/actions-plugin",
|
||||
"@kbn/management-cards-navigation",
|
||||
"@kbn/core-application-browser",
|
||||
"@kbn/discover-plugin"
|
||||
"@kbn/discover-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const startMock = (): ServerlessPluginStart => ({
|
|||
setBreadcrumbs: jest.fn(),
|
||||
setProjectHome: jest.fn(),
|
||||
setSideNavComponentDeprecated: jest.fn(),
|
||||
getNavigationCards: jest.fn(),
|
||||
});
|
||||
|
||||
export const serverlessMock = {
|
||||
|
|
|
@ -16,3 +16,5 @@ export const SideNavComponent: FC<NavigationProps> = (props) => (
|
|||
<SideNavComponentLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export { manageOrgMembersNavCardName, generateManageOrgMembersNavCard } from './nav_cards';
|
||||
|
|
26
x-pack/plugins/serverless/public/navigation/nav_cards.ts
Normal file
26
x-pack/plugins/serverless/public/navigation/nav_cards.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { appCategories, CardNavExtensionDefinition } from '@kbn/management-cards-navigation';
|
||||
|
||||
export const manageOrgMembersNavCardName = 'organization_members';
|
||||
|
||||
export function generateManageOrgMembersNavCard(cloudOrgUrl?: string): CardNavExtensionDefinition {
|
||||
return {
|
||||
category: appCategories.ACCESS,
|
||||
description: i18n.translate('xpack.serverless.nav.manageOrgMembersDescription', {
|
||||
defaultMessage: 'Invite team members and assign them roles to access this project.',
|
||||
}),
|
||||
icon: 'users',
|
||||
skipValidation: true,
|
||||
href: cloudOrgUrl ?? '',
|
||||
title: i18n.translate('xpack.serverless.nav.manageOrgMembersTitle', {
|
||||
defaultMessage: 'Manage organization members',
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -15,7 +15,11 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import { API_SWITCH_PROJECT as projectChangeAPIUrl } from '../common';
|
||||
import { ServerlessConfig } from './config';
|
||||
import { SideNavComponent } from './navigation';
|
||||
import {
|
||||
generateManageOrgMembersNavCard,
|
||||
manageOrgMembersNavCardName,
|
||||
SideNavComponent,
|
||||
} from './navigation';
|
||||
import {
|
||||
ServerlessPluginSetup,
|
||||
ServerlessPluginSetupDependencies,
|
||||
|
@ -95,6 +99,16 @@ export class ServerlessPlugin
|
|||
},
|
||||
setBreadcrumbs: (breadcrumbs, params) => project.setBreadcrumbs(breadcrumbs, params),
|
||||
setProjectHome: (homeHref: string) => project.setHome(homeHref),
|
||||
getNavigationCards: (roleManagementEnabled, extendCardNavDefinitions) => {
|
||||
if (!roleManagementEnabled) return extendCardNavDefinitions;
|
||||
|
||||
const manageOrgMembersNavCard = generateManageOrgMembersNavCard(cloud.organizationUrl);
|
||||
if (extendCardNavDefinitions) {
|
||||
extendCardNavDefinitions[manageOrgMembersNavCardName] = manageOrgMembersNavCard;
|
||||
return extendCardNavDefinitions;
|
||||
}
|
||||
return { [manageOrgMembersNavCardName]: manageOrgMembersNavCard };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
|||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation';
|
||||
import { CardNavExtensionDefinition } from '@kbn/management-cards-navigation';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginSetup {}
|
||||
|
@ -35,6 +36,10 @@ export interface ServerlessPluginStart {
|
|||
* @deprecated Use {@link ServerlessPluginStart.initNavigation} instead.
|
||||
*/
|
||||
setSideNavComponentDeprecated: (navigation: SideNavComponent) => void;
|
||||
getNavigationCards(
|
||||
roleManagementEnabled?: boolean,
|
||||
extendCardNavDefinitions?: Record<string, CardNavExtensionDefinition>
|
||||
): Record<string, CardNavExtensionDefinition> | undefined;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
|
|
|
@ -26,5 +26,7 @@
|
|||
"@kbn/cloud-plugin",
|
||||
"@kbn/serverless-common-settings",
|
||||
"@kbn/shared-ux-chrome-navigation",
|
||||
"@kbn/i18n",
|
||||
"@kbn/management-cards-navigation",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"observabilityShared",
|
||||
"management",
|
||||
"discover",
|
||||
"security",
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { appIds } from '@kbn/management-cards-navigation';
|
||||
import { appCategories } from '@kbn/management-cards-navigation/src/types';
|
||||
import { appCategories, appIds } from '@kbn/management-cards-navigation';
|
||||
import { of } from 'rxjs';
|
||||
import { navigationTree } from './navigation_tree';
|
||||
import { createObservabilityDashboardRegistration } from './logs_signal/overview_registration';
|
||||
|
@ -52,16 +51,15 @@ export class ServerlessObservabilityPlugin
|
|||
core: CoreStart,
|
||||
setupDeps: ServerlessObservabilityPublicStartDependencies
|
||||
): ServerlessObservabilityPublicStart {
|
||||
const { serverless, management } = setupDeps;
|
||||
const { serverless, management, security } = setupDeps;
|
||||
|
||||
const navigationTree$ = of(navigationTree);
|
||||
serverless.setProjectHome('/app/observability/landing');
|
||||
serverless.initNavigation(navigationTree$, { dataTestSubj: 'svlObservabilitySideNav' });
|
||||
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
hideLinksTo: [appIds.RULES],
|
||||
extendCardNavDefinitions: {
|
||||
const extendCardNavDefinitions = serverless.getNavigationCards(
|
||||
security.authz.isRoleManagementEnabled(),
|
||||
{
|
||||
aiAssistantManagementObservability: {
|
||||
category: appCategories.OTHER,
|
||||
title: i18n.translate('xpack.serverlessObservability.aiAssistantManagementTitle', {
|
||||
|
@ -75,8 +73,14 @@ export class ServerlessObservabilityPlugin
|
|||
),
|
||||
icon: 'sparkles',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
hideLinksTo: [appIds.RULES],
|
||||
extendCardNavDefinitions,
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
ObservabilitySharedPluginSetup,
|
||||
ObservabilitySharedPluginStart,
|
||||
} from '@kbn/observability-shared-plugin/public';
|
||||
import { SecurityPluginStart } from '@kbn/security-plugin/public';
|
||||
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -34,4 +35,5 @@ export interface ServerlessObservabilityPublicStartDependencies {
|
|||
serverless: ServerlessPluginStart;
|
||||
management: ManagementStart;
|
||||
data: DataPublicPluginStart;
|
||||
security: SecurityPluginStart;
|
||||
}
|
||||
|
|
|
@ -28,5 +28,6 @@
|
|||
"@kbn/serverless-observability-settings",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/discover-plugin",
|
||||
"@kbn/security-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -121,16 +121,22 @@ export class ServerlessSearchPlugin
|
|||
core: CoreStart,
|
||||
services: ServerlessSearchPluginStartDependencies
|
||||
): ServerlessSearchPluginStart {
|
||||
const { serverless, management, indexManagement } = services;
|
||||
const { serverless, management, indexManagement, security } = services;
|
||||
serverless.setProjectHome('/app/elasticsearch');
|
||||
|
||||
const navigationTree$ = of(navigationTree);
|
||||
serverless.initNavigation(navigationTree$, { dataTestSubj: 'svlSearchSideNav' });
|
||||
|
||||
const extendCardNavDefinitions = serverless.getNavigationCards(
|
||||
security.authz.isRoleManagementEnabled()
|
||||
);
|
||||
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
hideLinksTo: [appIds.MAINTENANCE_WINDOWS],
|
||||
extendCardNavDefinitions,
|
||||
});
|
||||
|
||||
indexManagement?.extensionsService.setIndexMappingsContent(createIndexMappingsContent(core));
|
||||
indexManagement?.extensionsService.addIndexDetailsTab(
|
||||
createIndexDocumentsContent(core, services)
|
||||
|
|
|
@ -11,16 +11,37 @@ export function SvlManagementPageProvider({ getService }: FtrProviderContext) {
|
|||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
// API keys card
|
||||
async assertApiKeysManagementCardExists() {
|
||||
await testSubjects.existOrFail('app-card-api_keys');
|
||||
},
|
||||
async assertApiKeysManagementCardDoesNotExist() {
|
||||
await testSubjects.missingOrFail('app-card-api_keys');
|
||||
},
|
||||
async clickApiKeysManagementCard() {
|
||||
await testSubjects.click('app-card-api_keys');
|
||||
},
|
||||
|
||||
// Roles card
|
||||
async assertRoleManagementCardExists() {
|
||||
await testSubjects.existOrFail('app-card-roles');
|
||||
},
|
||||
|
||||
async assertRoleManagementCardDoesNotExist() {
|
||||
await testSubjects.missingOrFail('app-card-roles');
|
||||
},
|
||||
|
||||
async clickRoleManagementCard() {
|
||||
await testSubjects.click('app-card-roles');
|
||||
},
|
||||
|
||||
// Organization members card
|
||||
async assertOrgMembersManagementCardExists() {
|
||||
await testSubjects.existOrFail('app-card-organization_members');
|
||||
},
|
||||
async assertOrgMembersManagementCardDoesNotExist() {
|
||||
await testSubjects.missingOrFail('app-card-organization_members');
|
||||
},
|
||||
async clickOrgMembersManagementCard() {
|
||||
await testSubjects.click('app-card-organization_members');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
this.tags('smoke');
|
||||
before(async () => {
|
||||
await pageObjects.svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
});
|
||||
|
||||
|
@ -37,12 +40,29 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('navigates to API keys management by clicking the card', async () => {
|
||||
await testSubjects.click('app-card-api_keys');
|
||||
expect(async () => {
|
||||
await pageObjects.common.waitUntilUrlIncludes('/app/management/security/api_keys');
|
||||
}).not.to.throwError();
|
||||
});
|
||||
|
||||
describe('Roles management card', () => {
|
||||
it('should not be displayed by default', async () => {
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardDoesNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Organization members management card', () => {
|
||||
it('should not be displayed by default', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
await pageObjects.svlManagementPage.assertOrgMembersManagementCardDoesNotExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'svlManagementPage']);
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
|
||||
describe('Management navigation cards', function () {
|
||||
this.tags('smoke');
|
||||
|
||||
describe('as Admin', function () {
|
||||
before(async () => {
|
||||
await pageObjects.svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
});
|
||||
|
||||
it('renders the page', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain(`/management`);
|
||||
});
|
||||
|
||||
it('displays the API keys management card, and will navigate to the API keys UI', async () => {
|
||||
await pageObjects.svlManagementPage.assertApiKeysManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickApiKeysManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/api_keys');
|
||||
});
|
||||
|
||||
it('displays the roles management card, and will navigate to the Roles UI', async () => {
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickRoleManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/roles');
|
||||
});
|
||||
|
||||
it('displays the Organization members management card, and will navigate to the cloud organization URL', async () => {
|
||||
await pageObjects.svlManagementPage.assertOrgMembersManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickOrgMembersManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
// `--xpack.cloud.organization_url: '/account/members'`,
|
||||
expect(url).to.contain('/account/members');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', function () {
|
||||
before(async () => {
|
||||
await pageObjects.svlCommonPage.loginWithRole('viewer');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
});
|
||||
|
||||
it('renders the page', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain(`/management`);
|
||||
});
|
||||
|
||||
it('should not display the roles manangement card', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardDoesNotExist();
|
||||
});
|
||||
|
||||
it('displays the organization members management card, and will navigate to the cloud organization URL', async () => {
|
||||
// The org members nav card is always visible because there is no way to check if a user has approprite privileges
|
||||
await pageObjects.svlManagementPage.assertOrgMembersManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickOrgMembersManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
// `--xpack.cloud.organization_url: '/account/members'`,
|
||||
expect(url).to.contain('/account/members');
|
||||
});
|
||||
|
||||
describe('API keys management card - search solution', function () {
|
||||
this.tags(['skipSvlOblt', 'skipSvlSec']);
|
||||
|
||||
it('displays the API keys management card, and will navigate to the API keys UI (search only)', async () => {
|
||||
await pageObjects.svlManagementPage.assertApiKeysManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickApiKeysManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/api_keys');
|
||||
});
|
||||
});
|
||||
|
||||
describe('API keys management card - oblt & sec solutions', function () {
|
||||
this.tags(['skipSvlSearch']);
|
||||
|
||||
it('should not display the API keys manangement card (oblt & security only)', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
await pageObjects.svlManagementPage.assertApiKeysManagementCardDoesNotExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,41 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'svlManagementPage']);
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
|
||||
describe('Roles management card', function () {
|
||||
this.tags('smoke');
|
||||
before(async () => {
|
||||
// Navigate to the index management page
|
||||
await pageObjects.svlCommonPage.loginAsAdmin();
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
});
|
||||
|
||||
it('renders the page, displays the Roles card, and will navigate to the Roles UI', async () => {
|
||||
await retry.waitFor('page to be visible', async () => {
|
||||
return await testSubjects.exists('cards-navigation-page');
|
||||
});
|
||||
|
||||
let url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain(`/management`);
|
||||
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardExists();
|
||||
|
||||
await pageObjects.svlManagementPage.clickRoleManagementCard();
|
||||
|
||||
url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/roles');
|
||||
});
|
||||
});
|
||||
};
|
|
@ -22,6 +22,8 @@ export default createTestConfig({
|
|||
'--xpack.infra.enabled=true',
|
||||
'--xpack.infra.featureFlags.customThresholdAlertsEnabled=true',
|
||||
'--xpack.security.roleManagementEnabled=true',
|
||||
`--xpack.cloud.base_url='https://cloud.elastic.co'`,
|
||||
`--xpack.cloud.organization_url='/account/members'`,
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
|
|
@ -11,6 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
describe('serverless observability UI - feature flags', function () {
|
||||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
loadTestFile(require.resolve('./infra'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,11 @@ export default createTestConfig({
|
|||
},
|
||||
suiteTags: { exclude: ['skipSvlSearch'] },
|
||||
// add feature flags
|
||||
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
|
||||
kbnServerArgs: [
|
||||
`--xpack.security.roleManagementEnabled=true`,
|
||||
`--xpack.cloud.base_url='https://cloud.elastic.co'`,
|
||||
`--xpack.cloud.organization_url='/account/members'`,
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
|
|
|
@ -10,6 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless search UI - feature flags', function () {
|
||||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,11 @@ export default createTestConfig({
|
|||
},
|
||||
suiteTags: { exclude: ['skipSvlSec'] },
|
||||
// add feature flags
|
||||
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
|
||||
kbnServerArgs: [
|
||||
`--xpack.security.roleManagementEnabled=true`,
|
||||
`--xpack.cloud.base_url='https://cloud.elastic.co'`,
|
||||
`--xpack.cloud.organization_url='/account/members'`,
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
|
|
|
@ -10,6 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless security UI - feature flags', function () {
|
||||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue