diff --git a/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx b/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
index b63325aa33..df27789a52 100644
--- a/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
+++ b/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
@@ -1250,13 +1250,6 @@ export function foldLine (line) {
return lineArray;
}
-export function isValidLDAPUrl (url) {
- if (url.startsWith("ldap:///")) {
- return true;
- }
- return false;
-}
-
export function getBaseDNFromTree (entrydn, treeViewRootSuffixes) {
for (const suffixObj of treeViewRootSuffixes) {
if (entrydn.toLowerCase().indexOf(suffixObj.name.toLowerCase()) !== -1) {
diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
index 2974d6dc92..ad42b402f0 100644
--- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
+++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
@@ -33,11 +33,11 @@ import {
} from '@patternfly/react-table';
import {
runGenericSearch, getSearchEntries, getAttributesNameAndOid,
- getRdnInfo, isValidIpAddress, isValidLDAPUrl, isValidHostname,
- modifyLdapEntry
+ getRdnInfo, modifyLdapEntry
} from '../../lib/utils.jsx';
import {
- valid_filter
+ valid_filter, isValidIpAddress, isValidLDAPUrl,
+ isValidHostname
} from '../../../tools.jsx';
import GenericPagination from '../../lib/genericPagination.jsx';
import LdapNavigator from '../../lib/ldapNavigator.jsx';
diff --git a/src/cockpit/389-console/src/lib/server/settings.jsx b/src/cockpit/389-console/src/lib/server/settings.jsx
index bf39369d52..20f2780ead 100644
--- a/src/cockpit/389-console/src/lib/server/settings.jsx
+++ b/src/cockpit/389-console/src/lib/server/settings.jsx
@@ -1,6 +1,6 @@
import cockpit from "cockpit";
import React from "react";
-import { log_cmd, valid_dn } from "../tools.jsx";
+import { log_cmd, valid_dn, isValidIpAddress } from "../tools.jsx";
import {
Button,
Checkbox,
@@ -10,6 +10,11 @@ import {
FormSelectOption,
Grid,
GridItem,
+ HelperText,
+ HelperTextItem,
+ Select,
+ SelectOption,
+ SelectVariant,
Spinner,
Tab,
Tabs,
@@ -72,6 +77,7 @@ const adv_attrs = [
'nsslapd-plugin-binddn-tracking',
'nsslapd-attribute-name-exceptions',
'nsslapd-dn-validate-strict',
+ 'nsslapd-haproxy-trusted-ip',
];
export class ServerSettings extends React.Component {
@@ -94,6 +100,56 @@ export class ServerSettings extends React.Component {
advSaveDisabled: true,
advReloading: false,
errObjAdv: {},
+ haproxyIPs: [],
+ _haproxyIPs: [],
+ haproxyIPsOptions: [],
+ isHaproxyIPsOpen: false,
+ invalidIP: false,
+ };
+
+ this.handleOnHaproxyIPsToggle = isHaproxyIPsOpen => {
+ this.setState({
+ isHaproxyIPsOpen,
+ invalidIP: false,
+ });
+ };
+ this.handleOnHaproxyIPsSelect = (event, selection, nav_tab) => {
+ const id = 'nsslapd-haproxy-trusted-ip';
+ const { haproxyIPs } = this.state;
+ // The first if-block is when removing an item from the list
+ if (haproxyIPs.includes(selection)) {
+ this.setState(
+ prevState => ({
+ haproxyIPs: prevState.haproxyIPs.filter(item => item !== selection),
+ isHaproxyIPsOpen: false
+ }), () => { this.validateSaveBtn(nav_tab, id, haproxyIPs.filter(item => item !== selection)) });
+ // The second if-block is when adding an item to the list
+ } else {
+ this.setState(
+ prevState => ({
+ haproxyIPs: [...prevState.haproxyIPs, selection],
+ isHaproxyIPsOpen: false,
+ }), () => { this.validateSaveBtn(nav_tab, id, [...haproxyIPs, selection]) });
+ }
+ };
+
+ this.handleOnHaproxyIPsClear = (event, nav_tab) => {
+ const id = 'nsslapd-haproxy-trusted-ip';
+ const selection = [];
+ this.setState({
+ haproxyIPs: [],
+ isHaproxyIPsOpen: false,
+ invalidIP: false,
+ }, () => { this.validateSaveBtn(nav_tab, id, selection) });
+ };
+
+ this.handleOnCreateHaproxyIP = newValue => {
+ if (!this.state.haproxyIPsOptions.includes(newValue)) {
+ this.setState({
+ haproxyIPsOptions: [...this.state.haproxyIPsOptions, newValue],
+ isHaproxyIPsOpen: false
+ });
+ }
};
// Toggle currently active tab
@@ -128,7 +184,7 @@ export class ServerSettings extends React.Component {
this.reloadRootDN = this.reloadRootDN.bind(this);
this.saveDiskMonitoring = this.saveDiskMonitoring.bind(this);
this.reloadDiskMonitoring = this.reloadDiskMonitoring.bind(this);
- this.saveAdvanced = this.saveAdvanced.bind(this);
+ this.handleSaveAdvanced = this.handleSaveAdvanced.bind(this);
this.reloadAdvanced = this.reloadAdvanced.bind(this);
this.onMinusConfig = (id, nav_tab) => {
this.setState({
@@ -198,8 +254,9 @@ export class ServerSettings extends React.Component {
let disableBtnName = "";
let config_attrs = [];
let valueErr = false;
- let errObj;
- if (nav_tab == "config") {
+ let invalidIP = false;
+ let errObj = {};
+ if (nav_tab === "config") {
config_attrs = general_attrs;
disableBtnName = "configSaveDisabled";
errObj = this.state.errObjConfig;
@@ -288,11 +345,21 @@ export class ServerSettings extends React.Component {
valueErr = true;
disableSaveBtn = true;
}
+ if (attr === 'nsslapd-haproxy-trusted-ip') {
+ for (const ip of value) {
+ if (value && !isValidIpAddress(ip)) {
+ invalidIP = true;
+ disableSaveBtn = true;
+ break;
+ }
+ }
+ }
}
errObj[attr] = valueErr;
this.setState({
[attr]: value,
+ invalidIP,
errObjConfig: errObj,
[disableBtnName]: disableSaveBtn
}, () => { this.validatePaths(disableSaveBtn) });
@@ -385,6 +452,8 @@ export class ServerSettings extends React.Component {
confirmRootpw: attrs['nsslapd-rootpw'][0],
'nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
'nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ haproxyIPs: attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
+ 'nsslapd-haproxy-trusted-ip': attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
'nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
'nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
'nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
@@ -414,6 +483,8 @@ export class ServerSettings extends React.Component {
_confirmRootpw: attrs['nsslapd-rootpw'][0],
'_nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
'_nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ _haproxyIPs: attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
+ '_nsslapd-haproxy-trusted-ip': attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
'_nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
'_nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
'_nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
@@ -597,37 +668,109 @@ export class ServerSettings extends React.Component {
});
}
- saveAdvanced() {
+ /* We use this function for multi-valued config attributes because they require a special treatment */
+ handleMultivaluedAttributeReplace(attrs) {
+ const cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'delete', 'nsslapd-haproxy-trusted-ip'
+ ];
+ log_cmd("handleMultivaluedAttributeReplace", "Removing cn=config attribute", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ if (attrs.length > 0) {
+ const cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'add', ...attrs
+ ];
+ log_cmd("handleMultivaluedAttributeReplace", "Adding multivalued cn=config attribute", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.reloadAdvanced();
+ this.props.addNotification(
+ "success",
+ "Successfully updated Advanced configuration"
+ );
+ })
+ .fail(err => {
+ const errMsg = JSON.parse(err);
+ this.reloadAdvanced();
+ this.props.addNotification(
+ "error",
+ `Error updating Advanced configuration - ${errMsg.desc}`
+ );
+ });
+ } else {
+ this.reloadAdvanced();
+ this.props.addNotification(
+ "success",
+ "Successfully updated Advanced configuration"
+ );
+ }
+ })
+ .fail(err => {
+ const errMsg = JSON.parse(err);
+ this.reloadAdvanced();
+ this.props.addNotification(
+ "error",
+ `Error updating Advanced configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleSaveAdvanced() {
this.setState({
advReloading: true,
});
+ let doHaproxy = false;
+ const addHAproxy = [];
const cmd = [
'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
'config', 'replace'
];
+
for (const attr of adv_attrs) {
- if (this.state['_' + attr] != this.state[attr]) {
- let val = this.state[attr];
- if (typeof val === "boolean") {
- if (val) {
- val = "on";
- } else {
- val = "off";
+ if (this.state['_' + attr] !== this.state[attr]) {
+ if (attr === 'nsslapd-haproxy-trusted-ip') {
+ if (this.state.haproxyIPs.sort().toString() !== this.state._haproxyIPs.sort().toString()) {
+ doHaproxy = true;
+ for (const val of this.state.haproxyIPs) {
+ addHAproxy.push(attr + "=" + val);
+ }
+ }
+ } else {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
}
+ cmd.push(attr + "=" + val);
}
- cmd.push(attr + "=" + val);
}
}
- log_cmd("saveAdvanced", "Saving Advanced configuration", cmd);
+ // If HAProxy IPs is the only thing that changed, we don't need to run the main replace
+ if (cmd.length === 5 && doHaproxy) {
+ this.handleMultivaluedAttributeReplace(addHAproxy);
+ return;
+ }
+ log_cmd("handleSaveAdvanced", "Saving Advanced configuration", cmd);
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
- this.reloadAdvanced();
- this.props.addNotification(
- "success",
- "Successfully updated Advanced configuration"
- );
+ if (doHaproxy) {
+ this.handleMultivaluedAttributeReplace(addHAproxy);
+ } else {
+ this.reloadAdvanced();
+ this.props.addNotification(
+ "success",
+ "Successfully updated Advanced configuration"
+ );
+ }
})
.fail(err => {
const errMsg = JSON.parse(err);
@@ -696,6 +839,8 @@ export class ServerSettings extends React.Component {
this.setState(() => (
{
'nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ haproxyIPs: attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
+ 'nsslapd-haproxy-trusted-ip': attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
'nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
'nsslapd-schemacheck': schemaCheck,
'nsslapd-syntaxcheck': syntaxCheck,
@@ -709,6 +854,8 @@ export class ServerSettings extends React.Component {
'nsslapd-readonly': readOnly,
// Record original values
'_nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ _haproxyIPs: attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
+ '_nsslapd-haproxy-trusted-ip': attrs['nsslapd-haproxy-trusted-ip'] ? attrs['nsslapd-haproxy-trusted-ip'] : [],
'_nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
'_nsslapd-schemacheck': schemaCheck,
'_nsslapd-syntaxcheck': syntaxCheck,
@@ -722,6 +869,7 @@ export class ServerSettings extends React.Component {
'_nsslapd-readonly': readOnly,
advReloading: false,
advSaveDisabled: true,
+ isHaproxyIPsOpen: false
})
);
})
@@ -1426,12 +1574,51 @@ export class ServerSettings extends React.Component {
/>
+
+
+ Trusted HAProxy Server IPs
+
+
+
+ {(this.state.invalidIP) &&
+
+ Invalid format for IP address
+ }
+
+