[7.x] Security - remove usage of legacy notifier (#38260) (#38452)

* remove usage of legacy notifier

* fix superfluous whitespace changes

* fix redirection on role management screen

* extract session expiration warning into its own component
This commit is contained in:
Larry Gregory 2019-06-07 16:51:37 -04:00 committed by GitHub
parent f7236a96e8
commit 00f220df0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 117 additions and 57 deletions

View file

@ -0,0 +1,7 @@
/*
* 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 { SessionExpirationWarning } from './session_expiration_warning';

View file

@ -0,0 +1,19 @@
/*
* 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 React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { SessionExpirationWarning } from './session_expiration_warning';
describe('SessionExpirationWarning', () => {
it('fires its callback when the OK button is clicked', () => {
const handler = jest.fn();
const wrapper = mountWithIntl(<SessionExpirationWarning onRefreshSession={handler} />);
expect(handler).toBeCalledTimes(0);
wrapper.find('EuiButton[data-test-subj="refreshSessionButton"]').simulate('click');
expect(handler).toBeCalledTimes(1);
});
});

View file

@ -0,0 +1,39 @@
/*
* 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 React from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
interface Props {
onRefreshSession: () => void;
}
export const SessionExpirationWarning = (props: Props) => {
return (
<>
<p>
<FormattedMessage
id="xpack.security.components.sessionExpiration.logoutNotification"
defaultMessage="You will soon be logged out due to inactivity. Click OK to resume."
/>
</p>
<div className="eui-textRight">
<EuiButton
size="s"
color="warning"
onClick={props.onRefreshSession}
data-test-subj="refreshSessionButton"
>
<FormattedMessage
id="xpack.security.components.sessionExpiration.okButtonText"
defaultMessage="OK"
/>
</EuiButton>
</div>
</>
);
};

View file

@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { uiModules } from 'ui/modules';
import { isSystemApiRequest } from 'ui/system_api';
import { PathProvider } from 'plugins/xpack_main/services/path';
import { toastNotifications } from 'ui/notify';
import 'plugins/security/services/auto_logout';
import { SessionExpirationWarning } from '../components/session_expiration_warning';
/**
* Client session timeout is decreased by this number so that Kibana server
@ -27,35 +30,40 @@ module.config(($httpProvider) => {
$q,
$injector,
sessionTimeout,
Notifier,
Private,
autoLogout
) => {
function refreshSession() {
// Make a simple request to keep the session alive
$injector.get('es').ping();
clearNotifications();
}
const isUnauthenticated = Private(PathProvider).isUnauthenticated();
const notifier = new Notifier();
const notificationLifetime = 60 * 1000;
const notificationOptions = {
type: 'warning',
content: i18n.translate('xpack.security.hacks.logoutNotification', {
defaultMessage: 'You will soon be logged out due to inactivity. Click OK to resume.'
}),
icon: 'warning',
color: 'warning',
text: (
<SessionExpirationWarning onRefreshSession={refreshSession} />
),
title: i18n.translate('xpack.security.hacks.warningTitle', {
defaultMessage: 'Warning'
}),
lifetime: Math.min(
toastLifeTimeMs: Math.min(
(sessionTimeout - SESSION_TIMEOUT_GRACE_PERIOD_MS),
notificationLifetime
),
actions: ['accept']
};
let pendingNotification;
let activeNotification;
let pendingSessionExpiration;
function clearNotifications() {
if (pendingNotification) $timeout.cancel(pendingNotification);
if (activeNotification) activeNotification.clear();
if (pendingSessionExpiration) clearTimeout(pendingSessionExpiration);
if (activeNotification) toastNotifications.remove(activeNotification);
}
function scheduleNotification() {
@ -63,14 +71,8 @@ module.config(($httpProvider) => {
}
function showNotification() {
activeNotification = notifier.add(notificationOptions, (action) => {
if (action === 'accept') {
// Make a simple request to keep the session alive
$injector.get('es').ping();
} else {
autoLogout();
}
});
activeNotification = toastNotifications.add(notificationOptions);
pendingSessionExpiration = setTimeout(() => autoLogout(), notificationOptions.toastLifeTimeMs);
}
function interceptorFactory(responseHandler) {

View file

@ -1,22 +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 { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import { Notifier } from 'ui/notify';
export function checkLicenseError(kbnUrl, Promise, Private) {
const xpackInfo = Private(XPackInfoProvider);
const genericNotifier = new Notifier({ location: 'Security' });
return err => {
if (!xpackInfo.get('features.security.showLinks')) {
genericNotifier.error(xpackInfo.get('features.security.linksMessage'));
kbnUrl.redirect('/management');
return Promise.halt();
}
return Promise.reject(err);
};
}

View file

@ -8,7 +8,7 @@ import _ from 'lodash';
import routes from 'ui/routes';
import { capabilities } from 'ui/capabilities';
import { kfetch } from 'ui/kfetch';
import { fatalError } from 'ui/notify';
import { fatalError, toastNotifications } from 'ui/notify';
import template from 'plugins/security/views/management/edit_role/edit_role.html';
import 'ui/angular_ui_select';
import 'plugins/security/services/application_privilege';
@ -19,7 +19,6 @@ import 'plugins/security/services/shield_indices';
import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import { SpacesManager } from '../../../../../spaces/public/lib';
import { checkLicenseError } from 'plugins/security/lib/check_license_error';
import { EDIT_ROLES_PATH, ROLES_PATH } from '../management_urls';
import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs';
@ -28,6 +27,7 @@ import { EditRolePage } from './components';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nContext } from 'ui/i18n';
import { i18n } from '@kbn/i18n';
routes.when(`${EDIT_ROLES_PATH}/:name?`, {
template,
@ -37,7 +37,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
: getCreateRoleBreadcrumbs
),
resolve: {
role($route, ShieldRole, kbnUrl, Promise, Notifier) {
role($route, ShieldRole, Promise, kbnUrl) {
const name = $route.current.params.name;
let role;
@ -45,17 +45,19 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
if (name != null) {
role = ShieldRole.get({ name }).$promise
.catch((response) => {
if (response.status !== 404) {
if (response.status === 404) {
toastNotifications.addDanger({
title: i18n.translate('xpack.security.management.roles.roleNotFound',
{
defaultMessage: 'No "{roleName}" role found.',
values: { roleName: name }
}),
});
kbnUrl.redirect(ROLES_PATH);
} else {
return fatalError(response);
}
const notifier = new Notifier();
notifier.error(`No "${name}" role found.`);
kbnUrl.redirect(ROLES_PATH);
return Promise.halt();
});
} else {
role = Promise.resolve(new ShieldRole({
elasticsearch: {
@ -70,11 +72,10 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
return role.then(res => res.toJSON());
},
users(ShieldUser, kbnUrl, Promise, Private) {
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'))
.catch(checkLicenseError(kbnUrl, Promise, Private));
.then(users => _.map(users, 'username'));
},
indexPatterns(Private) {
const indexPatterns = Private(IndexPatternsProvider);

View file

@ -17,8 +17,22 @@ import { ROLES_PATH, USERS_PATH } from './management_urls';
import { management } from 'ui/management';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
routes.defaults(/\/management/, {
routes.defaults(/^\/management\/security(\/|$)/, {
resolve: {
showLinks(kbnUrl, Promise, Private) {
const xpackInfo = Private(XPackInfoProvider);
if (!xpackInfo.get('features.security.showLinks')) {
toastNotifications.addDanger({
title: xpackInfo.get('features.security.linksMessage')
});
kbnUrl.redirect('/management');
return Promise.halt();
}
}
}
}).defaults(/\/management/, {
resolve: {
securityManagementSection: function (ShieldUser, Private) {
const xpackInfo = Private(XPackInfoProvider);

View file

@ -8651,7 +8651,7 @@
"xpack.security.account.passwordsDoNotMatch": "パスワードが一致していません。",
"xpack.security.account.usernameGroupDescription": "この情報は変更できません。",
"xpack.security.account.usernameGroupTitle": "ユーザー名とメールアドレス",
"xpack.security.hacks.logoutNotification": "操作がないため間もなくログアウトします。再開するには [OK] をクリックしてくださ。",
"xpack.security.components.sessionExpiration.logoutNotification": "操作がないため間もなくログアウトします。再開するには [OK] をクリックしてくださ。",
"xpack.security.hacks.warningTitle": "警告",
"xpack.security.loggedOut.login": "ログイン",
"xpack.security.loggedOut.title": "ログアウト完了",

View file

@ -8661,7 +8661,7 @@
"xpack.security.account.passwordsDoNotMatch": "密码不匹配。",
"xpack.security.account.usernameGroupDescription": "不能更改此信息。",
"xpack.security.account.usernameGroupTitle": "用户名和电子邮件",
"xpack.security.hacks.logoutNotification": "由于处于不活动状态,您即将退出。单击“确定”可以恢复。",
"xpack.security.components.sessionExpiration.logoutNotification": "由于处于不活动状态,您即将退出。单击“确定”可以恢复。",
"xpack.security.hacks.warningTitle": "警告",
"xpack.security.loggedOut.login": "登录",
"xpack.security.loggedOut.title": "已成功退出",