mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Support space-specific default routes (#44678)
This commit is contained in:
parent
51d734e9b8
commit
0bfa7ca5c6
59 changed files with 1251 additions and 843 deletions
|
@ -18,10 +18,6 @@
|
|||
# default to `true` starting in Kibana 7.0.
|
||||
#server.rewriteBasePath: false
|
||||
|
||||
# Specifies the default route when opening Kibana. You can use this setting to modify
|
||||
# the landing page when opening Kibana.
|
||||
#server.defaultRoute: /app/kibana
|
||||
|
||||
# The maximum payload size in bytes for incoming server requests.
|
||||
#server.maxPayloadBytes: 1048576
|
||||
|
||||
|
|
|
@ -256,10 +256,6 @@ deprecation warning at startup. This setting cannot end in a slash (`/`).
|
|||
`server.customResponseHeaders:`:: *Default: `{}`* Header names and values to
|
||||
send on all responses to the client from the Kibana server.
|
||||
|
||||
[[server-default]]`server.defaultRoute:`:: *Default: "/app/kibana"* This setting
|
||||
specifies the default route when opening Kibana. You can use this setting to
|
||||
modify the landing page when opening Kibana. Supported on {ece}.
|
||||
|
||||
`server.host:`:: *Default: "localhost"* This setting specifies the host of the
|
||||
back end server.
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "array",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -88,6 +89,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "boolean",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -108,6 +110,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -126,6 +129,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "image",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -146,6 +150,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "json",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -164,6 +169,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "number",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -186,6 +192,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "select",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -204,6 +211,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -222,6 +230,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "json",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -240,6 +249,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "markdown",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -258,6 +268,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "number",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -280,6 +291,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "select",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -298,6 +310,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -342,6 +355,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "array",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -360,6 +374,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "boolean",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -380,6 +395,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -398,6 +414,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "image",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -418,6 +435,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "json",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -436,6 +454,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "number",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -458,6 +477,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "select",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -476,6 +496,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -494,6 +515,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "json",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -512,6 +534,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "markdown",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -530,6 +553,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "number",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -552,6 +576,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "select",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
|
@ -570,6 +595,7 @@ exports[`AdvancedSettings should render normally 1`] = `
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -689,6 +715,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] =
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -731,6 +758,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] =
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -868,6 +896,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1`
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
@ -910,6 +939,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1`
|
|||
"readonly": false,
|
||||
"requiresPageReload": false,
|
||||
"type": "string",
|
||||
"validation": undefined,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -3707,3 +3707,422 @@ exports[`Field for string setting should render user value if there is user valu
|
|||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`Field for stringWithValidation setting should render as read only if saving is disabled 1`] = `
|
||||
<EuiFlexGroup
|
||||
className="mgtAdvancedSettings__field"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
className="mgtAdvancedSettings__fieldWrapper"
|
||||
description={
|
||||
<React.Fragment>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Description for String test validation setting",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
fullWidth={false}
|
||||
gutterSize="l"
|
||||
idAria="string:test-validation:setting-aria"
|
||||
title={
|
||||
<h3>
|
||||
String test validation setting
|
||||
|
||||
</h3>
|
||||
}
|
||||
titleSize="xs"
|
||||
>
|
||||
<EuiFormRow
|
||||
className="mgtAdvancedSettings__fieldRow"
|
||||
describedByIds={
|
||||
Array [
|
||||
"string:test-validation:setting-aria",
|
||||
]
|
||||
}
|
||||
display="row"
|
||||
error={null}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={null}
|
||||
isInvalid={false}
|
||||
label="string:test-validation:setting"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFieldText
|
||||
aria-label="string test validation setting"
|
||||
compressed={false}
|
||||
data-test-subj="advancedSetting-editField-string:test-validation:setting"
|
||||
disabled={true}
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
value="foo-default"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`Field for stringWithValidation setting should render as read only with help text if overridden 1`] = `
|
||||
<EuiFlexGroup
|
||||
className="mgtAdvancedSettings__field"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
className="mgtAdvancedSettings__fieldWrapper"
|
||||
description={
|
||||
<React.Fragment>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Description for String test validation setting",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiText
|
||||
size="xs"
|
||||
>
|
||||
<React.Fragment>
|
||||
<FormattedMessage
|
||||
defaultMessage="Default: {value}"
|
||||
id="kbn.management.settings.field.defaultValueText"
|
||||
values={
|
||||
Object {
|
||||
"value": <EuiCode>
|
||||
foo-default
|
||||
</EuiCode>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</EuiText>
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
}
|
||||
fullWidth={false}
|
||||
gutterSize="l"
|
||||
idAria="string:test-validation:setting-aria"
|
||||
title={
|
||||
<h3>
|
||||
String test validation setting
|
||||
|
||||
</h3>
|
||||
}
|
||||
titleSize="xs"
|
||||
>
|
||||
<EuiFormRow
|
||||
className="mgtAdvancedSettings__fieldRow"
|
||||
describedByIds={
|
||||
Array [
|
||||
"string:test-validation:setting-aria",
|
||||
]
|
||||
}
|
||||
display="row"
|
||||
error={null}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={
|
||||
<EuiText
|
||||
size="xs"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="This setting is overridden by the Kibana server and can not be changed."
|
||||
id="kbn.management.settings.field.helpText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={false}
|
||||
label="string:test-validation:setting"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFieldText
|
||||
aria-label="string test validation setting"
|
||||
compressed={false}
|
||||
data-test-subj="advancedSetting-editField-string:test-validation:setting"
|
||||
disabled={true}
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
value="fooUserValue"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`Field for stringWithValidation setting should render custom setting icon if it is custom 1`] = `
|
||||
<EuiFlexGroup
|
||||
className="mgtAdvancedSettings__field"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
className="mgtAdvancedSettings__fieldWrapper"
|
||||
description={
|
||||
<React.Fragment>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Description for String test validation setting",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
fullWidth={false}
|
||||
gutterSize="l"
|
||||
idAria="string:test-validation:setting-aria"
|
||||
title={
|
||||
<h3>
|
||||
String test validation setting
|
||||
<EuiIconTip
|
||||
aria-label="Custom setting"
|
||||
color="primary"
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Custom setting"
|
||||
id="kbn.management.settings.field.customSettingTooltip"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
type="asterisk"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
titleSize="xs"
|
||||
>
|
||||
<EuiFormRow
|
||||
className="mgtAdvancedSettings__fieldRow"
|
||||
describedByIds={
|
||||
Array [
|
||||
"string:test-validation:setting-aria",
|
||||
]
|
||||
}
|
||||
display="row"
|
||||
error={null}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={null}
|
||||
isInvalid={false}
|
||||
label="string:test-validation:setting"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFieldText
|
||||
aria-label="string test validation setting"
|
||||
compressed={false}
|
||||
data-test-subj="advancedSetting-editField-string:test-validation:setting"
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
value="foo-default"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`Field for stringWithValidation setting should render default value if there is no user value set 1`] = `
|
||||
<EuiFlexGroup
|
||||
className="mgtAdvancedSettings__field"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
className="mgtAdvancedSettings__fieldWrapper"
|
||||
description={
|
||||
<React.Fragment>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Description for String test validation setting",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
fullWidth={false}
|
||||
gutterSize="l"
|
||||
idAria="string:test-validation:setting-aria"
|
||||
title={
|
||||
<h3>
|
||||
String test validation setting
|
||||
|
||||
</h3>
|
||||
}
|
||||
titleSize="xs"
|
||||
>
|
||||
<EuiFormRow
|
||||
className="mgtAdvancedSettings__fieldRow"
|
||||
describedByIds={
|
||||
Array [
|
||||
"string:test-validation:setting-aria",
|
||||
]
|
||||
}
|
||||
display="row"
|
||||
error={null}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={null}
|
||||
isInvalid={false}
|
||||
label="string:test-validation:setting"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFieldText
|
||||
aria-label="string test validation setting"
|
||||
compressed={false}
|
||||
data-test-subj="advancedSetting-editField-string:test-validation:setting"
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
value="foo-default"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`Field for stringWithValidation setting should render user value if there is user value is set 1`] = `
|
||||
<EuiFlexGroup
|
||||
className="mgtAdvancedSettings__field"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
className="mgtAdvancedSettings__fieldWrapper"
|
||||
description={
|
||||
<React.Fragment>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Description for String test validation setting",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiText
|
||||
size="xs"
|
||||
>
|
||||
<React.Fragment>
|
||||
<FormattedMessage
|
||||
defaultMessage="Default: {value}"
|
||||
id="kbn.management.settings.field.defaultValueText"
|
||||
values={
|
||||
Object {
|
||||
"value": <EuiCode>
|
||||
foo-default
|
||||
</EuiCode>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</EuiText>
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
}
|
||||
fullWidth={false}
|
||||
gutterSize="l"
|
||||
idAria="string:test-validation:setting-aria"
|
||||
title={
|
||||
<h3>
|
||||
String test validation setting
|
||||
|
||||
</h3>
|
||||
}
|
||||
titleSize="xs"
|
||||
>
|
||||
<EuiFormRow
|
||||
className="mgtAdvancedSettings__fieldRow"
|
||||
describedByIds={
|
||||
Array [
|
||||
"string:test-validation:setting-aria",
|
||||
]
|
||||
}
|
||||
display="row"
|
||||
error={null}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={
|
||||
<span>
|
||||
<span>
|
||||
<EuiLink
|
||||
aria-label="Reset string test validation setting to default"
|
||||
color="primary"
|
||||
data-test-subj="advancedSetting-resetField-string:test-validation:setting"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Reset to default"
|
||||
id="kbn.management.settings.field.resetToDefaultLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
isInvalid={false}
|
||||
label="string:test-validation:setting"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFieldText
|
||||
aria-label="string test validation setting"
|
||||
compressed={false}
|
||||
data-test-subj="advancedSetting-editField-string:test-validation:setting"
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
value="fooUserValue"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
|
|
@ -166,7 +166,7 @@ class FieldUI extends PureComponent {
|
|||
|
||||
onFieldChange = (e) => {
|
||||
const value = e.target.value;
|
||||
const { type } = this.props.setting;
|
||||
const { type, validation } = this.props.setting;
|
||||
const { unsavedValue } = this.state;
|
||||
|
||||
let newUnsavedValue = undefined;
|
||||
|
@ -181,8 +181,21 @@ class FieldUI extends PureComponent {
|
|||
default:
|
||||
newUnsavedValue = value;
|
||||
}
|
||||
|
||||
let isInvalid = false;
|
||||
let error = undefined;
|
||||
|
||||
if (validation && validation.regex) {
|
||||
if (!validation.regex.test(newUnsavedValue)) {
|
||||
error = validation.message;
|
||||
isInvalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
unsavedValue: newUnsavedValue,
|
||||
isInvalid,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,22 @@ const settings = {
|
|||
isOverridden: false,
|
||||
options: null,
|
||||
},
|
||||
stringWithValidation: {
|
||||
name: 'string:test-validation:setting',
|
||||
ariaName: 'string test validation setting',
|
||||
displayName: 'String test validation setting',
|
||||
description: 'Description for String test validation setting',
|
||||
type: 'string',
|
||||
validation: {
|
||||
regex: new RegExp('/^foo'),
|
||||
message: 'must start with "foo"'
|
||||
},
|
||||
value: undefined,
|
||||
defVal: 'foo-default',
|
||||
isCustom: false,
|
||||
isOverridden: false,
|
||||
options: null,
|
||||
}
|
||||
};
|
||||
const userValues = {
|
||||
array: ['user', 'value'],
|
||||
|
@ -153,6 +169,10 @@ const userValues = {
|
|||
number: 10,
|
||||
select: 'banana',
|
||||
string: 'foo',
|
||||
stringWithValidation: 'fooUserValue'
|
||||
};
|
||||
const invalidUserValues = {
|
||||
stringWithValidation: 'invalidUserValue'
|
||||
};
|
||||
const save = jest.fn(() => Promise.resolve());
|
||||
const clear = jest.fn(() => Promise.resolve());
|
||||
|
@ -392,6 +412,16 @@ describe('Field', () => {
|
|||
const userValue = userValues[type];
|
||||
const fieldUserValue = type === 'array' ? userValue.join(', ') : userValue;
|
||||
|
||||
if (setting.validation) {
|
||||
const invalidUserValue = invalidUserValues[type];
|
||||
it('should display an error when validation fails', async () => {
|
||||
component.instance().onFieldChange({ target: { value: invalidUserValue } });
|
||||
component.update();
|
||||
const errorMessage = component.find('.euiFormErrorText').text();
|
||||
expect(errorMessage).toEqual(setting.validation.message);
|
||||
});
|
||||
}
|
||||
|
||||
it('should be able to change value and cancel', async () => {
|
||||
component.instance().onFieldChange({ target: { value: fieldUserValue } });
|
||||
component.update();
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Settings', function () {
|
|||
def = {
|
||||
value: 'the original',
|
||||
description: 'the one and only',
|
||||
options: 'all the options'
|
||||
options: 'all the options',
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -76,6 +76,18 @@ describe('Settings', function () {
|
|||
expect(invoke({ def }).type).to.equal('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('that contains a validation object', function () {
|
||||
it('constructs a validation regex with message', function () {
|
||||
def.validation = {
|
||||
regexString: '^foo',
|
||||
message: 'must start with "foo"'
|
||||
};
|
||||
const result = invoke({ def });
|
||||
expect(result.validation.regex).to.be.a(RegExp);
|
||||
expect(result.validation.message).to.equal('must start with "foo"');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not given a setting definition object', function () {
|
||||
|
@ -94,6 +106,10 @@ describe('Settings', function () {
|
|||
it('sets options to undefined', function () {
|
||||
expect(invoke().options).to.be.undefined;
|
||||
});
|
||||
|
||||
it('sets validation to undefined', function () {
|
||||
expect(invoke().validation).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,6 +43,10 @@ export function toEditableConfig({ def, name, value, isCustom, isOverridden }) {
|
|||
defVal: def.value,
|
||||
type: getValType(def, value),
|
||||
description: def.description,
|
||||
validation: def.validation ? {
|
||||
regex: new RegExp(def.validation.regexString),
|
||||
message: def.validation.message
|
||||
} : undefined,
|
||||
options: def.options,
|
||||
optionLabels: def.optionLabels,
|
||||
requiresPageReload: !!def.requiresPageReload,
|
||||
|
|
|
@ -55,6 +55,24 @@ export function getUiSettingDefaults() {
|
|||
'buildNum': {
|
||||
readonly: true
|
||||
},
|
||||
'defaultRoute': {
|
||||
name: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteTitle', {
|
||||
defaultMessage: 'Default route',
|
||||
}),
|
||||
value: '/app/kibana',
|
||||
validation: {
|
||||
regexString: '^\/',
|
||||
message: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteValidationMessage', {
|
||||
defaultMessage: 'The route must start with a slash ("/")',
|
||||
}),
|
||||
},
|
||||
description:
|
||||
i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteText', {
|
||||
defaultMessage: 'This setting specifies the default route when opening Kibana. ' +
|
||||
'You can use this setting to modify the landing page when opening Kibana. ' +
|
||||
'The route must start with a slash ("/").',
|
||||
}),
|
||||
},
|
||||
'query:queryString:options': {
|
||||
name: i18n.translate('kbn.advancedSettings.query.queryStringOptionsTitle', {
|
||||
defaultMessage: 'Query string options',
|
||||
|
|
|
@ -78,7 +78,7 @@ export default () => Joi.object({
|
|||
server: Joi.object({
|
||||
uuid: Joi.string().guid().default(),
|
||||
name: Joi.string().default(os.hostname()),
|
||||
defaultRoute: Joi.string().default('/app/kibana').regex(/^\//, `start with a slash`),
|
||||
defaultRoute: Joi.string().regex(/^\//, `start with a slash`),
|
||||
customResponseHeaders: Joi.object().unknown(true).default({}),
|
||||
xsrf: Joi.object({
|
||||
disableProtection: Joi.boolean().default(false),
|
||||
|
|
|
@ -95,6 +95,7 @@ const cspRules = (settings, log) => {
|
|||
|
||||
const deprecations = [
|
||||
//server
|
||||
rename('server.defaultRoute', 'uiSettings.overrides.defaultRoute'),
|
||||
unused('server.xsrf.token'),
|
||||
unused('uiSettings.enabled'),
|
||||
rename('optimize.lazy', 'optimize.watch'),
|
||||
|
|
|
@ -62,6 +62,24 @@ describe('server/config', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('server.defaultRoute', () => {
|
||||
it('renames to uiSettings.overrides.defaultRoute when specified', () => {
|
||||
const settings = {
|
||||
server: {
|
||||
defaultRoute: '/app/foo',
|
||||
},
|
||||
};
|
||||
|
||||
expect(transformDeprecations(settings)).toEqual({
|
||||
uiSettings: {
|
||||
overrides: {
|
||||
defaultRoute: '/app/foo'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('csp.rules', () => {
|
||||
describe('with nonce source', () => {
|
||||
it('logs a warning', () => {
|
||||
|
@ -74,20 +92,18 @@ describe('server/config', function () {
|
|||
const log = jest.fn();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in script-src",
|
||||
],
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in script-src",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('replaces a nonce', () => {
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'nonce-{nonce}'`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
transformDeprecations({ csp: { rules: [`script-src 'nonce-{nonce}'`] } }, jest.fn()).csp
|
||||
.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
transformDeprecations(
|
||||
|
@ -158,12 +174,12 @@ describe('server/config', function () {
|
|||
const log = jest.fn();
|
||||
transformDeprecations({ csp: { rules: [`script-src 'unsafe-eval'`] } }, log);
|
||||
expect(log.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules must contain the 'self' source. Automatically adding to script-src.",
|
||||
],
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules must contain the 'self' source. Automatically adding to script-src.",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('adds self', () => {
|
||||
|
|
|
@ -25,6 +25,7 @@ import Boom from 'boom';
|
|||
import { setupVersionCheck } from './version_check';
|
||||
import { registerHapiPlugins } from './register_hapi_plugins';
|
||||
import { setupBasePathProvider } from './setup_base_path_provider';
|
||||
import { setupDefaultRouteProvider } from './setup_default_route_provider';
|
||||
import { setupXsrf } from './xsrf';
|
||||
|
||||
export default async function (kbnServer, server, config) {
|
||||
|
@ -33,6 +34,8 @@ export default async function (kbnServer, server, config) {
|
|||
|
||||
setupBasePathProvider(kbnServer);
|
||||
|
||||
setupDefaultRouteProvider(server);
|
||||
|
||||
await registerHapiPlugins(server);
|
||||
|
||||
// provide a simple way to expose static directories
|
||||
|
@ -86,10 +89,8 @@ export default async function (kbnServer, server, config) {
|
|||
server.route({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
handler(req, h) {
|
||||
const basePath = req.getBasePath();
|
||||
const defaultRoute = config.get('server.defaultRoute');
|
||||
return h.redirect(`${basePath}${defaultRoute}`);
|
||||
async handler(req, h) {
|
||||
return h.redirect(await req.getDefaultRoute());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
jest.mock('../../../ui/ui_settings/ui_settings_mixin', () => {
|
||||
return jest.fn();
|
||||
});
|
||||
|
||||
import * as kbnTestServer from '../../../../test_utils/kbn_server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { Root } from '../../../../core/server/root';
|
||||
|
||||
let mockDefaultRouteSetting: any = '';
|
||||
|
||||
describe('default route provider', () => {
|
||||
let root: Root;
|
||||
beforeAll(async () => {
|
||||
root = kbnTestServer.createRoot();
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
const kbnServer = kbnTestServer.getKbnServer(root);
|
||||
|
||||
kbnServer.server.decorate('request', 'getUiSettingsService', function() {
|
||||
return {
|
||||
get: (key: string) => {
|
||||
if (key === 'defaultRoute') {
|
||||
return Promise.resolve(mockDefaultRouteSetting);
|
||||
}
|
||||
throw Error(`unsupported ui setting: ${key}`);
|
||||
},
|
||||
getDefaults: () => {
|
||||
return Promise.resolve({
|
||||
defaultRoute: {
|
||||
value: '/app/kibana',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => await root.shutdown());
|
||||
|
||||
it('redirects to the configured default route', async function() {
|
||||
mockDefaultRouteSetting = '/app/some/default/route';
|
||||
|
||||
const { status, header } = await kbnTestServer.request.get(root, '/');
|
||||
expect(status).toEqual(302);
|
||||
expect(header).toMatchObject({
|
||||
location: '/app/some/default/route',
|
||||
});
|
||||
});
|
||||
|
||||
const invalidRoutes = [
|
||||
'http://not-your-kibana.com',
|
||||
'///example.com',
|
||||
'//example.com',
|
||||
' //example.com',
|
||||
];
|
||||
for (const route of invalidRoutes) {
|
||||
it(`falls back to /app/kibana when the configured route (${route}) is not a valid relative path`, async function() {
|
||||
mockDefaultRouteSetting = route;
|
||||
|
||||
const { status, header } = await kbnTestServer.request.get(root, '/');
|
||||
expect(status).toEqual(302);
|
||||
expect(header).toMatchObject({
|
||||
location: '/app/kibana',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
74
src/legacy/server/http/setup_default_route_provider.ts
Normal file
74
src/legacy/server/http/setup_default_route_provider.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Legacy } from 'kibana';
|
||||
import { parse } from 'url';
|
||||
|
||||
export function setupDefaultRouteProvider(server: Legacy.Server) {
|
||||
server.decorate('request', 'getDefaultRoute', async function() {
|
||||
// @ts-ignore
|
||||
const request: Legacy.Request = this;
|
||||
|
||||
const serverBasePath: string = server.config().get('server.basePath');
|
||||
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
|
||||
const defaultRoute = await uiSettings.get('defaultRoute');
|
||||
const qualifiedDefaultRoute = `${request.getBasePath()}${defaultRoute}`;
|
||||
|
||||
if (isRelativePath(qualifiedDefaultRoute, serverBasePath)) {
|
||||
return qualifiedDefaultRoute;
|
||||
} else {
|
||||
server.log(
|
||||
['http', 'warn'],
|
||||
`Ignoring configured default route of '${defaultRoute}', as it is malformed.`
|
||||
);
|
||||
|
||||
const fallbackRoute = (await uiSettings.getDefaults()).defaultRoute.value;
|
||||
|
||||
const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`;
|
||||
return qualifiedFallbackRoute;
|
||||
}
|
||||
});
|
||||
|
||||
function isRelativePath(candidatePath: string, basePath = '') {
|
||||
// validate that `candidatePath` is not attempting a redirect to somewhere
|
||||
// outside of this Kibana install
|
||||
const { protocol, hostname, port, pathname } = parse(
|
||||
candidatePath,
|
||||
false /* parseQueryString */,
|
||||
true /* slashesDenoteHost */
|
||||
);
|
||||
|
||||
// We should explicitly compare `protocol`, `port` and `hostname` to null to make sure these are not
|
||||
// detected in the URL at all. For example `hostname` can be empty string for Node URL parser, but
|
||||
// browser (because of various bwc reasons) processes URL differently (e.g. `///abc.com` - for browser
|
||||
// hostname is `abc.com`, but for Node hostname is an empty string i.e. everything between schema (`//`)
|
||||
// and the first slash that belongs to path.
|
||||
if (protocol !== null || hostname !== null || port !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!String(pathname).startsWith(basePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
1
src/legacy/server/kbn_server.d.ts
vendored
1
src/legacy/server/kbn_server.d.ts
vendored
|
@ -83,6 +83,7 @@ declare module 'hapi' {
|
|||
interface Request {
|
||||
getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract;
|
||||
getBasePath(): string;
|
||||
getDefaultRoute(): Promise<string>;
|
||||
getUiSettingsService(): any;
|
||||
getCapabilities(): Promise<Capabilities>;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react';
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { getSpaceColor } from '../../../../../../../../../spaces/common';
|
||||
import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes';
|
||||
import { Space } from '../../../../../../../../../spaces/common/model/space';
|
||||
import {
|
||||
FeaturesPrivileges,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { InjectedIntl } from '@kbn/i18n/react';
|
||||
import React, { Component } from 'react';
|
||||
import { Space } from '../../../../../../../../../spaces/common/model/space';
|
||||
import { getSpaceColor } from '../../../../../../../../../spaces/common/space_attributes';
|
||||
import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes';
|
||||
|
||||
const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => {
|
||||
if (!space) {
|
||||
|
|
|
@ -21,3 +21,8 @@ export const MAX_SPACE_INITIALS = 2;
|
|||
* @type {string}
|
||||
*/
|
||||
export const KIBANA_SPACES_STATS_TYPE = 'spaces';
|
||||
|
||||
/**
|
||||
* The path to enter a space.
|
||||
*/
|
||||
export const ENTER_SPACE_PATH = '/spaces/enter';
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
export { isReservedSpace } from './is_reserved_space';
|
||||
export { MAX_SPACE_INITIALS } from './constants';
|
||||
|
||||
export { getSpaceInitials, getSpaceColor } from './space_attributes';
|
||||
export { getSpaceIdFromPath, addSpaceIdToPath } from './lib/spaces_url_parser';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
import { DEFAULT_SPACE_ID } from '../constants';
|
||||
import { addSpaceIdToPath, getSpaceIdFromPath } from './spaces_url_parser';
|
||||
|
||||
describe('getSpaceIdFromPath', () => {
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
import { DEFAULT_SPACE_ID } from '../constants';
|
||||
|
||||
export function getSpaceIdFromPath(
|
||||
requestBasePath: string = '/',
|
|
@ -14,12 +14,11 @@ import { AuditLogger } from '../../server/lib/audit_logger';
|
|||
import mappings from './mappings.json';
|
||||
import { wrapError } from './server/lib/errors';
|
||||
import { getActiveSpace } from './server/lib/get_active_space';
|
||||
import { getSpaceSelectorUrl } from './server/lib/get_space_selector_url';
|
||||
import { migrateToKibana660 } from './server/lib/migrations';
|
||||
import { plugin } from './server/new_platform';
|
||||
import { SecurityPlugin } from '../security';
|
||||
import { SpacesServiceSetup } from './server/new_platform/spaces_service/spaces_service';
|
||||
import { initSpaceSelectorView } from './server/routes/views';
|
||||
import { initSpaceSelectorView, initEnterSpaceView } from './server/routes/views';
|
||||
|
||||
export interface SpacesPlugin {
|
||||
getSpaceId: SpacesServiceSetup['getSpaceId'];
|
||||
|
@ -88,7 +87,7 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
return {
|
||||
spaces: [],
|
||||
activeSpace: null,
|
||||
spaceSelectorURL: getSpaceSelectorUrl(server.config()),
|
||||
serverBasePath: server.config().get('server.basePath'),
|
||||
};
|
||||
},
|
||||
async replaceInjectedVars(
|
||||
|
@ -181,6 +180,7 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
},
|
||||
});
|
||||
|
||||
initEnterSpaceView(server);
|
||||
initSpaceSelectorView(server);
|
||||
|
||||
server.expose('getSpaceId', (request: any) => spacesService.getSpaceId(request));
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { EuiAvatar, isValidHex } from '@elastic/eui';
|
||||
import React, { SFC } from 'react';
|
||||
import { getSpaceColor, getSpaceInitials, MAX_SPACE_INITIALS } from '../../common';
|
||||
import { MAX_SPACE_INITIALS } from '../../common';
|
||||
import { Space } from '../../common/model/space';
|
||||
import { getSpaceImageUrl } from '../../common/space_attributes';
|
||||
import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from '../lib/space_attributes';
|
||||
|
||||
interface Props {
|
||||
space: Partial<Space>;
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
*/
|
||||
|
||||
export { SpacesManager } from './spaces_manager';
|
||||
export { getSpaceInitials, getSpaceColor, getSpaceImageUrl } from './space_attributes';
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
|
||||
import { VISUALIZATION_COLORS } from '@elastic/eui';
|
||||
import { MAX_SPACE_INITIALS } from './constants';
|
||||
import { Space } from './model/space';
|
||||
import { Space } from '../../common/model/space';
|
||||
import { MAX_SPACE_INITIALS } from '../../common';
|
||||
|
||||
// code point for lowercase "a"
|
||||
const FALLBACK_CODE_POINT = 97;
|
|
@ -3,21 +3,18 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { EventEmitter } from 'events';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management';
|
||||
import { Space } from '../../common/model/space';
|
||||
import { GetSpacePurpose } from '../../common/model/types';
|
||||
import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types';
|
||||
import { ENTER_SPACE_PATH } from '../../common/constants';
|
||||
import { addSpaceIdToPath } from '../../common';
|
||||
|
||||
export class SpacesManager extends EventEmitter {
|
||||
private spaceSelectorURL: string;
|
||||
|
||||
constructor(spaceSelectorURL: string) {
|
||||
constructor(private readonly serverBasePath: string) {
|
||||
super();
|
||||
this.spaceSelectorURL = spaceSelectorURL;
|
||||
}
|
||||
|
||||
public async getSpaces(purpose?: GetSpacePurpose): Promise<Space[]> {
|
||||
|
@ -89,36 +86,14 @@ export class SpacesManager extends EventEmitter {
|
|||
}
|
||||
|
||||
public async changeSelectedSpace(space: Space) {
|
||||
await kfetch({
|
||||
pathname: `/api/spaces/v1/space/${encodeURIComponent(space.id)}/select`,
|
||||
method: 'POST',
|
||||
})
|
||||
.then(response => {
|
||||
if (response.location) {
|
||||
window.location = response.location;
|
||||
} else {
|
||||
this._displayError();
|
||||
}
|
||||
})
|
||||
.catch(() => this._displayError());
|
||||
window.location.href = addSpaceIdToPath(this.serverBasePath, space.id, ENTER_SPACE_PATH);
|
||||
}
|
||||
|
||||
public redirectToSpaceSelector() {
|
||||
window.location.href = this.spaceSelectorURL;
|
||||
window.location.href = `${this.serverBasePath}/spaces/space_selector`;
|
||||
}
|
||||
|
||||
public async requestRefresh() {
|
||||
this.emit('request_refresh');
|
||||
}
|
||||
|
||||
public _displayError() {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.spaces.spacesManager.unableToChangeSpaceWarningTitle', {
|
||||
defaultMessage: 'Unable to change your Space',
|
||||
}),
|
||||
text: i18n.translate('xpack.spaces.spacesManager.unableToChangeSpaceWarningDescription', {
|
||||
defaultMessage: 'please try again later',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
|
||||
import { getSpaceColor, getSpaceInitials } from '../../../../lib/space_attributes';
|
||||
import { encode, imageTypes } from '../../../../../common/lib/dataurl';
|
||||
|
||||
import { MAX_SPACE_INITIALS } from '../../../../../common/constants';
|
||||
import { Space } from '../../../../../common/model/space';
|
||||
import { getSpaceColor, getSpaceInitials } from '../../../../../common/space_attributes';
|
||||
|
||||
interface Props {
|
||||
space: Partial<Space>;
|
||||
|
|
|
@ -24,7 +24,7 @@ const MANAGE_SPACES_KEY = 'spaces';
|
|||
|
||||
routes.defaults(/\/management/, {
|
||||
resolve: {
|
||||
spacesManagementSection(activeSpace: any, spaceSelectorURL: string) {
|
||||
spacesManagementSection(activeSpace: any, serverBasePath: string) {
|
||||
function getKibanaSection() {
|
||||
return management.getSection('kibana');
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ routes.defaults(/\/management/, {
|
|||
|
||||
// Customize Saved Objects Management
|
||||
const action = new CopyToSpaceSavedObjectsManagementAction(
|
||||
new SpacesManager(spaceSelectorURL),
|
||||
new SpacesManager(serverBasePath),
|
||||
activeSpace.space
|
||||
);
|
||||
// This route resolve function executes any time the management screen is loaded, and we want to ensure
|
||||
|
|
|
@ -22,11 +22,11 @@ routes.when('/management/spaces/list', {
|
|||
template,
|
||||
k7Breadcrumbs: getListBreadcrumbs,
|
||||
requireUICapability: 'management.kibana.spaces',
|
||||
controller($scope: any, spacesNavState: SpacesNavState, spaceSelectorURL: string) {
|
||||
controller($scope: any, spacesNavState: SpacesNavState, serverBasePath: string) {
|
||||
$scope.$$postDigest(async () => {
|
||||
const domNode = document.getElementById(reactRootNodeId);
|
||||
|
||||
const spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
const spacesManager = new SpacesManager(serverBasePath);
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
|
@ -49,11 +49,11 @@ routes.when('/management/spaces/create', {
|
|||
template,
|
||||
k7Breadcrumbs: getCreateBreadcrumbs,
|
||||
requireUICapability: 'management.kibana.spaces',
|
||||
controller($scope: any, spacesNavState: SpacesNavState, spaceSelectorURL: string) {
|
||||
controller($scope: any, spacesNavState: SpacesNavState, serverBasePath: string) {
|
||||
$scope.$$postDigest(async () => {
|
||||
const domNode = document.getElementById(reactRootNodeId);
|
||||
|
||||
const spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
const spacesManager = new SpacesManager(serverBasePath);
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
|
@ -85,14 +85,14 @@ routes.when('/management/spaces/edit/:spaceId', {
|
|||
$route: any,
|
||||
chrome: any,
|
||||
spacesNavState: SpacesNavState,
|
||||
spaceSelectorURL: string
|
||||
serverBasePath: string
|
||||
) {
|
||||
$scope.$$postDigest(async () => {
|
||||
const domNode = document.getElementById(reactRootNodeId);
|
||||
|
||||
const { spaceId } = $route.current.params;
|
||||
|
||||
const spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
const spacesManager = new SpacesManager(serverBasePath);
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
|
|
|
@ -54,9 +54,9 @@ chromeHeaderNavControlsRegistry.register((chrome: any, activeSpace: any) => ({
|
|||
return;
|
||||
}
|
||||
|
||||
const spaceSelectorURL = chrome.getInjected('spaceSelectorURL');
|
||||
const serverBasePath = chrome.getInjected('serverBasePath');
|
||||
|
||||
spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
spacesManager = new SpacesManager(serverBasePath);
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nContext>
|
||||
|
|
|
@ -21,10 +21,10 @@ import { SpaceSelector } from './space_selector';
|
|||
const module = uiModules.get('spaces_selector', []);
|
||||
module.controller(
|
||||
'spacesSelectorController',
|
||||
($scope: any, spaces: Space[], spaceSelectorURL: string) => {
|
||||
($scope: any, spaces: Space[], serverBasePath: string) => {
|
||||
const domNode = document.getElementById('spaceSelectorRoot');
|
||||
|
||||
const spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
const spacesManager = new SpacesManager(serverBasePath);
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { Space } from '../../common/model/space';
|
||||
import { wrapError } from './errors';
|
||||
import { SpacesClient } from './spaces_client';
|
||||
import { getSpaceIdFromPath } from './spaces_url_parser';
|
||||
import { getSpaceIdFromPath } from '../../common';
|
||||
|
||||
export async function getActiveSpace(
|
||||
spacesClient: SpacesClient,
|
||||
|
|
|
@ -428,7 +428,7 @@ describe('onPostAuthInterceptor', () => {
|
|||
);
|
||||
}, 30000);
|
||||
|
||||
it('allows the request to continue when accessing the root of a non-default space', async () => {
|
||||
it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => {
|
||||
const spaces = [
|
||||
{
|
||||
id: 'default',
|
||||
|
@ -449,9 +449,8 @@ describe('onPostAuthInterceptor', () => {
|
|||
|
||||
const { response, spacesService } = await request('/s/a-space', spaces);
|
||||
|
||||
// OSS handles this redirection for us
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.header.location).toEqual(`/s/a-space${defaultRoute}`);
|
||||
expect(response.header.location).toEqual(`/s/a-space/spaces/enter`);
|
||||
|
||||
expect(spacesService.scopedClient).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -463,7 +462,7 @@ describe('onPostAuthInterceptor', () => {
|
|||
}, 30000);
|
||||
|
||||
describe('with a single available space', () => {
|
||||
it('it redirects to the defaultRoute within the context of the single Space when navigating to Kibana root', async () => {
|
||||
it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => {
|
||||
const spaces = [
|
||||
{
|
||||
id: 'a-space',
|
||||
|
@ -477,7 +476,7 @@ describe('onPostAuthInterceptor', () => {
|
|||
const { response, spacesService } = await request('/', spaces);
|
||||
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.header.location).toEqual(`/s/a-space${defaultRoute}`);
|
||||
expect(response.header.location).toEqual(`/s/a-space/spaces/enter`);
|
||||
|
||||
expect(spacesService.scopedClient).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -488,7 +487,7 @@ describe('onPostAuthInterceptor', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('it redirects to the defaultRoute within the context of the Default Space when navigating to Kibana root', async () => {
|
||||
it('it redirects to the "enter space" endpoint within the context of the Default Space when navigating to Kibana root', async () => {
|
||||
// This is very similar to the test above, but this handles the condition where the only available space is the Default Space,
|
||||
// which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user
|
||||
// is redirected to does not contain a space identifier (e.g., /s/foo)
|
||||
|
@ -506,7 +505,7 @@ describe('onPostAuthInterceptor', () => {
|
|||
const { response, spacesService } = await request('/', spaces);
|
||||
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.header.location).toEqual(defaultRoute);
|
||||
expect(response.header.location).toEqual('/spaces/enter');
|
||||
expect(spacesService.scopedClient).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
import { Logger, CoreSetup } from 'src/core/server';
|
||||
import { Space } from '../../../common/model/space';
|
||||
import { wrapError } from '../errors';
|
||||
import { addSpaceIdToPath } from '../spaces_url_parser';
|
||||
import { XPackMainPlugin } from '../../../../xpack_main/xpack_main';
|
||||
import { SpacesServiceSetup } from '../../new_platform/spaces_service/spaces_service';
|
||||
import { LegacyAPI } from '../../new_platform/plugin';
|
||||
import { getSpaceSelectorUrl } from '../get_space_selector_url';
|
||||
import { DEFAULT_SPACE_ID } from '../../../common/constants';
|
||||
import { DEFAULT_SPACE_ID, ENTER_SPACE_PATH } from '../../../common/constants';
|
||||
import { addSpaceIdToPath } from '../../../common';
|
||||
|
||||
export interface OnPostAuthInterceptorDeps {
|
||||
getLegacyAPI(): LegacyAPI;
|
||||
|
@ -28,7 +28,7 @@ export function initSpacesOnPostAuthRequestInterceptor({
|
|||
log,
|
||||
http,
|
||||
}: OnPostAuthInterceptorDeps) {
|
||||
const { serverBasePath, serverDefaultRoute } = getLegacyAPI().legacyConfig;
|
||||
const { serverBasePath } = getLegacyAPI().legacyConfig;
|
||||
|
||||
http.registerOnPostAuth(async (request, response, toolkit) => {
|
||||
const path = request.url.pathname!;
|
||||
|
@ -38,6 +38,7 @@ export function initSpacesOnPostAuthRequestInterceptor({
|
|||
// The root of kibana is also the root of the defaut space,
|
||||
// since the default space does not have a URL Identifier (i.e., `/s/foo`).
|
||||
const isRequestingKibanaRoot = path === '/' && spaceId === DEFAULT_SPACE_ID;
|
||||
const isRequestingSpaceRoot = path === '/' && spaceId !== DEFAULT_SPACE_ID;
|
||||
const isRequestingApplication = path.startsWith('/app');
|
||||
|
||||
const spacesClient = await spacesService.scopedClient(request);
|
||||
|
@ -54,7 +55,7 @@ export function initSpacesOnPostAuthRequestInterceptor({
|
|||
// No need for an interstitial screen where there is only one possible outcome.
|
||||
const space = spaces[0];
|
||||
|
||||
const destination = addSpaceIdToPath(serverBasePath, space.id, serverDefaultRoute);
|
||||
const destination = addSpaceIdToPath(serverBasePath, space.id, ENTER_SPACE_PATH);
|
||||
return response.redirected({ headers: { location: destination } });
|
||||
}
|
||||
|
||||
|
@ -72,6 +73,9 @@ export function initSpacesOnPostAuthRequestInterceptor({
|
|||
statusCode: wrappedError.output.statusCode,
|
||||
});
|
||||
}
|
||||
} else if (isRequestingSpaceRoot) {
|
||||
const destination = addSpaceIdToPath(serverBasePath, spaceId, ENTER_SPACE_PATH);
|
||||
return response.redirected({ headers: { location: destination } });
|
||||
}
|
||||
|
||||
// This condition should only happen after selecting a space, or when transitioning from one application to another
|
||||
|
|
|
@ -11,9 +11,9 @@ import {
|
|||
} from 'src/core/server';
|
||||
import { format } from 'url';
|
||||
import { DEFAULT_SPACE_ID } from '../../../common/constants';
|
||||
import { getSpaceIdFromPath } from '../spaces_url_parser';
|
||||
import { modifyUrl } from '../utils/url';
|
||||
import { LegacyAPI } from '../../new_platform/plugin';
|
||||
import { getSpaceIdFromPath } from '../../../common';
|
||||
|
||||
export interface OnRequestInterceptorDeps {
|
||||
getLegacyAPI(): LegacyAPI;
|
||||
|
|
|
@ -20,7 +20,6 @@ import { checkLicense } from '../lib/check_license';
|
|||
import { spacesSavedObjectsClientWrapperFactory } from '../lib/saved_objects_client/saved_objects_client_wrapper_factory';
|
||||
import { SpacesAuditLogger } from '../lib/audit_logger';
|
||||
import { createSpacesTutorialContextFactory } from '../lib/spaces_tutorial_context_factory';
|
||||
import { initInternalApis } from '../routes/api/v1';
|
||||
import { initExternalSpacesApi } from '../routes/api/external';
|
||||
import { getSpacesUsageCollector } from '../lib/get_spaces_usage_collector';
|
||||
import { SpacesService } from './spaces_service';
|
||||
|
@ -178,13 +177,6 @@ export class Plugin {
|
|||
})
|
||||
);
|
||||
|
||||
initInternalApis({
|
||||
legacyRouter: legacyAPI.router,
|
||||
getLegacyAPI: this.getLegacyAPI,
|
||||
spacesService,
|
||||
xpackMain: xpackMainPlugin,
|
||||
});
|
||||
|
||||
initExternalSpacesApi({
|
||||
legacyRouter: legacyAPI.router,
|
||||
log: this.log,
|
||||
|
|
|
@ -13,9 +13,9 @@ import {
|
|||
SavedObjectsErrorHelpers,
|
||||
} from 'src/core/server';
|
||||
import { DEFAULT_SPACE_ID } from '../../../common/constants';
|
||||
import { getSpaceIdFromPath } from '../../lib/spaces_url_parser';
|
||||
import { createOptionalPlugin } from '../../../../../server/lib/optional_plugin';
|
||||
import { LegacyAPI } from '../plugin';
|
||||
import { getSpaceIdFromPath } from '../../../common';
|
||||
|
||||
const mockLogger = {
|
||||
trace: jest.fn(),
|
||||
|
|
|
@ -12,11 +12,11 @@ import { OptionalPlugin } from '../../../../../server/lib/optional_plugin';
|
|||
import { DEFAULT_SPACE_ID } from '../../../common/constants';
|
||||
import { SecurityPlugin } from '../../../../security';
|
||||
import { SpacesClient } from '../../lib/spaces_client';
|
||||
import { getSpaceIdFromPath, addSpaceIdToPath } from '../../lib/spaces_url_parser';
|
||||
import { SpacesConfigType } from '../config';
|
||||
import { namespaceToSpaceId, spaceIdToNamespace } from '../../lib/utils/namespace';
|
||||
import { LegacyAPI } from '../plugin';
|
||||
import { Space } from '../../../common/model/space';
|
||||
import { getSpaceIdFromPath, addSpaceIdToPath } from '../../../common';
|
||||
|
||||
type RequestFacade = KibanaRequest | Legacy.Request;
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import { createSpaces } from './create_spaces';
|
|||
import { ExternalRouteDeps } from '../external';
|
||||
import { SpacesService } from '../../../new_platform/spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
import { InternalRouteDeps } from '../v1';
|
||||
import { LegacyAPI } from '../../../new_platform/plugin';
|
||||
|
||||
interface KibanaServer extends Legacy.Server {
|
||||
|
@ -79,9 +78,7 @@ async function readStreamToCompletion(stream: Readable) {
|
|||
return (createPromiseFromStreams([stream, createConcatStream([])]) as unknown) as any[];
|
||||
}
|
||||
|
||||
export function createTestHandler(
|
||||
initApiFn: (deps: ExternalRouteDeps & InternalRouteDeps) => void
|
||||
) {
|
||||
export function createTestHandler(initApiFn: (deps: ExternalRouteDeps) => void) {
|
||||
const teardowns: TeardownFn[] = [];
|
||||
|
||||
const spaces = createSpaces();
|
||||
|
@ -254,7 +251,6 @@ export function createTestHandler(
|
|||
});
|
||||
|
||||
initApiFn({
|
||||
getLegacyAPI: () => legacyAPI,
|
||||
routePreCheckLicenseFn: pre,
|
||||
savedObjects: server.savedObjects,
|
||||
spacesService,
|
||||
|
|
|
@ -1,36 +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 { Legacy } from 'kibana';
|
||||
import { XPackMainPlugin } from '../../../../../xpack_main/xpack_main';
|
||||
import { routePreCheckLicense } from '../../../lib/route_pre_check_license';
|
||||
import { initInternalSpacesApi } from './spaces';
|
||||
import { SpacesServiceSetup } from '../../../new_platform/spaces_service/spaces_service';
|
||||
import { LegacyAPI } from '../../../new_platform/plugin';
|
||||
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
interface RouteDeps {
|
||||
xpackMain: XPackMainPlugin;
|
||||
spacesService: SpacesServiceSetup;
|
||||
getLegacyAPI(): LegacyAPI;
|
||||
legacyRouter: Legacy.Server['route'];
|
||||
}
|
||||
|
||||
export interface InternalRouteDeps extends Omit<RouteDeps, 'xpackMain'> {
|
||||
routePreCheckLicenseFn: any;
|
||||
}
|
||||
|
||||
export function initInternalApis({ xpackMain, ...rest }: RouteDeps) {
|
||||
const routePreCheckLicenseFn = routePreCheckLicense({ xpackMain });
|
||||
|
||||
const deps: InternalRouteDeps = {
|
||||
...rest,
|
||||
routePreCheckLicenseFn,
|
||||
};
|
||||
|
||||
initInternalSpacesApi(deps);
|
||||
}
|
|
@ -1,93 +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.
|
||||
*/
|
||||
|
||||
jest.mock('../../../lib/route_pre_check_license', () => {
|
||||
return {
|
||||
routePreCheckLicense: () => (request: any, h: any) => h.continue,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../server/lib/get_client_shield', () => {
|
||||
return {
|
||||
getClient: () => {
|
||||
return {
|
||||
callWithInternalUser: jest.fn(() => {
|
||||
return;
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import Boom from 'boom';
|
||||
import { createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
|
||||
import { initInternalSpacesApi } from './spaces';
|
||||
|
||||
describe('Spaces API', () => {
|
||||
let request: RequestRunner;
|
||||
let teardowns: TeardownFn[];
|
||||
|
||||
beforeEach(() => {
|
||||
const setup = createTestHandler(initInternalSpacesApi);
|
||||
|
||||
request = setup.request;
|
||||
teardowns = setup.teardowns;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(teardowns.splice(0).map(fn => fn()));
|
||||
});
|
||||
|
||||
test('POST space/{id}/select should respond with the new space location', async () => {
|
||||
const { response } = await request('POST', '/api/spaces/v1/space/a-space/select');
|
||||
|
||||
const { statusCode, payload } = response;
|
||||
|
||||
expect(statusCode).toEqual(200);
|
||||
|
||||
const result = JSON.parse(payload);
|
||||
expect(result.location).toEqual('/s/a-space');
|
||||
});
|
||||
|
||||
test(`returns result of routePreCheckLicense`, async () => {
|
||||
const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', {
|
||||
preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'),
|
||||
expectSpacesClientCall: false,
|
||||
});
|
||||
|
||||
const { statusCode, payload } = response;
|
||||
|
||||
expect(statusCode).toEqual(403);
|
||||
expect(JSON.parse(payload)).toMatchObject({
|
||||
message: 'test forbidden message',
|
||||
});
|
||||
});
|
||||
|
||||
test('POST space/{id}/select should respond with 404 when the space is not found', async () => {
|
||||
const { response } = await request('POST', '/api/spaces/v1/space/not-a-space/select');
|
||||
|
||||
const { statusCode } = response;
|
||||
|
||||
expect(statusCode).toEqual(404);
|
||||
});
|
||||
|
||||
test('POST space/{id}/select should respond with the new space location when a server.basePath is in use', async () => {
|
||||
const testConfig = {
|
||||
'server.basePath': '/my/base/path',
|
||||
};
|
||||
|
||||
const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', {
|
||||
testConfig,
|
||||
});
|
||||
|
||||
const { statusCode, payload } = response;
|
||||
|
||||
expect(statusCode).toEqual(200);
|
||||
|
||||
const result = JSON.parse(payload);
|
||||
expect(result.location).toEqual('/my/base/path/s/a-space');
|
||||
});
|
||||
});
|
|
@ -1,51 +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 Boom from 'boom';
|
||||
import { Space } from '../../../../common/model/space';
|
||||
import { wrapError } from '../../../lib/errors';
|
||||
import { SpacesClient } from '../../../lib/spaces_client';
|
||||
import { addSpaceIdToPath } from '../../../lib/spaces_url_parser';
|
||||
import { getSpaceById } from '../../lib';
|
||||
import { InternalRouteDeps } from '.';
|
||||
|
||||
export function initInternalSpacesApi(deps: InternalRouteDeps) {
|
||||
const { legacyRouter, spacesService, getLegacyAPI, routePreCheckLicenseFn } = deps;
|
||||
|
||||
legacyRouter({
|
||||
method: 'POST',
|
||||
path: '/api/spaces/v1/space/{id}/select',
|
||||
async handler(request: any) {
|
||||
const { savedObjects, legacyConfig } = getLegacyAPI();
|
||||
|
||||
const { SavedObjectsClient } = savedObjects;
|
||||
const spacesClient: SpacesClient = await spacesService.scopedClient(request);
|
||||
const id = request.params.id;
|
||||
|
||||
const basePath = legacyConfig.serverBasePath;
|
||||
const defaultRoute = legacyConfig.serverDefaultRoute;
|
||||
try {
|
||||
const existingSpace: Space | null = await getSpaceById(
|
||||
spacesClient,
|
||||
id,
|
||||
SavedObjectsClient.errors
|
||||
);
|
||||
if (!existingSpace) {
|
||||
return Boom.notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
location: addSpaceIdToPath(basePath, existingSpace.id, defaultRoute),
|
||||
};
|
||||
} catch (error) {
|
||||
return wrapError(error);
|
||||
}
|
||||
},
|
||||
options: {
|
||||
pre: [routePreCheckLicenseFn],
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { Legacy } from 'kibana';
|
||||
import { ENTER_SPACE_PATH } from '../../../common/constants';
|
||||
import { wrapError } from '../../lib/errors';
|
||||
|
||||
export function initEnterSpaceView(server: Legacy.Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: ENTER_SPACE_PATH,
|
||||
async handler(request, h) {
|
||||
try {
|
||||
return h.redirect(await request.getDefaultRoute());
|
||||
} catch (e) {
|
||||
server.log(['spaces', 'error'], `Error navigating to space: ${e}`);
|
||||
return wrapError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
|
@ -5,3 +5,4 @@
|
|||
*/
|
||||
|
||||
export { initSpaceSelectorView } from './space_selector';
|
||||
export { initEnterSpaceView } from './enter_space';
|
||||
|
|
|
@ -11074,8 +11074,6 @@
|
|||
"xpack.spaces.spaceSelector.findSpacePlaceholder": "スペースを検索",
|
||||
"xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription": "検索条件に一致するスペースがありません",
|
||||
"xpack.spaces.spaceSelector.selectSpacesTitle": "スペースの選択",
|
||||
"xpack.spaces.spacesManager.unableToChangeSpaceWarningDescription": "後程再試行してください",
|
||||
"xpack.spaces.spacesManager.unableToChangeSpaceWarningTitle": "スペースを変更できません",
|
||||
"xpack.spaces.spacesTitle": "スペース",
|
||||
"xpack.spaces.management.copyToSpace.actionDescription": "この保存されたオブジェクトを 1 つまたは複数のスペースにコピーします。",
|
||||
"xpack.spaces.management.copyToSpace.actionTitle": "スペースにコピー",
|
||||
|
|
|
@ -11076,8 +11076,6 @@
|
|||
"xpack.spaces.spaceSelector.findSpacePlaceholder": "查找工作区",
|
||||
"xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription": "没有匹配搜索条件的空间",
|
||||
"xpack.spaces.spaceSelector.selectSpacesTitle": "选择您的空间",
|
||||
"xpack.spaces.spacesManager.unableToChangeSpaceWarningDescription": "请稍后重试",
|
||||
"xpack.spaces.spacesManager.unableToChangeSpaceWarningTitle": "无法更改空间",
|
||||
"xpack.spaces.spacesTitle": "工作区",
|
||||
"xpack.spaces.management.copyToSpace.actionDescription": "将此已保存对象复制到一个或多个工作区",
|
||||
"xpack.spaces.management.copyToSpace.actionTitle": "复制到工作区",
|
||||
|
|
60
x-pack/test/functional/apps/spaces/enter_space.ts
Normal file
60
x-pack/test/functional/apps/spaces/enter_space.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function enterSpaceFunctonalTests({
|
||||
getService,
|
||||
getPageObjects,
|
||||
}: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['security', 'spaceSelector']);
|
||||
|
||||
describe('Enter Space', function() {
|
||||
this.tags('smoke');
|
||||
before(async () => await esArchiver.load('spaces/enter_space'));
|
||||
after(async () => await esArchiver.unload('spaces/enter_space'));
|
||||
|
||||
afterEach(async () => {
|
||||
await PageObjects.security.logout();
|
||||
});
|
||||
|
||||
it('allows user to navigate to different spaces, respecting the configured default route', async () => {
|
||||
const spaceId = 'another-space';
|
||||
|
||||
await PageObjects.security.login(null, null, {
|
||||
expectSpaceSelector: true,
|
||||
});
|
||||
|
||||
await PageObjects.spaceSelector.clickSpaceCard(spaceId);
|
||||
|
||||
await PageObjects.spaceSelector.expectRoute(spaceId, '/app/kibana/#/dashboard');
|
||||
|
||||
await PageObjects.spaceSelector.openSpacesNav();
|
||||
|
||||
// change spaces
|
||||
|
||||
await PageObjects.spaceSelector.clickSpaceAvatar('default');
|
||||
|
||||
await PageObjects.spaceSelector.expectRoute('default', '/app/canvas');
|
||||
});
|
||||
|
||||
it('falls back to the default home page when the configured default route is malformed', async () => {
|
||||
await kibanaServer.uiSettings.replace({ defaultRoute: 'http://example.com/evil' });
|
||||
|
||||
// This test only works with the default space, as other spaces have an enforced relative url of `${serverBasePath}/s/space-id/${defaultRoute}`
|
||||
const spaceId = 'default';
|
||||
|
||||
await PageObjects.security.login(null, null, {
|
||||
expectSpaceSelector: true,
|
||||
});
|
||||
|
||||
await PageObjects.spaceSelector.clickSpaceCard(spaceId);
|
||||
|
||||
await PageObjects.spaceSelector.expectHomePage(spaceId);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -12,5 +12,6 @@ export default function spacesApp({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./copy_saved_objects'));
|
||||
loadTestFile(require.resolve('./feature_controls/spaces_security'));
|
||||
loadTestFile(require.resolve('./spaces_selection'));
|
||||
loadTestFile(require.resolve('./enter_space'));
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,287 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"config": {
|
||||
"dynamic": "true",
|
||||
"properties": {
|
||||
"buildNum": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"dateFormat:tz": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"defaultRoute": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionsJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"panelsJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"refreshInterval": {
|
||||
"properties": {
|
||||
"display": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"pause": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"section": {
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeFrom": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timeRestore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timeTo": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"uiStateJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"index-pattern": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"fieldFormatMap": {
|
||||
"type": "text"
|
||||
},
|
||||
"fields": {
|
||||
"type": "text"
|
||||
},
|
||||
"intervalName": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"notExpandable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sourceFilters": {
|
||||
"type": "text"
|
||||
},
|
||||
"timeFieldName": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"columns": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"space": {
|
||||
"properties": {
|
||||
"_reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"color": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"disabledFeatures": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"initials": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 2048,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"spaceId": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timelion-sheet": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timelion_chart_height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_columns": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_interval": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timelion_other_interval": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timelion_rows": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_sheet": {
|
||||
"type": "text"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"url": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"accessCount": {
|
||||
"type": "long"
|
||||
},
|
||||
"accessDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"createDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"url": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 2048,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualization": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"savedSearchId": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"uiStateJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"visState": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_replicas": "1",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,14 +28,18 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }) {
|
|||
}
|
||||
|
||||
async expectHomePage(spaceId) {
|
||||
return await this.expectRoute(spaceId, `/app/kibana#/home`);
|
||||
}
|
||||
|
||||
async expectRoute(spaceId, route) {
|
||||
return await retry.try(async () => {
|
||||
log.debug(`expectHomePage(${spaceId})`);
|
||||
log.debug(`expectRoute(${spaceId}, ${route})`);
|
||||
await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000);
|
||||
const url = await browser.getCurrentUrl();
|
||||
if (spaceId === 'default') {
|
||||
expect(url).to.contain(`/app/kibana#/home`);
|
||||
expect(url).to.contain(route);
|
||||
} else {
|
||||
expect(url).to.contain(`/s/${spaceId}/app/kibana#/home`);
|
||||
expect(url).to.contain(`/s/${spaceId}${route}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,125 +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 expect from '@kbn/expect';
|
||||
import { SuperTest } from 'supertest';
|
||||
import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants';
|
||||
import { getUrlPrefix } from '../lib/space_test_utils';
|
||||
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
|
||||
|
||||
interface SelectTest {
|
||||
statusCode: number;
|
||||
response: (resp: { [key: string]: any }) => void;
|
||||
}
|
||||
|
||||
interface SelectTests {
|
||||
default: SelectTest;
|
||||
}
|
||||
|
||||
interface SelectTestDefinition {
|
||||
user?: TestDefinitionAuthentication;
|
||||
currentSpaceId: string;
|
||||
selectSpaceId: string;
|
||||
tests: SelectTests;
|
||||
}
|
||||
|
||||
const nonExistantSpaceId = 'not-a-space';
|
||||
|
||||
export function selectTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
|
||||
const createExpectEmptyResult = () => (resp: { [key: string]: any }) => {
|
||||
expect(resp.body).to.eql('');
|
||||
};
|
||||
|
||||
const createExpectNotFoundResult = () => (resp: { [key: string]: any }) => {
|
||||
expect(resp.body).to.eql({
|
||||
error: 'Not Found',
|
||||
message: 'Not Found',
|
||||
statusCode: 404,
|
||||
});
|
||||
};
|
||||
|
||||
const createExpectRbacForbidden = (spaceId: any) => (resp: { [key: string]: any }) => {
|
||||
expect(resp.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: `Unauthorized to get ${spaceId} space`,
|
||||
});
|
||||
};
|
||||
|
||||
const createExpectResults = (spaceId: string) => (resp: { [key: string]: any }) => {
|
||||
const allSpaces = [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default Space',
|
||||
description: 'This is the default space',
|
||||
disabledFeatures: [],
|
||||
_reserved: true,
|
||||
},
|
||||
{
|
||||
id: 'space_1',
|
||||
name: 'Space 1',
|
||||
description: 'This is the first test space',
|
||||
disabledFeatures: [],
|
||||
},
|
||||
{
|
||||
id: 'space_2',
|
||||
name: 'Space 2',
|
||||
description: 'This is the second test space',
|
||||
disabledFeatures: [],
|
||||
},
|
||||
];
|
||||
expect(resp.body).to.eql(allSpaces.find(space => space.id === spaceId));
|
||||
};
|
||||
|
||||
const createExpectSpaceResponse = (spaceId: string) => (resp: { [key: string]: any }) => {
|
||||
if (spaceId === DEFAULT_SPACE_ID) {
|
||||
expectDefaultSpaceResponse(resp);
|
||||
} else {
|
||||
expect(resp.body).to.eql({
|
||||
location: `/s/${spaceId}/app/kibana`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const expectDefaultSpaceResponse = (resp: { [key: string]: any }) => {
|
||||
expect(resp.body).to.eql({
|
||||
location: `/app/kibana`,
|
||||
});
|
||||
};
|
||||
|
||||
const makeSelectTest = (describeFn: DescribeFn) => (
|
||||
description: string,
|
||||
{ user = {}, currentSpaceId, selectSpaceId, tests }: SelectTestDefinition
|
||||
) => {
|
||||
describeFn(description, () => {
|
||||
before(() => esArchiver.load('saved_objects/spaces'));
|
||||
after(() => esArchiver.unload('saved_objects/spaces'));
|
||||
|
||||
it(`should return ${tests.default.statusCode}`, async () => {
|
||||
return supertest
|
||||
.post(`${getUrlPrefix(currentSpaceId)}/api/spaces/v1/space/${selectSpaceId}/select`)
|
||||
.auth(user.username, user.password)
|
||||
.expect(tests.default.statusCode)
|
||||
.then(tests.default.response);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const selectTest = makeSelectTest(describe);
|
||||
// @ts-ignore
|
||||
selectTest.only = makeSelectTest(describe.only);
|
||||
|
||||
return {
|
||||
createExpectEmptyResult,
|
||||
createExpectNotFoundResult,
|
||||
createExpectRbacForbidden,
|
||||
createExpectResults,
|
||||
createExpectSpaceResponse,
|
||||
expectDefaultSpaceResponse,
|
||||
nonExistantSpaceId,
|
||||
selectTest,
|
||||
};
|
||||
}
|
|
@ -25,7 +25,6 @@ export default function({ loadTestFile, getService }: TestInvoker) {
|
|||
loadTestFile(require.resolve('./delete'));
|
||||
loadTestFile(require.resolve('./get_all'));
|
||||
loadTestFile(require.resolve('./get'));
|
||||
loadTestFile(require.resolve('./select'));
|
||||
loadTestFile(require.resolve('./update'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,341 +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 { AUTHENTICATION } from '../../common/lib/authentication';
|
||||
import { SPACES } from '../../common/lib/spaces';
|
||||
import { TestInvoker } from '../../common/lib/types';
|
||||
import { selectTestSuiteFactory } from '../../common/suites/select';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function selectSpaceTestSuite({ getService }: TestInvoker) {
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
const {
|
||||
selectTest,
|
||||
nonExistantSpaceId,
|
||||
createExpectSpaceResponse,
|
||||
createExpectRbacForbidden,
|
||||
createExpectNotFoundResult,
|
||||
} = selectTestSuiteFactory(esArchiver, supertestWithoutAuth);
|
||||
|
||||
describe('select', () => {
|
||||
// Tests with users that have privileges globally in Kibana
|
||||
[
|
||||
{
|
||||
currentSpaceId: SPACES.DEFAULT.spaceId,
|
||||
selectSpaceId: SPACES.SPACE_1.spaceId,
|
||||
users: {
|
||||
noAccess: AUTHENTICATION.NOT_A_KIBANA_USER,
|
||||
superuser: AUTHENTICATION.SUPERUSER,
|
||||
allGlobally: AUTHENTICATION.KIBANA_RBAC_USER,
|
||||
readGlobally: AUTHENTICATION.KIBANA_RBAC_DASHBOARD_ONLY_USER,
|
||||
legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER,
|
||||
dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER,
|
||||
dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER,
|
||||
},
|
||||
},
|
||||
{
|
||||
currentSpaceId: SPACES.SPACE_1.spaceId,
|
||||
selectSpaceId: SPACES.DEFAULT.spaceId,
|
||||
users: {
|
||||
noAccess: AUTHENTICATION.NOT_A_KIBANA_USER,
|
||||
superuser: AUTHENTICATION.SUPERUSER,
|
||||
allGlobally: AUTHENTICATION.KIBANA_RBAC_USER,
|
||||
readGlobally: AUTHENTICATION.KIBANA_RBAC_DASHBOARD_ONLY_USER,
|
||||
legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER,
|
||||
dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER,
|
||||
dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER,
|
||||
},
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(
|
||||
`user with no access selects ${scenario.selectSpaceId} space from the ${scenario.currentSpaceId} space`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.noAccess,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 403,
|
||||
response: createExpectRbacForbidden(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`superuser selects ${scenario.selectSpaceId} space from the ${scenario.currentSpaceId} space`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.superuser,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`rbac user with all globally selects ${scenario.selectSpaceId} space from the ${scenario.currentSpaceId} space`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.allGlobally,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`dual-privileges user selects ${scenario.selectSpaceId} space from the ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.dualAll,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`legacy user selects ${scenario.selectSpaceId} space from the ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.legacyAll,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 403,
|
||||
response: createExpectRbacForbidden(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`user with read globally selects ${scenario.selectSpaceId} space from the
|
||||
${scenario.currentSpaceId} space`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.readGlobally,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`dual-privileges readonly user selects ${scenario.selectSpaceId} space from
|
||||
the ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.dualRead,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Select the same space that you're currently in with users which have space specific privileges.
|
||||
// Our intent is to ensure that you have privileges at the space that you're selecting.
|
||||
[
|
||||
{
|
||||
spaceId: SPACES.DEFAULT.spaceId,
|
||||
users: {
|
||||
allAtSpace: AUTHENTICATION.KIBANA_RBAC_DEFAULT_SPACE_ALL_USER,
|
||||
readAtSpace: AUTHENTICATION.KIBANA_RBAC_DEFAULT_SPACE_READ_USER,
|
||||
allAtOtherSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_1_ALL_USER,
|
||||
},
|
||||
},
|
||||
{
|
||||
spaceId: SPACES.SPACE_1.spaceId,
|
||||
users: {
|
||||
allAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_1_ALL_USER,
|
||||
readAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_1_READ_USER,
|
||||
allAtOtherSpace: AUTHENTICATION.KIBANA_RBAC_DEFAULT_SPACE_ALL_USER,
|
||||
},
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(
|
||||
`rbac user with all at space can select ${scenario.spaceId}
|
||||
from the same space`,
|
||||
{
|
||||
currentSpaceId: scenario.spaceId,
|
||||
selectSpaceId: scenario.spaceId,
|
||||
user: scenario.users.allAtSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.spaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`rbac user with read at space can select ${scenario.spaceId}
|
||||
from the same space`,
|
||||
{
|
||||
currentSpaceId: scenario.spaceId,
|
||||
selectSpaceId: scenario.spaceId,
|
||||
user: scenario.users.readAtSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.spaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`rbac user with all at other space cannot select ${scenario.spaceId}
|
||||
from the same space`,
|
||||
{
|
||||
currentSpaceId: scenario.spaceId,
|
||||
selectSpaceId: scenario.spaceId,
|
||||
user: scenario.users.allAtOtherSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 403,
|
||||
response: createExpectRbacForbidden(scenario.spaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Select a different space with users that only have privileges at certain spaces. Our intent
|
||||
// is to ensure that a user can select a space based on their privileges at the space that they're selecting
|
||||
// not at the space that they're currently in.
|
||||
[
|
||||
{
|
||||
currentSpaceId: SPACES.SPACE_2.spaceId,
|
||||
selectSpaceId: SPACES.SPACE_1.spaceId,
|
||||
users: {
|
||||
userWithAllAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_1_ALL_USER,
|
||||
userWithAllAtOtherSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_2_ALL_USER,
|
||||
userWithAllAtBothSpaces: AUTHENTICATION.KIBANA_RBAC_SPACE_1_2_ALL_USER,
|
||||
},
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(
|
||||
`rbac user with all at ${scenario.selectSpaceId} can select ${scenario.selectSpaceId}
|
||||
from ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.userWithAllAtSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`rbac user with all at both spaces can select ${scenario.selectSpaceId}
|
||||
from ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.userWithAllAtBothSpaces,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
selectTest(
|
||||
`rbac user with all at ${scenario.currentSpaceId} space cannot select ${scenario.selectSpaceId}
|
||||
from ${scenario.currentSpaceId}`,
|
||||
{
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.userWithAllAtOtherSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 403,
|
||||
response: createExpectRbacForbidden(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Select non-existent spaces and ensure we get a 404 or a 403
|
||||
describe('non-existent space', () => {
|
||||
[
|
||||
{
|
||||
currentSpaceId: SPACES.DEFAULT.spaceId,
|
||||
selectSpaceId: nonExistantSpaceId,
|
||||
users: {
|
||||
userWithAllGlobally: AUTHENTICATION.KIBANA_RBAC_USER,
|
||||
userWithAllAtSpace: AUTHENTICATION.KIBANA_RBAC_DEFAULT_SPACE_ALL_USER,
|
||||
},
|
||||
},
|
||||
{
|
||||
currentSpaceId: SPACES.SPACE_1.spaceId,
|
||||
selectSpaceId: nonExistantSpaceId,
|
||||
users: {
|
||||
userWithAllGlobally: AUTHENTICATION.KIBANA_RBAC_USER,
|
||||
userWithAllAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_1_ALL_USER,
|
||||
},
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(`rbac user with all globally cannot access non-existent space`, {
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.userWithAllGlobally,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 404,
|
||||
response: createExpectNotFoundResult(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
selectTest(`rbac user with all at space cannot access non-existent space`, {
|
||||
currentSpaceId: scenario.currentSpaceId,
|
||||
selectSpaceId: scenario.selectSpaceId,
|
||||
user: scenario.users.userWithAllAtSpace,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 403,
|
||||
response: createExpectRbacForbidden(scenario.selectSpaceId),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -17,7 +17,6 @@ export default function spacesOnlyTestSuite({ loadTestFile }: TestInvoker) {
|
|||
loadTestFile(require.resolve('./delete'));
|
||||
loadTestFile(require.resolve('./get_all'));
|
||||
loadTestFile(require.resolve('./get'));
|
||||
loadTestFile(require.resolve('./select'));
|
||||
loadTestFile(require.resolve('./update'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,74 +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 { SPACES } from '../../common/lib/spaces';
|
||||
import { TestInvoker } from '../../common/lib/types';
|
||||
import { selectTestSuiteFactory } from '../../common/suites/select';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function selectSpaceTestSuite({ getService }: TestInvoker) {
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
const {
|
||||
selectTest,
|
||||
createExpectSpaceResponse,
|
||||
createExpectNotFoundResult,
|
||||
nonExistantSpaceId,
|
||||
} = selectTestSuiteFactory(esArchiver, supertestWithoutAuth);
|
||||
|
||||
describe('select', () => {
|
||||
[
|
||||
{
|
||||
spaceId: SPACES.DEFAULT.spaceId,
|
||||
otherSpaceId: SPACES.SPACE_1.spaceId,
|
||||
},
|
||||
{
|
||||
spaceId: SPACES.SPACE_1.spaceId,
|
||||
otherSpaceId: SPACES.DEFAULT.spaceId,
|
||||
},
|
||||
{
|
||||
spaceId: SPACES.SPACE_1.spaceId,
|
||||
otherSpaceId: SPACES.SPACE_2.spaceId,
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(`can select ${scenario.otherSpaceId} from ${scenario.spaceId}`, {
|
||||
currentSpaceId: scenario.spaceId,
|
||||
selectSpaceId: scenario.otherSpaceId,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 200,
|
||||
response: createExpectSpaceResponse(scenario.otherSpaceId),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-existant space', () => {
|
||||
[
|
||||
{
|
||||
spaceId: SPACES.DEFAULT.spaceId,
|
||||
otherSpaceId: nonExistantSpaceId,
|
||||
},
|
||||
{
|
||||
spaceId: SPACES.SPACE_1.spaceId,
|
||||
otherSpaceId: nonExistantSpaceId,
|
||||
},
|
||||
].forEach(scenario => {
|
||||
selectTest(`cannot select non-existant space from ${scenario.spaceId}`, {
|
||||
currentSpaceId: scenario.spaceId,
|
||||
selectSpaceId: scenario.otherSpaceId,
|
||||
tests: {
|
||||
default: {
|
||||
statusCode: 404,
|
||||
response: createExpectNotFoundResult(),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue