Skip to content

Commit

Permalink
[7.x] Migrate config deprecations and ShieldUser functionality to t…
Browse files Browse the repository at this point in the history
…he New Platform (#54004)
  • Loading branch information
azasypkin authored Jan 6, 2020
1 parent d7789f2 commit 12f4761
Show file tree
Hide file tree
Showing 28 changed files with 327 additions and 209 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIRoutes } from 'ui/routes';
import { isLeft } from 'fp-ts/lib/Either';
import { npSetup } from 'ui/new_platform';
import { SecurityPluginSetup } from '../../../../../../../plugins/security/public';
import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types';
import {
FrameworkAdapter,
Expand Down Expand Up @@ -58,7 +60,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
};

public async waitUntilFrameworkReady(): Promise<void> {
const $injector = await this.onKibanaReady();
await this.onKibanaReady();
const xpackInfo: any = this.xpackInfoService;
let xpackInfoUnpacked: FrameworkInfo;

Expand Down Expand Up @@ -95,8 +97,10 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
}
this.xpackInfo = xpackInfoUnpacked;

const securitySetup = ((npSetup.plugins as unknown) as { security?: SecurityPluginSetup })
.security;
try {
this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise;
this.shieldUser = (await securitySetup?.authc.getCurrentUser()) || null;
const assertUser = RuntimeFrameworkUser.decode(this.shieldUser);

if (isLeft(assertUser)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
import { render } from 'react-dom';
import { isEmpty } from 'lodash';
import { uiModules } from 'ui/modules';
import { npSetup } from 'ui/new_platform';
import { toastNotifications } from 'ui/notify';
import { I18nContext } from 'ui/i18n';
import { PipelineEditor } from '../../../../components/pipeline_editor';
Expand All @@ -21,7 +22,6 @@ app.directive('pipelineEdit', function($injector) {
const pipelineService = $injector.get('pipelineService');
const licenseService = $injector.get('logstashLicenseService');
const kbnUrl = $injector.get('kbnUrl');
const shieldUser = $injector.get('ShieldUser');
const $route = $injector.get('$route');

return {
Expand All @@ -32,7 +32,7 @@ app.directive('pipelineEdit', function($injector) {
scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`));

const userResource = logstashSecurity.isSecurityEnabled()
? await shieldUser.getCurrent().$promise
? await npSetup.plugins.security.authc.getCurrentUser()
: null;

render(
Expand Down
15 changes: 3 additions & 12 deletions x-pack/legacy/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,20 @@ export const security = kibana =>
enabled: Joi.boolean().default(true),
cookieName: HANDLED_IN_NEW_PLATFORM,
encryptionKey: HANDLED_IN_NEW_PLATFORM,
session: Joi.object({
idleTimeout: HANDLED_IN_NEW_PLATFORM,
lifespan: HANDLED_IN_NEW_PLATFORM,
}).default(),
session: HANDLED_IN_NEW_PLATFORM,
secureCookies: HANDLED_IN_NEW_PLATFORM,
public: HANDLED_IN_NEW_PLATFORM,
loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM,
authorization: Joi.object({
legacyFallback: Joi.object({
enabled: Joi.boolean().default(true), // deprecated
}).default(),
}).default(),
authorization: HANDLED_IN_NEW_PLATFORM,
audit: Joi.object({
enabled: Joi.boolean().default(false),
}).default(),
authc: HANDLED_IN_NEW_PLATFORM,
}).default();
},

deprecations: function({ rename, unused }) {
deprecations: function({ rename }) {
return [
unused('authorization.legacyFallback.enabled'),
rename('sessionTimeout', 'session.idleTimeout'),
rename('authProviders', 'authc.providers'),
(settings, log) => {
const hasSAMLProvider = get(settings, 'authc.providers', []).includes('saml');
Expand Down
6 changes: 1 addition & 5 deletions x-pack/legacy/plugins/security/public/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
*/

import { kfetch } from 'ui/kfetch';
import { AuthenticatedUser, Role, User, EditUser } from '../../common/model';
import { Role, User, EditUser } from '../../common/model';

const usersUrl = '/internal/security/users';
const rolesUrl = '/api/security/role';

export class UserAPIClient {
public async getCurrentUser(): Promise<AuthenticatedUser> {
return await kfetch({ pathname: `/internal/security/me` });
}

public async getUsers(): Promise<User[]> {
return await kfetch({ pathname: usersUrl });
}
Expand Down
33 changes: 0 additions & 33 deletions x-pack/legacy/plugins/security/public/services/shield_user.js

This file was deleted.

26 changes: 8 additions & 18 deletions x-pack/legacy/plugins/security/public/views/account/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,13 @@

import routes from 'ui/routes';
import template from './account.html';
import '../../services/shield_user';
import { i18n } from '@kbn/i18n';
import { I18nContext } from 'ui/i18n';
import { npSetup } from 'ui/new_platform';
import { AccountManagementPage } from './components';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';

const renderReact = (elem, user) => {
render(
<I18nContext>
<AccountManagementPage user={user} />
</I18nContext>,
elem
);
};

routes.when('/account', {
template,
k7Breadcrumbs: () => [
Expand All @@ -31,22 +22,21 @@ routes.when('/account', {
}),
},
],
resolve: {
user(ShieldUser) {
return ShieldUser.getCurrent().$promise;
},
},
controllerAs: 'accountController',
controller($scope, $route) {
controller($scope) {
$scope.$on('$destroy', () => {
const elem = document.getElementById('userProfileReactRoot');
if (elem) {
unmountComponentAtNode(elem);
}
});
$scope.$$postDigest(() => {
const elem = document.getElementById('userProfileReactRoot');
renderReact(elem, $route.current.locals.user);
render(
<I18nContext>
<AccountManagementPage securitySetup={npSetup.plugins.security} />
</I18nContext>,
document.getElementById('userProfileReactRoot')
);
});
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { act } from '@testing-library/react';
import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { securityMock } from '../../../../../../../plugins/security/public/mocks';
import { AccountManagementPage } from './account_management_page';
import { AuthenticatedUser } from '../../../../common/model';

jest.mock('ui/kfetch');

Expand All @@ -32,39 +35,85 @@ const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }:
};
};

function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) {
const securitySetupMock = securityMock.createSetup();
securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser);
return securitySetupMock;
}

describe('<AccountManagementPage>', () => {
it(`displays users full name, username, and email address`, () => {
it(`displays users full name, username, and email address`, async () => {
const user = createUser();
const wrapper = mountWithIntl(<AccountManagementPage user={user} />);
const wrapper = mountWithIntl(
<AccountManagementPage securitySetup={getSecuritySetupMock({ currentUser: user })} />
);

await act(async () => {
await nextTick();
wrapper.update();
});

expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(
user.full_name
);
expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username);
expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email);
});

it(`displays username when full_name is not provided`, () => {
it(`displays username when full_name is not provided`, async () => {
const user = createUser({ withFullName: false });
const wrapper = mountWithIntl(<AccountManagementPage user={user} />);
const wrapper = mountWithIntl(
<AccountManagementPage securitySetup={getSecuritySetupMock({ currentUser: user })} />
);

await act(async () => {
await nextTick();
wrapper.update();
});

expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username);
});

it(`displays a placeholder when no email address is provided`, () => {
it(`displays a placeholder when no email address is provided`, async () => {
const user = createUser({ withEmail: false });
const wrapper = mountWithIntl(<AccountManagementPage user={user} />);
const wrapper = mountWithIntl(
<AccountManagementPage securitySetup={getSecuritySetupMock({ currentUser: user })} />
);

await act(async () => {
await nextTick();
wrapper.update();
});

expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address');
});

it(`displays change password form for users in the native realm`, () => {
it(`displays change password form for users in the native realm`, async () => {
const user = createUser();
const wrapper = mountWithIntl(<AccountManagementPage user={user} />);
const wrapper = mountWithIntl(
<AccountManagementPage securitySetup={getSecuritySetupMock({ currentUser: user })} />
);

await act(async () => {
await nextTick();
wrapper.update();
});

expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(1);
expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(1);
});

it(`does not display change password form for users in the saml realm`, () => {
it(`does not display change password form for users in the saml realm`, async () => {
const user = createUser({ realm: 'saml' });
const wrapper = mountWithIntl(<AccountManagementPage user={user} />);
const wrapper = mountWithIntl(
<AccountManagementPage securitySetup={getSecuritySetupMock({ currentUser: user })} />
);

await act(async () => {
await nextTick();
wrapper.update();
});

expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0);
expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,41 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { SecurityPluginSetup } from '../../../../../../../plugins/security/public';
import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model';
import { ChangePassword } from './change_password';
import { PersonalInfo } from './personal_info';

interface Props {
user: AuthenticatedUser;
securitySetup: SecurityPluginSetup;
}

export const AccountManagementPage: React.FC<Props> = props => (
<EuiPage>
<EuiPageBody restrictWidth>
<EuiPanel>
<EuiText data-test-subj={'userDisplayName'}>
<h1>{getUserDisplayName(props.user)}</h1>
</EuiText>
export const AccountManagementPage = (props: Props) => {
const [currentUser, setCurrentUser] = useState<AuthenticatedUser | null>(null);
useEffect(() => {
props.securitySetup.authc.getCurrentUser().then(setCurrentUser);
}, [props]);

<EuiSpacer size="xl" />
if (!currentUser) {
return null;
}

<PersonalInfo user={props.user} />
return (
<EuiPage>
<EuiPageBody restrictWidth>
<EuiPanel>
<EuiText data-test-subj={'userDisplayName'}>
<h1>{getUserDisplayName(currentUser)}</h1>
</EuiText>

<ChangePassword user={props.user} />
</EuiPanel>
</EuiPageBody>
</EuiPage>
);
<EuiSpacer size="xl" />

<PersonalInfo user={currentUser} />

<ChangePassword user={currentUser} />
</EuiPanel>
</EuiPageBody>
</EuiPage>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { kfetch } from 'ui/kfetch';
import { fatalError, toastNotifications } from 'ui/notify';
import { npStart } from 'ui/new_platform';
import template from 'plugins/security/views/management/edit_role/edit_role.html';
import 'plugins/security/services/shield_user';
import 'plugins/security/services/shield_role';
import 'plugins/security/services/shield_indices';
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
import { UserAPIClient } from '../../../lib/api';
import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls';
import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs';

Expand Down Expand Up @@ -69,9 +69,8 @@ const routeDefinition = action => ({

return role.then(res => res.toJSON());
},
users(ShieldUser) {
// $promise is used here because the result is an ngResource, not a promise itself
return ShieldUser.query().$promise.then(users => _.map(users, 'username'));
users() {
return new UserAPIClient().getUsers().then(users => _.map(users, 'username'));
},
indexPatterns() {
return npStart.plugins.data.indexPatterns.getTitles();
Expand Down
Loading

0 comments on commit 12f4761

Please sign in to comment.