[Security] Add message to login page (#51557)

* [Security] Add loginAssistanceMessage to login page

* Fix tests

* Fix login_page.test.tsx

* Fix defaultValue

* Render login assistance message independently of other messages and use EuiText instead of EuiCallOut

* Use small text

Co-Authored-By: Caroline Horn <549577+cchaos@users.noreply.github.com>

* Flip order of message around
This commit is contained in:
Søren Louv-Jansen 2019-11-26 13:19:11 +01:00 committed by GitHub
parent 0557a40a9d
commit e8e517475a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 188 additions and 38 deletions

View file

@ -20,7 +20,7 @@ are enabled.
Do not set this to `false`; it disables the login form, user and role management
screens, and authorization using <<kibana-privileges>>. To disable
{security-features} entirely, see
{ref}/security-settings.html[{es} security settings].
{ref}/security-settings.html[{es} security settings].
`xpack.security.audit.enabled`::
Set to `true` to enable audit logging for security events. By default, it is set
@ -40,7 +40,7 @@ An arbitrary string of 32 characters or more that is used to encrypt credentials
in a cookie. It is crucial that this key is not exposed to users of {kib}. By
default, a value is automatically generated in memory. If you use that default
behavior, all sessions are invalidated when {kib} restarts.
In addition, high-availability deployments of {kib} will behave unexpectedly
In addition, high-availability deployments of {kib} will behave unexpectedly
if this setting isn't the same for all instances of {kib}.
`xpack.security.secureCookies`::
@ -53,3 +53,6 @@ routing requests through a load balancer or proxy).
Sets the session duration (in milliseconds). By default, sessions stay active
until the browser is closed. When this is set to an explicit timeout, closing the
browser still requires the user to log back in to {kib}.
`xpack.security.loginAssistanceMessage`::
Adds a message to the login screen. Useful for displaying information about maintenance windows, links to corporate sign up pages etc.

View file

@ -180,6 +180,7 @@ kibana_vars=(
xpack.security.encryptionKey
xpack.security.secureCookies
xpack.security.sessionTimeout
xpack.security.loginAssistanceMessage
telemetry.enabled
telemetry.sendUsageFrom
)

View file

@ -31,6 +31,7 @@ export const security = (kibana) => new kibana.Plugin({
encryptionKey: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
sessionTimeout: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
secureCookies: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
loginAssistanceMessage: Joi.string().default(),
authorization: Joi.object({
legacyFallback: Joi.object({
enabled: Joi.boolean().default(true) // deprecated
@ -147,7 +148,9 @@ export const security = (kibana) => new kibana.Plugin({
server.injectUiAppVars('login', () => {
const { showLogin, allowLogin, layout = 'form' } = securityPlugin.__legacyCompat.license.getFeatures();
const { loginAssistanceMessage } = securityPlugin.__legacyCompat.config;
return {
loginAssistanceMessage,
loginState: {
showLogin,
allowLogin,

View file

@ -2,6 +2,20 @@
exports[`BasicLoginForm renders as expected 1`] = `
<Fragment>
<EuiText
size="s"
>
<ReactMarkdown
astPlugins={Array []}
escapeHtml={true}
plugins={Array []}
rawSourcePos={false}
renderers={Object {}}
skipHtml={false}
sourcePos={false}
transformLinkUri={[Function]}
/>
</EuiText>
<EuiPanel>
<form
onSubmit={[Function]}

View file

@ -50,6 +50,7 @@ describe('BasicLoginForm', () => {
loginState={loginState}
next={''}
intl={null as any}
loginAssistanceMessage=""
/>
)
).toMatchSnapshot();
@ -68,6 +69,7 @@ describe('BasicLoginForm', () => {
next={''}
infoMessage={'Hey this is an info message'}
intl={null as any}
loginAssistanceMessage=""
/>
);
@ -86,6 +88,7 @@ describe('BasicLoginForm', () => {
loginState={loginState}
next={''}
intl={null as any}
loginAssistanceMessage=""
/>
);

View file

@ -7,6 +7,8 @@
import { EuiButton, EuiCallOut, EuiFieldText, EuiFormRow, EuiPanel, EuiSpacer } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, FormEvent, Fragment, MouseEvent } from 'react';
import ReactMarkdown from 'react-markdown';
import { EuiText } from '@elastic/eui';
import { LoginState } from '../../../../../common/login_state';
interface Props {
@ -16,6 +18,7 @@ interface Props {
loginState: LoginState;
next: string;
intl: InjectedIntl;
loginAssistanceMessage: string;
}
interface State {
@ -38,6 +41,7 @@ class BasicLoginFormUI extends Component<Props, State> {
public render() {
return (
<Fragment>
{this.renderLoginAssistanceMessage()}
{this.renderMessage()}
<EuiPanel>
<form onSubmit={this.submit}>
@ -102,6 +106,16 @@ class BasicLoginFormUI extends Component<Props, State> {
);
}
private renderLoginAssistanceMessage = () => {
return (
<Fragment>
<EuiText size="s">
<ReactMarkdown>{this.props.loginAssistanceMessage}</ReactMarkdown>
</EuiText>
</Fragment>
);
};
private renderMessage = () => {
if (this.state.message) {
return (
@ -132,6 +146,7 @@ class BasicLoginFormUI extends Component<Props, State> {
</Fragment>
);
}
return null;
};

View file

@ -160,6 +160,88 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi
</div>
`;
exports[`LoginPage disabled form states renders as expected when loginAssistanceMessage is set 1`] = `
<div
className="loginWelcome login-form"
>
<header
className="loginWelcome__header"
>
<div
className="loginWelcome__content eui-textCenter"
>
<EuiSpacer
size="xxl"
/>
<span
className="loginWelcome__logo"
>
<EuiIcon
size="xxl"
type="logoKibana"
/>
</span>
<EuiTitle
className="loginWelcome__title"
size="l"
>
<h1>
<FormattedMessage
defaultMessage="Welcome to Kibana"
id="xpack.security.loginPage.welcomeTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
<EuiText
className="loginWelcome__subtitle"
color="subdued"
size="s"
>
<p>
<FormattedMessage
defaultMessage="Your window into the Elastic Stack"
id="xpack.security.loginPage.welcomeDescription"
values={Object {}}
/>
</p>
</EuiText>
<EuiSpacer
size="xl"
/>
</div>
</header>
<div
className="loginWelcome__content loginWelcome-body"
>
<EuiFlexGroup
gutterSize="l"
>
<EuiFlexItem>
<InjectIntl(BasicLoginFormUI)
http={
Object {
"post": [MockFunction],
}
}
isSecureConnection={false}
loginAssistanceMessage="This is an *important* message"
loginState={
Object {
"allowLogin": true,
"layout": "form",
}
}
next=""
requiresSecureConnection={false}
window={Object {}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</div>
</div>
`;
exports[`LoginPage disabled form states renders as expected when secure cookies are required but not present 1`] = `
<div
className="loginWelcome login-form"
@ -385,6 +467,7 @@ exports[`LoginPage enabled form state renders as expected 1`] = `
}
}
isSecureConnection={false}
loginAssistanceMessage=""
loginState={
Object {
"allowLogin": true,

View file

@ -46,6 +46,7 @@ describe('LoginPage', () => {
loginState: createLoginState(),
isSecureConnection: false,
requiresSecureConnection: true,
loginAssistanceMessage: '',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();
@ -61,6 +62,7 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
loginAssistanceMessage: '',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();
@ -76,6 +78,7 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
loginAssistanceMessage: '',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();
@ -91,6 +94,21 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
loginAssistanceMessage: '',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();
});
it('renders as expected when loginAssistanceMessage is set', () => {
const props = {
http: createMockHttp(),
window: {},
next: '',
loginState: createLoginState(),
isSecureConnection: false,
requiresSecureConnection: false,
loginAssistanceMessage: 'This is an *important* message',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();
@ -106,6 +124,7 @@ describe('LoginPage', () => {
loginState: createLoginState(),
isSecureConnection: false,
requiresSecureConnection: false,
loginAssistanceMessage: '',
};
expect(shallow(<LoginPage {...props} />)).toMatchSnapshot();

View file

@ -31,6 +31,7 @@ interface Props {
loginState: LoginState;
isSecureConnection: boolean;
requiresSecureConnection: boolean;
loginAssistanceMessage: string;
}
export class LoginPage extends Component<Props, {}> {

View file

@ -39,7 +39,8 @@ interface AnyObject {
$http: AnyObject,
$window: AnyObject,
secureCookies: boolean,
loginState: LoginState
loginState: LoginState,
loginAssistanceMessage: string
) => {
const basePath = chrome.getBasePath();
const next = parseNext($window.location.href, basePath);
@ -59,6 +60,7 @@ interface AnyObject {
loginState={loginState}
isSecureConnection={isSecure}
requiresSecureConnection={secureCookies}
loginAssistanceMessage={loginAssistanceMessage}
next={next}
/>
</I18nContext>,

View file

@ -13,45 +13,48 @@ import { createConfig$, ConfigSchema } from './config';
describe('config schema', () => {
it('generates proper defaults', () => {
expect(ConfigSchema.validate({})).toMatchInlineSnapshot(`
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"secureCookies": false,
"sessionTimeout": null,
}
`);
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"loginAssistanceMessage": "",
"secureCookies": false,
"sessionTimeout": null,
}
`);
expect(ConfigSchema.validate({}, { dist: false })).toMatchInlineSnapshot(`
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"secureCookies": false,
"sessionTimeout": null,
}
`);
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"loginAssistanceMessage": "",
"secureCookies": false,
"sessionTimeout": null,
}
`);
expect(ConfigSchema.validate({}, { dist: true })).toMatchInlineSnapshot(`
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"secureCookies": false,
"sessionTimeout": null,
}
`);
Object {
"authc": Object {
"providers": Array [
"basic",
],
},
"cookieName": "sid",
"loginAssistanceMessage": "",
"secureCookies": false,
"sessionTimeout": null,
}
`);
});
it('should throw error if xpack.security.encryptionKey is less than 32 characters', () => {

View file

@ -26,6 +26,7 @@ const providerOptionsSchema = (providerType: string, optionsSchema: Type<any>) =
export const ConfigSchema = schema.object(
{
loginAssistanceMessage: schema.string({ defaultValue: '' }),
cookieName: schema.string({ defaultValue: 'sid' }),
encryptionKey: schema.conditional(
schema.contextRef('dist'),

View file

@ -52,6 +52,7 @@ describe('Security Plugin', () => {
],
},
"cookieName": "sid",
"loginAssistanceMessage": undefined,
"secureCookies": true,
"sessionTimeout": 1500,
},

View file

@ -205,6 +205,7 @@ export class Plugin {
// We should stop exposing this config as soon as only new platform plugin consumes it. The only
// exception may be `sessionTimeout` as other parts of the app may want to know it.
config: {
loginAssistanceMessage: config.loginAssistanceMessage,
sessionTimeout: config.sessionTimeout,
secureCookies: config.secureCookies,
cookieName: config.cookieName,