This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit 3e193b9a79c09f7ad0324af3ebb478491fe69692
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Tue Apr 16 01:07:36 2019 +0200
Issue 50041 - Add the rest UI Plugin tabs - Part 2
Description: Add UI plugin tabs for autoMembership, DNA, managedEntries,
passthroughAuthentication, usn.
Add Shared Config Entry to referentialIntegrity plugin.
Add Plugin Precedence field to the basic plugin configuration.
Fix CLI tools according to the UI changes.
https://pagure.io/389-ds-base/issue/50041
Reviewed by: mreynolds (Thanks!)
---
src/cockpit/389-console/.eslintrc.json | 11 +-
.../389-console/src/lib/plugins/accountPolicy.jsx | 610 ++++++-
.../src/lib/plugins/attributeUniqueness.jsx | 647 +++++++-
.../389-console/src/lib/plugins/autoMembership.jsx | 1015 +++++++++++-
src/cockpit/389-console/src/lib/plugins/dna.jsx | 1129 ++++++++++++-
.../src/lib/plugins/linkedAttributes.jsx | 435 ++++-
.../389-console/src/lib/plugins/managedEntries.jsx | 883 +++++++++-
.../389-console/src/lib/plugins/memberOf.jsx | 72 +-
.../src/lib/plugins/passthroughAuthentication.jsx | 1156 +++++++++++++-
.../src/lib/plugins/pluginBasicConfig.jsx | 55 +-
.../389-console/src/lib/plugins/pluginModal.jsx | 21 +-
.../389-console/src/lib/plugins/pluginTables.jsx | 1688 ++++++++++++++++++++
.../src/lib/plugins/referentialIntegrity.jsx | 744 ++++++++-
.../389-console/src/lib/plugins/retroChangelog.jsx | 223 ++-
.../src/lib/plugins/rootDNAccessControl.jsx | 247 ++-
src/cockpit/389-console/src/lib/plugins/usn.jsx | 312 +++-
.../389-console/src/lib/plugins/winsync.jsx | 234 +++
src/cockpit/389-console/src/plugins.jsx | 29 +-
src/lib389/lib389/cli_conf/__init__.py | 2 +-
.../lib389/cli_conf/plugins/accountpolicy.py | 8 +
src/lib389/lib389/cli_conf/plugins/automember.py | 110 +-
src/lib389/lib389/cli_conf/plugins/dna.py | 117 +-
.../lib389/cli_conf/plugins/managedentries.py | 31 +-
.../lib389/cli_conf/plugins/passthroughauth.py | 79 +-
src/lib389/lib389/cli_conf/plugins/referint.py | 68 +-
src/lib389/lib389/plugins.py | 33 +-
26 files changed, 9752 insertions(+), 207 deletions(-)
diff --git a/src/cockpit/389-console/.eslintrc.json
b/src/cockpit/389-console/.eslintrc.json
index 11a82c8..03ff3c4 100644
--- a/src/cockpit/389-console/.eslintrc.json
+++ b/src/cockpit/389-console/.eslintrc.json
@@ -14,13 +14,16 @@
},
"plugins": ["flowtype", "react"],
"rules": {
- "indent": ["error", 4,
+ "indent": [
+ "error",
+ 4,
{
"ObjectExpression": "first",
- "CallExpression": {"arguments": "first"},
+ "CallExpression": { "arguments": "first"
},
"MemberExpression": 2,
- "ignoredNodes": [ "JSXAttribute" ]
- }],
+ "ignoredNodes": ["JSXAttribute"]
+ }
+ ],
"newline-per-chained-call": ["error", {
"ignoreChainWithDepth": 2 }],
"lines-between-class-members": ["error", "always",
{ "exceptAfterSingleLine": true }],
"prefer-promise-reject-errors": ["error", {
"allowEmptyReject": true }],
diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
index fae8652..2d769d1 100644
--- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
@@ -5,9 +5,585 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class AccountPolicy extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.getAttributes = this.getAttributes.bind(this);
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+
+ this.state = {
+ attributes: [],
+ configArea: "",
+ configDN: "",
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: [],
+ configEntryModalShow: false,
+ fixupModalShow: false,
+ newEntry: false
+ };
+ }
+
+ openModal() {
+ this.getAttributes();
+ if (!this.state.configArea) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: "",
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: []
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ "show",
+ this.state.configArea
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Account Policy Plugin config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configDN: this.state.configArea,
+ altStateAttrName:
+ configEntry["altstateattrname"] === undefined
+ ? []
+ : [
+ {
+ id:
configEntry["altstateattrname"][0],
+ label:
configEntry["altstateattrname"][0]
+ }
+ ],
+ alwaysRecordLogin: !(
+ configEntry["alwaysrecordlogin"] === undefined
||
+ configEntry["alwaysrecordlogin"][0] ==
"no"
+ ),
+ alwaysRecordLoginAttr:
+ configEntry["alwaysrecordloginattr"] === undefined
+ ? []
+ : [
+ {
+ id:
configEntry["alwaysrecordloginattr"][0],
+ label:
configEntry["alwaysrecordloginattr"][0]
+ }
+ ],
+ limitAttrName:
+ configEntry["limitattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["limitattrname"][0],
+ label: configEntry["limitattrname"][0]
+ }
+ ],
+ specAttrName:
+ configEntry["specattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["specattrname"][0],
+ label: configEntry["specattrname"][0]
+ }
+ ],
+ stateAttrName:
+ configEntry["stateattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["stateattrname"][0],
+ label: configEntry["stateattrname"][0]
+ }
+ ]
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: this.state.configArea,
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configDN,
+ altStateAttrName,
+ alwaysRecordLogin,
+ alwaysRecordLoginAttr,
+ limitAttrName,
+ specAttrName,
+ stateAttrName
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ action,
+ configDN,
+ "--always-record-login",
+ alwaysRecordLogin ? "yes" : "no"
+ ];
+
+ cmd = [...cmd, "--alt-state-attr"];
+ if (altStateAttrName.length != 0) {
+ cmd = [...cmd, altStateAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--always-record-login-attr"];
+ if (alwaysRecordLoginAttr.length != 0) {
+ cmd = [...cmd, alwaysRecordLoginAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--limit-attr"];
+ if (limitAttrName.length != 0) {
+ cmd = [...cmd, limitAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--spec-attr"];
+ if (specAttrName.length != 0) {
+ cmd = [...cmd, specAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--state-attr"];
+ if (stateAttrName.length != 0) {
+ cmd = [...cmd, stateAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "accountPolicyOperation",
+ `Do the ${action} operation on the Account Policy Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("accountPolicyOperation", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configDN} was successfully ${action}ed`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig() {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ "delete",
+ this.state.configDN
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Account Policy Plugin config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${this.state.configDN} was successfully deleted`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Account
Policy Plugin");
+
+ this.setState({
+ configArea:
+ pluginRow["nsslapd_pluginconfigarea"] === undefined
+ ? ""
+ : pluginRow["nsslapd_pluginconfigarea"][0]
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ Manage Account Policy Plugin Shared Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configDN">
+ <Col sm={4}>
+ <ControlLabel title="DN of the
config entry">
+ Config DN
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={configDN}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="alwaysRecordLoginAttr"
+ controlId="alwaysRecordLoginAttr"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Specifies the attribute to
store the time of the last successful login in this attribute in the users directory entry
(alwaysRecordLoginAttr)"
+
+
Always Record Login Attribute
+ </Col>
+ <Col sm={5}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ alwaysRecordLoginAttr: value
+ });
+ }}
+ selected={alwaysRecordLoginAttr}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="alwaysRecordLogin"
+ checked={alwaysRecordLogin}
+ title="Sets that every entry
records its last login time (alwaysRecordLogin)"
+ onChange={this.handleCheckboxChange}
+
+
Always Record Login
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="specAttrName"
+ controlId="specAttrName"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Specifies the attribute to
identify which entries are account policy configuration entries (specAttrName)"
+
+
Specific Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ specAttrName: value
+ });
+ }}
+ selected={specAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="stateAttrName"
+ controlId="stateAttrName"
+ disabled={false}
+
+
<Col sm={4}>
+ <ControlLabel title="Specifies
the primary time attribute used to evaluate an account policy (stateAttrName)">
+ State Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ stateAttrName: value
+ });
+ }}
+ selected={stateAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="altStateAttrName"
+ controlId="altStateAttrName"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Provides a backup attribute
for the server to reference to evaluate the expiration time (altStateAttrName)"
+
+
Alternative State Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ altStateAttrName: value
+ });
+ }}
+ selected={altStateAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="limitAttrName"
disabled={false}>
+ <Col sm={4}>
+ <ControlLabel title="Specifies
the attribute within the policy to use for the account inactivation limit
(limitAttrName)">
+ Limit Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ limitAttrName: value
+ });
+ }}
+ selected={limitAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteConfig}
+ disabled={newEntry}
+
+ Delete
+ </Button>
+ <Button bsStyle="primary"
onClick={this.editConfig} disabled={newEntry}>
+ Save
+ </Button>
+ <Button bsStyle="primary"
onClick={this.addConfig} disabled={!newEntry}>
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +594,39 @@ class AccountPolicy extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={6}>
+ <Form horizontal>
+ <FormGroup key="configArea"
controlId="configArea">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="DN of the shared config entry
(nsslapd-pluginConfigArea)"
+
+
Shared Config Entry
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={configArea}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openModal}
+
+
Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
index 0521a89..3d81d58 100644
--- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
@@ -5,9 +5,637 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class AttributeUniqueness extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ attributes: [],
+ objectClasses: [],
+
+ configName: "",
+ configEnabled: false,
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: [],
+
+ newEntry: false,
+ showConfigModal: false,
+ showConfirmDeleteConfig: false
+ };
+
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+ this.getObjectClasses = this.getObjectClasses.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ }
+
+ handleSwitchChange(value) {
+ this.setState({
+ configEnabled: !value
+ });
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ // Get all the attributes and matching rules now
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "attr-uniq",
+ "list"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Attribute Uniqueness Plugin
configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ if (err != 0) {
+ let errMsg = JSON.parse(err);
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ this.getObjectClasses();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: []
+ });
+ } else {
+ let configAttrNamesList = [];
+ let configSubtreesList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "attr-uniq",
+ "show",
+ name
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Attribute Uniqueness Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ?
"" : configEntry["cn"][0],
+ configEnabled: !(
+ configEntry["nsslapd-pluginenabled"] ===
undefined ||
+ configEntry["nsslapd-pluginenabled"][0] ==
"off"
+ ),
+ acrossAllSubtrees: !(
+ configEntry["uniqueness-across-all-subtrees"]
=== undefined ||
+ configEntry["uniqueness-across-all-subtrees"][0] ==
"off"
+ ),
+ topEntryOc:
+ configEntry["uniqueness-top-entry-oc"] ===
undefined
+ ? []
+ : [
+ {
+ id:
configEntry["uniqueness-top-entry-oc"][0],
+ label:
configEntry["uniqueness-top-entry-oc"][0]
+ }
+ ],
+ subtreeEnriesOc:
+ configEntry["uniqueness-subtree-entries-oc"] ===
undefined
+ ? []
+ : [
+ {
+ id:
configEntry["uniqueness-subtree-entries-oc"][0],
+ label:
configEntry["uniqueness-subtree-entries-oc"][0]
+ }
+ ]
+ });
+
+ if (configEntry["uniqueness-attribute-name"] ===
undefined) {
+ this.setState({ attrNames: [] });
+ } else {
+ for (let value of
configEntry["uniqueness-attribute-name"]) {
+ configAttrNamesList = [
+ ...configAttrNamesList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({ attrNames: configAttrNamesList });
+ }
+ if (configEntry["uniqueness-subtrees"] === undefined)
{
+ this.setState({ subtrees: [] });
+ } else {
+ for (let value of
configEntry["uniqueness-subtrees"]) {
+ configSubtreesList = [
+ ...configSubtreesList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({ subtrees: configSubtreesList });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configName,
+ configEnabled,
+ attrNames,
+ subtrees,
+ acrossAllSubtrees,
+ topEntryOc,
+ subtreeEnriesOc
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "attr-uniq",
+ action,
+ configName,
+ "--enabled",
+ configEnabled ? "on" : "off",
+ "--across-all-subtrees",
+ acrossAllSubtrees ? "on" : "off"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ if (!(action == "add" && attrNames.length == 0)) {
+ cmd = [...cmd, "--attr-name"];
+ if (attrNames.length != 0) {
+ for (let value of attrNames) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ if (!(action == "add" && subtrees.length == 0)) {
+ cmd = [...cmd, "--subtree"];
+ if (subtrees.length != 0) {
+ for (let value of subtrees) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ cmd = [...cmd, "--top-entry-oc"];
+ if (topEntryOc.length != 0) {
+ cmd = [...cmd, topEntryOc[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--subtree-entries-oc"];
+ if (subtreeEnriesOc.length != 0) {
+ cmd = [...cmd, subtreeEnriesOc[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "attrUniqOperation",
+ `Do the ${action} operation on the Attribute Uniqueness Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("attrUniqOperation", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "attr-uniq",
+ "delete",
+ configName
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Attribute Uniqueness Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
+ getObjectClasses() {
+ const oc_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "objectclasses",
+ "list"
+ ];
+ log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);
+ cockpit
+ .spawn(oc_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const ocContent = JSON.parse(content);
+ let ocs = [];
+ for (let content of ocContent["items"]) {
+ ocs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ objectClasses: ocs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
objectClasses - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} Attribute
Uniqueness Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
controlId="configName">
+ <Col sm={3}>
+ <ControlLabel title='Sets the name
of the plug-in configuration record. (cn) You can use any string, but "attribute_name
Attribute Uniqueness" is recommended.'>
+ Config Name
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="attrNames"
+ controlId="attrNames"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the name of the
attribute whose values must be unique. This attribute is multi-valued.
(uniqueness-attribute-name)"
+
+
Attribute Names
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ attrNames: values
+ });
+ }}
+ selected={attrNames}
+ newSelectionPrefix="Add an
attribute: "
+ options={attributes}
+ placeholder="Type an attribute
name..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="subtrees"
+ controlId="subtrees"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the DN under which the
plug-in checks for uniqueness of the attributes value. This attribute is multi-valued
(uniqueness-subtrees)"
+
+
Subtrees
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ subtrees: values
+ });
+ }}
+ selected={subtrees}
+ options={[""]}
+ newSelectionPrefix="Add a
subtree: "
+ placeholder="Type a subtree
DN..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="topEntryOc"
+ controlId="topEntryOc"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Verifies that the value of
the attribute set in uniqueness-attribute-name is unique in this subtree
(uniqueness-top-entry-oc)"
+
+
Top Entry OC
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ topEntryOc: value
+ });
+ }}
+ selected={topEntryOc}
+ options={objectClasses}
+ newSelectionPrefix="Add a top
entry objectClass: "
+ placeholder="Type an
objectClass..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="subtreeEnriesOc"
+ controlId="subtreeEnriesOc"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Verifies if an attribute is
unique, if the entry contains the object class set in this parameter
(uniqueness-subtree-entries-oc)"
+
+
Subtree Entries OC
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ subtreeEnriesOc: value
+ });
+ }}
+ selected={subtreeEnriesOc}
+ options={objectClasses}
+ newSelectionPrefix="Add a
subtree entries objectClass: "
+ placeholder="Type an
objectClass..."
+ />
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="acrossAllSubtrees"
+ checked={acrossAllSubtrees}
+ title="If enabled (on), the
plug-in checks that the attribute is unique across all subtrees set. If you set the
attribute to off, uniqueness is only enforced within the subtree of the updated entry
(uniqueness-across-all-subtrees)"
+ onChange={this.handleCheckboxChange}
+
+
Across All Subtrees
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configEnabled"
+ controlId="configEnabled"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies whether or not the
config is enabled."
+
+
Enable config
+ </Col>
+ <Col sm={3}>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="configEnabled"
+ value={configEnabled}
+ onChange={() =>
+
this.handleSwitchChange(configEnabled)
+ }
+ animate={false}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +646,24 @@ class AttributeUniqueness extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <AttrUniqConfigTable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
index e3291fa..b5079ea 100644
--- a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
@@ -1,13 +1,1005 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { AutoMembershipDefinitionTable, AutoMembershipRegexTable } from
"./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class AutoMembership extends React.Component {
+ componentWillMount() {
+ this.loadDefinitions();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ definitionRows: [],
+ regexRows: [],
+ regexesToDelete: [],
+ attributes: [],
+
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: "",
+
+ regexName: "",
+ regexExclusive: [],
+ regexInclusive: [],
+ regexTargetGroup: "",
+
+ newDefinitionEntry: false,
+ newRegexEntry: false,
+ definitionEntryModalShow: false,
+ regexEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.loadDefinitions = this.loadDefinitions.bind(this);
+ this.loadRegexes = this.loadRegexes.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditDefinitionModal = this.showEditDefinitionModal.bind(this);
+ this.showAddDefinitionModal = this.showAddDefinitionModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteDefinition = this.deleteDefinition.bind(this);
+ this.addDefinition = this.addDefinition.bind(this);
+ this.editDefinition = this.editDefinition.bind(this);
+
+ this.openRegexModal = this.openRegexModal.bind(this);
+ this.closeRegexModal = this.closeRegexModal.bind(this);
+ this.showEditRegexModal = this.showEditRegexModal.bind(this);
+ this.showAddRegexModal = this.showAddRegexModal.bind(this);
+ this.cmdRegexOperation = this.cmdRegexOperation.bind(this);
+ this.deleteRegex = this.deleteRegex.bind(this);
+ this.addRegex = this.addRegex.bind(this);
+ this.editRegex = this.editRegex.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadDefinitions() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "list",
+ "definitions"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadDefinitions", "Get Auto Membership Plugin
definitions", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ definitionRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadDefinitions failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadRegexes(defName) {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "list",
+ "regexes",
+ defName
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadRegexes", "Get Auto Membership Plugin regexes",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ regexRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadRegexes failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditDefinitionModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddDefinitionModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: true,
+ regexRows: [],
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: ""
+ });
+ } else {
+ this.loadRegexes(name);
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "definition",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Auto Membership Plugin
definition entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let definitionEntry = JSON.parse(content).attrs;
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: false,
+ definitionName:
+ definitionEntry["cn"] === undefined ? ""
: definitionEntry["cn"][0],
+ defaultGroup:
+ definitionEntry["automemberdefaultgroup"] ===
undefined
+ ? ""
+ :
definitionEntry["automemberdefaultgroup"][0],
+ filter:
+ definitionEntry["automemberfilter"] === undefined
+ ? ""
+ : definitionEntry["automemberfilter"][0],
+ scope:
+ definitionEntry["automemberscope"] === undefined
+ ? ""
+ : definitionEntry["automemberscope"][0]
+ });
+
+ if (definitionEntry["automembergroupingattr"] ===
undefined) {
+ this.setState({
+ groupingAttrMember: [],
+ groupingAttrEntry: ""
+ });
+ } else {
+ let groupingAttr =
definitionEntry["automembergroupingattr"][0];
+ this.setState({
+ groupingAttrMember: [
+ {
+ id: groupingAttr.split(":")[0],
+ label: groupingAttr.split(":")[0]
+ }
+ ],
+ groupingAttrEntry: groupingAttr.split(":")[1]
+ });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: true,
+ regexRows: [],
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ showEditRegexModal(rowData) {
+ this.openRegexModal(rowData.cn[0]);
+ }
+
+ showAddRegexModal(rowData) {
+ this.openRegexModal();
+ }
+
+ closeModal() {
+ this.setState({ definitionEntryModalShow: false });
+ }
+
+ closeRegexModal() {
+ this.setState({ regexEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ definitionName,
+ defaultGroup,
+ filter,
+ groupingAttrMember,
+ groupingAttrEntry,
+ scope
+ } = this.state;
+
+ if (
+ definitionName === "" ||
+ scope === "" ||
+ filter === "" ||
+ groupingAttrMember.length == 0 ||
+ groupingAttrEntry === ""
+ ) {
+ this.props.addNotification(
+ "warning",
+ "Name, Scope, Filter and Grouping Attribute are required."
+ );
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ action,
+ "--default-group",
+ defaultGroup || action == "add" ? defaultGroup :
"delete",
+ "--filter",
+ filter || action == "add" ? filter : "delete",
+ "--scope",
+ scope || action == "add" ? scope : "delete"
+ ];
+
+ cmd = [...cmd, "--grouping-attr"];
+ if (groupingAttrMember.length != 0 && groupingAttrEntry.length != 0)
{
+ cmd = [...cmd, `${groupingAttrMember[0].id}:${groupingAttrEntry}`];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "AutoMembershipOperation",
+ `Do the ${action} operation on the Auto Membership Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("AutoMembershipOperation",
"Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${definitionName}" entry`
+ );
+ this.loadDefinitions();
+ this.purgeRegexUpdate();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc.indexOf("nothing to set") === 0) {
+ this.props.addNotification(
+ "error",
+ `Error during the definition entry ${action} operation -
${errMsg.desc}`
+ );
+ } else {
+ this.purgeRegexUpdate();
+ }
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ purgeRegexUpdate() {
+ const { definitionName, regexesToDelete, regexRows } = this.state;
+
+ for (let regexToDelete of regexesToDelete) {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "regex",
+ regexToDelete,
+ "delete"
+ ];
+
+ log_cmd(
+ "AutoMembershipRegexOperation",
+ `Do the delete operation on the Auto Membership Plugin Regex Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info(
+ "AutoMembershipRegexOperation",
+ "Result",
+ `The delete operation was successfully done on
"${regexToDelete}" entry`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the regex "${regexToDelete}" entry
delete operation - ${
+ errMsg.desc
+ }`
+ );
+ });
+ }
+
+ for (let row of regexRows) {
+ let action = "";
+ let regexName = "";
+ if (row.needsadd !== undefined) {
+ action = "add";
+ regexName = row.cn[0];
+ } else if (row.needsupdate !== undefined) {
+ action = "set";
+ regexName = row.cn[0];
+ } else {
+ continue;
+ }
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "regex",
+ regexName,
+ action
+ ];
+
+ let regexTargetGroup = row.automembertargetgroup[0];
+ let regexExclusive = row.automemberexclusiveregex;
+ let regexInclusive = row.automemberinclusiveregex;
+
+ if (!(action == "add" && regexTargetGroup == 0)) {
+ cmd = [...cmd, "--target-group"];
+ if (regexTargetGroup) {
+ cmd = [...cmd, regexTargetGroup];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ if (!(action == "add" && regexExclusive.length == 0)) {
+ cmd = [...cmd, "--exclusive"];
+ if (regexExclusive.length != 0) {
+ for (let regex of regexExclusive) {
+ cmd = [...cmd, regex];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+ if (!(action == "add" && regexInclusive.length == 0)) {
+ cmd = [...cmd, "--inclusive"];
+ if (regexInclusive.length != 0) {
+ for (let regex of regexInclusive) {
+ cmd = [...cmd, regex];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ log_cmd(
+ "AutoMembershipRegexOperation",
+ `Do the ${action} operation on the Auto Membership Plugin Regex Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info(
+ "AutoMembershipRegexOperation",
+ "Result",
+ `The ${action} operation was successfully done on
"${regexName}" entry`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the regex "${regexName}" entry
${action} operation - ${
+ errMsg.desc
+ }`
+ );
+ });
+ }
+ }
+
+ deleteDefinition(rowData) {
+ let definitionName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteDefinition", "Delete the Auto Membership Plugin
definition entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteDefinition", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `Definition entry ${definitionName} was successfully deleted`
+ );
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the definition entry removal operation -
${errMsg.desc}`
+ );
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addDefinition() {
+ this.cmdOperation("add");
+ }
+
+ editDefinition() {
+ this.cmdOperation("set");
+ }
+
+ openRegexModal(name) {
+ if (name) {
+ let regexEntry = this.state.regexRows.filter(row => row.cn[0] ===
name)[0];
+
+ let exclusiveRegexList = [];
+ let inclusiveRegexList = [];
+ // Get all the attributes and matching rules now
+ if (regexEntry["automemberexclusiveregex"] === undefined) {
+ this.setState({ regexExclusive: [] });
+ } else {
+ for (let value of regexEntry["automemberexclusiveregex"]) {
+ exclusiveRegexList = [...exclusiveRegexList, { id: value, label:
value }];
+ }
+ this.setState({ regexExclusive: exclusiveRegexList });
+ }
+ if (regexEntry["automemberinclusiveregex"] === undefined) {
+ this.setState({ regexInclusive: [] });
+ } else {
+ for (let value of regexEntry["automemberinclusiveregex"]) {
+ inclusiveRegexList = [...inclusiveRegexList, { id: value, label:
value }];
+ }
+ this.setState({ regexInclusive: inclusiveRegexList });
+ }
+ this.setState({
+ regexEntryModalShow: true,
+ newRegexEntry: false,
+ regexName: regexEntry["cn"][0],
+ regexTargetGroup:
+ regexEntry["automembertargetgroup"] === undefined
+ ? ""
+ : regexEntry["automembertargetgroup"][0]
+ });
+ } else {
+ this.setState({
+ regexEntryModalShow: true,
+ newRegexEntry: true,
+ regexName: "",
+ regexExclusive: [],
+ regexInclusive: [],
+ regexTargetGroup: ""
+ });
+ }
+ }
+
+ cmdRegexOperation(action) {
+ const {
+ regexRows,
+ regexName,
+ regexExclusive,
+ regexInclusive,
+ regexTargetGroup
+ } = this.state;
+
+ let regexExists = false;
+ if (regexRows.some(row => row.cn[0] === regexName)) {
+ regexExists = true;
+ }
+
+ if (regexName === "" || regexTargetGroup === "") {
+ this.props.addNotification("warning", "Name and Target Group
are required.");
+ } else {
+ if (action == "add") {
+ if (!regexExists) {
+ this.setState(prevState => ({
+ regexRows: [
+ ...prevState.regexRows,
+ {
+ cn: [regexName],
+ automembertargetgroup: [regexTargetGroup],
+ automemberexclusiveregex:
+ regexExclusive.length !== 0
+ ? regexExclusive.map(regex => regex.label)
+ : [],
+ automemberinclusiveregex:
+ regexInclusive.length !== 0
+ ? regexInclusive.map(regex => regex.label)
+ : [],
+ needsadd: true
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification("error", `Regex
"${regexName}" already exists`);
+ }
+ } else if (action == "set") {
+ if (regexExists) {
+ this.setState({
+ regexRows: regexRows.filter(row => row.cn[0] !== regexName)
+ });
+
+ this.setState(prevState => ({
+ regexRows: [
+ ...prevState.regexRows,
+ {
+ cn: [regexName],
+ automembertargetgroup: [regexTargetGroup],
+ automemberexclusiveregex:
+ regexExclusive.length !== 0
+ ? regexExclusive.map(regex => regex.label)
+ : [],
+ automemberinclusiveregex:
+ regexInclusive.length !== 0
+ ? regexInclusive.map(regex => regex.label)
+ : [],
+ needsupdate: true
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Regex "${regexName}" does not exist -
"${action}" is impossible`
+ );
+ }
+ }
+ this.closeRegexModal();
+ }
+ }
+
+ deleteRegex(rowData) {
+ const { regexRows } = this.state;
+ const regexName = rowData.cn[0];
+
+ if (regexRows.some(row => row.cn[0] === regexName)) {
+ this.setState({
+ regexRows: regexRows.filter(row => row.cn[0] !== regexName)
+ });
+ this.setState(prevState => ({
+ regexesToDelete: [...prevState.regexesToDelete, regexName]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Regex "${regexName}" does not exist - impossible to delete`
+ );
+ }
+ }
+
+ addRegex() {
+ this.cmdRegexOperation("add");
+ }
+
+ editRegex() {
+ this.cmdRegexOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ regexRows,
+ definitionEntryModalShow,
+ definitionName,
+ newDefinitionEntry,
+ regexEntryModalShow,
+ newRegexEntry,
+ attributes,
+ groupingAttrEntry,
+ groupingAttrMember,
+ regexName,
+ regexExclusive,
+ regexInclusive,
+ regexTargetGroup
+ } = this.state;
+
+ const modalDefinitionFields = {
+ defaultGroup: {
+ name: "Default Group",
+ value: this.state.defaultGroup,
+ help: `Sets default or fallback group to add the entry to as a member
attribute in group entry (autoMemberDefaultGroup)`
+ },
+ scope: {
+ name: "Scope",
+ value: this.state.scope,
+ help: "Sets the subtree DN to search for entries
(autoMemberScope)"
+ },
+ filter: {
+ name: "Filter",
+ value: this.state.filter,
+ help:
+ "Sets a standard LDAP search filter to use to search for
matching entries (autoMemberFilter)"
+ }
+ };
+
return (
<div>
+ <Modal show={definitionEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newDefinitionEntry ? "Add" : "Edit"}
Auto Membership Plugin
+ Definition Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="definitionName"
controlId="definitionName">
+ <Col sm={3}>
+ <ControlLabel>Definition
Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={definitionName}
+ onChange={this.handleFieldChange}
+ disabled={!newDefinitionEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalDefinitionFields).map(
+ ([id, content]) => (
+ <FormGroup key={id}
controlId={id}>
+ <Col sm={3}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+ <FormGroup
+ key="groupingAttrEntry"
+ controlId="groupingAttrEntry"
+
+
<Col sm={3}>
+ <ControlLabel
+ title={`Specifies the name of the
member attribute in the group entry and the attribute in the object entry that supplies
the member attribute value, in the format group_member_attr:entry_attr
(autoMemberGroupingAttr)`}
+
+
Grouping Attributes
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ groupingAttrMember: value
+ });
+ }}
+ selected={groupingAttrMember}
+ options={attributes}
+ newSelectionPrefix="Set an
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ <Col sm={1}>:</Col>
+ <Col sm={4}>
+ <FormControl
+ required
+ type="text"
+ value={groupingAttrEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <AutoMembershipRegexTable
+ rows={regexRows}
+ editConfig={this.showEditRegexModal}
+ deleteConfig={this.deleteRegex}
+ />
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddRegexModal}
+
+
Add Regex
+ </Button>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={
+ newDefinitionEntry ? this.addDefinition :
this.editDefinition
+ }
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={regexEntryModalShow} onHide={this.closeRegexModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeRegexModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>Manage Auto Membership Plugin Regex
Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="regexName"
controlId="regexName">
+ <Col sm={3}>
+ <ControlLabel>Regex
Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={regexName}
+ onChange={this.handleFieldChange}
+ disabled={!newRegexEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="regexExclusive"
controlId="regexExclusive">
+ <Col sm={3}>
+ <ControlLabel
+ title={`Sets a single regular
expression to use to identify entries to exclude (autoMemberExclusiveRegex)`}
+
+
Exclusive Regex
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ regexExclusive: value
+ });
+ }}
+ selected={regexExclusive}
+ options={[]}
+ newSelectionPrefix="Set an
exclusive regex: "
+ placeholder="Type a
regex..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="regexInclusive"
controlId="regexInclusive">
+ <Col sm={3}>
+ <ControlLabel
+ title={`Sets a single regular
expression to use to identify entries to exclude (autoMemberExclusiveRegex)`}
+
+
Inclusive Regex
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ regexInclusive: value
+ });
+ }}
+ selected={regexInclusive}
+ options={[]}
+ newSelectionPrefix="Set an
inclusive regex: "
+ placeholder="Type a
regex..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="regexTargetGroup"
+ controlId="regexTargetGroup"
+
+
<Col sm={3}>
+ <ControlLabel
+ title={`Sets which group to add the
entry to as a member, if it meets the regular expression conditions
(autoMemberTargetGroup)`}
+
+
Target Group
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={regexTargetGroup}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeRegexModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newRegexEntry ? this.addRegex : this.editRegex}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +1010,24 @@ class AutoMembership extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <AutoMembershipDefinitionTable
+ rows={this.state.definitionRows}
+ editConfig={this.showEditDefinitionModal}
+ deleteConfig={this.deleteDefinition}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddDefinitionModal}
+
+ Add Definition
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx
b/src/cockpit/389-console/src/lib/plugins/dna.jsx
index caf62bb..185ca1f 100644
--- a/src/cockpit/389-console/src/lib/plugins/dna.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx
@@ -1,13 +1,1113 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { DNATable, DNASharedTable } from "./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class DNA extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ sharedConfigRows: [],
+ attributes: [],
+
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: "",
+
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: "",
+
+ newEntry: false,
+ newSharedEntry: false,
+ configEntryModalShow: false,
+ sharedConfigListModalShow: false,
+ sharedConfigEntryModalShow: false,
+ showConfirmSharedSave: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.loadSharedConfigs = this.loadSharedConfigs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+
+ this.openSharedListModal = this.openSharedListModal.bind(this);
+ this.closeSharedListModal = this.closeSharedListModal.bind(this);
+
+ this.openSharedModal = this.openSharedModal.bind(this);
+ this.closeSharedModal = this.closeSharedModal.bind(this);
+ this.showEditSharedConfigModal = this.showEditSharedConfigModal.bind(this);
+ this.showAddSharedConfigModal = this.showAddSharedConfigModal.bind(this);
+ this.cmdSharedOperation = this.cmdSharedOperation.bind(this);
+ this.deleteSharedConfig = this.deleteSharedConfig.bind(this);
+ this.addSharedConfig = this.addSharedConfig.bind(this);
+ this.editSharedConfig = this.editSharedConfig.bind(this);
+
+ this.showConfirmSharedSave = this.showConfirmSharedSave.bind(this);
+ this.closeConfirmSharedSave = this.closeConfirmSharedSave.bind(this);
+ }
+
+ showConfirmSharedSave() {
+ if (this.state.sharedConfigEntry != "") {
+ this.setState({
+ showConfirmSharedSave: true
+ });
+ } else {
+ this.props.addNotification(
+ "warning",
+ "Shared Config Entry attribute is required for the 'Manage'
operation"
+ );
+ }
+ }
+
+ closeConfirmSharedSave() {
+ this.setState({
+ showConfirmSharedSave: false
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "list",
+ "configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get DNA Plugin configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadSharedConfigs(basedn) {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "list",
+ "shared-configs",
+ basedn
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadSharedConfigs", "Get DNA Plugin shared configs",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ sharedConfigRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadSharedConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: ""
+ });
+ } else {
+ let dnaTypeList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the DNA Plugin config entry",
cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ?
"" : configEntry["cn"][0],
+ prefix:
+ configEntry["dnaprefix"] === undefined
+ ? ""
+ : configEntry["dnaprefix"][0],
+ nextValue:
+ configEntry["dnanextvalue"] === undefined
+ ? ""
+ : configEntry["dnanextvalue"][0],
+ maxValue:
+ configEntry["dnamaxvalue"] === undefined
+ ? ""
+ : configEntry["dnamaxvalue"][0],
+ interval:
+ configEntry["dnainterval"] === undefined
+ ? ""
+ : configEntry["dnainterval"][0],
+ magicRegen:
+ configEntry["dnamagicregen"] === undefined
+ ? ""
+ : configEntry["dnamagicregen"][0],
+ filter:
+ configEntry["dnafilter"] === undefined
+ ? ""
+ : configEntry["dnafilter"][0],
+ scope:
+ configEntry["dnascope"] === undefined ?
"" : configEntry["dnascope"][0],
+ remoteBindDN:
+ configEntry["dnaremotebindDN"] === undefined
+ ? ""
+ : configEntry["dnaremotebindDN"][0],
+ remoteBindCred:
+ configEntry["dnaremotebindcred"] === undefined
+ ? ""
+ : configEntry["dnaremotebindcred"][0],
+ sharedConfigEntry:
+ configEntry["dnasharedcfgdn"] === undefined
+ ? ""
+ : configEntry["dnasharedcfgdn"][0],
+ threshold:
+ configEntry["dnathreshold"] === undefined
+ ? ""
+ : configEntry["dnathreshold"][0],
+ nextRange:
+ configEntry["dnanextrange"] === undefined
+ ? ""
+ : configEntry["dnanextrange"][0],
+ rangeRequesTimeout:
+ configEntry["dnarangerequesttimeout"] ===
undefined
+ ? ""
+ : configEntry["dnarangerequesttimeout"][0]
+ });
+ if (configEntry["dnatype"] === undefined) {
+ this.setState({ type: [] });
+ } else {
+ for (let value of configEntry["dnatype"]) {
+ dnaTypeList = [...dnaTypeList, { id: value, label: value
}];
+ }
+ this.setState({ type: dnaTypeList });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ showEditSharedConfigModal(rowData) {
+ this.openSharedModal(rowData.dnahostname[0], rowData.dnaportnum[0]);
+ }
+
+ showAddSharedConfigModal(rowData) {
+ this.openSharedModal();
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ closeSharedModal() {
+ this.setState({ sharedConfigEntryModalShow: false });
+ }
+
+ cmdOperation(action, muteError) {
+ const {
+ configName,
+ type,
+ prefix,
+ nextValue,
+ maxValue,
+ interval,
+ magicRegen,
+ filter,
+ scope,
+ remoteBindDN,
+ remoteBindCred,
+ sharedConfigEntry,
+ threshold,
+ nextRange,
+ rangeRequesTimeout
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ action,
+ "--prefix",
+ prefix || action == "add" ? prefix : "delete",
+ "--next-value",
+ nextValue || action == "add" ? nextValue : "delete",
+ "--max-value",
+ maxValue || action == "add" ? maxValue : "delete",
+ "--interval",
+ interval || action == "add" ? interval : "delete",
+ "--magic-regen",
+ magicRegen || action == "add" ? magicRegen : "delete",
+ "--filter",
+ filter || action == "add" ? filter : "delete",
+ "--scope",
+ scope || action == "add" ? scope : "delete",
+ "--remote-bind-dn",
+ remoteBindDN || action == "add" ? remoteBindDN :
"delete",
+ "--remote-bind-cred",
+ remoteBindCred || action == "add" ? remoteBindCred :
"delete",
+ "--shared-config-entry",
+ sharedConfigEntry || action == "add" ? sharedConfigEntry :
"delete",
+ "--threshold",
+ threshold || action == "add" ? threshold : "delete",
+ "--next-range",
+ nextRange || action == "add" ? nextRange : "delete",
+ "--range-request-timeout",
+ rangeRequesTimeout || action == "add" ? rangeRequesTimeout :
"delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ if (!(action == "add" && type.length == 0)) {
+ cmd = [...cmd, "--type"];
+ if (type.length != 0) {
+ for (let value of type) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd("DNAOperation", `Do the ${action} operation on the DNA Plugin`,
cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("DNAOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (muteError !== true) {
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ }
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the DNA Plugin config entry",
cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig(muteError) {
+ this.cmdOperation("add", muteError);
+ }
+
+ editConfig(muteError) {
+ this.cmdOperation("set", muteError);
+ }
+
+ openSharedListModal(sharedConfigEntry) {
+ // Save the config entry that is being edited now
+ if (this.state.newEntry) {
+ this.addConfig(true);
+ } else {
+ this.editConfig(true);
+ }
+ // Get all of the sharedConfig entries located under the base DN
+ this.loadSharedConfigs(sharedConfigEntry);
+ this.setState({ sharedConfigListModalShow: true });
+ }
+
+ closeSharedListModal() {
+ this.setState({ sharedConfigListModalShow: false });
+ }
+
+ openSharedModal(hostname, port) {
+ if (hostname && port) {
+ // Get all the attributes and matching rules now
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ this.state.configName,
+ "shared-config-entry",
+ hostname,
+ port,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openSharedModal", "Fetch the DNA Plugin shared config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: false,
+ sharedHostname:
+ configEntry["dnahostname"] === undefined
+ ? ""
+ : configEntry["dnahostname"][0],
+ sharedPort:
+ configEntry["dnaportnum"] === undefined
+ ? ""
+ : configEntry["dnaportnum"][0],
+ sharedSecurePort:
+ configEntry["dnasecureportnum"] === undefined
+ ? ""
+ : configEntry["dnasecureportnum"][0],
+ sharedRemainingValues:
+ configEntry["dnaremainingvalues"] === undefined
+ ? ""
+ : configEntry["dnaremainingvalues"][0],
+ sharedRemoteBindMethod:
+ configEntry["dnaremotebindmethod"] === undefined
+ ? ""
+ : configEntry["dnaremotebindmethod"][0],
+ sharedRemoteConnProtocol:
+ configEntry["dnaremoteconnprotocol"] === undefined
+ ? ""
+ : configEntry["dnaremoteconnprotocol"][0]
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: true,
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ } else {
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: true,
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: ""
+ });
+ }
+ }
+
+ cmdSharedOperation(action) {
+ const {
+ configName,
+ sharedHostname,
+ sharedPort,
+ sharedSecurePort,
+ sharedRemainingValues,
+ sharedRemoteBindMethod,
+ sharedRemoteConnProtocol
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ "shared-config-entry",
+ sharedHostname,
+ sharedPort,
+ action,
+ "--secure-port",
+ sharedSecurePort || action == "add" ? sharedSecurePort :
"delete",
+ "--remote-bind-method",
+ sharedRemoteBindMethod || action == "add" ? sharedRemoteBindMethod
: "delete",
+ "--remote-conn-protocol",
+ sharedRemoteConnProtocol || action == "add" ?
sharedRemoteConnProtocol : "delete",
+ "--remaining-values",
+ sharedRemainingValues || action == "add" ? sharedRemainingValues :
"delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "DNASharedOperation",
+ `Do the ${action} operation on the DNA Plugin Shared Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("DNASharedOperation", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${sharedHostname}:${sharedPort}" entry`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.closeSharedModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.closeSharedModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteSharedConfig(rowData) {
+ let sharedHostname = rowData.dnahostname[0];
+ let sharedPort = rowData.dnaportnum[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "dna",
+ "config",
+ this.state.configName,
+ "shared-config-entry",
+ sharedHostname,
+ sharedPort,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteSharedConfig", "Delete the DNA Plugin Shared config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteSharedConfig", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `Shared config entry ${sharedHostname} and ${sharedPort} was
successfully deleted`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the shared config entry removal operation -
${errMsg.desc}`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addSharedConfig() {
+ this.cmdSharedOperation("add");
+ }
+
+ editSharedConfig() {
+ this.cmdSharedOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ sharedConfigRows,
+ configEntryModalShow,
+ configName,
+ newEntry,
+ type,
+ sharedConfigEntry,
+ sharedConfigListModalShow,
+ sharedConfigEntryModalShow,
+ sharedHostname,
+ sharedPort,
+ newSharedEntry,
+ attributes
+ } = this.state;
+
+ const modalConfigFields = {
+ prefix: {
+ name: "Prefix",
+ value: this.state.prefix,
+ help:
+ "Defines a prefix that can be prepended to the generated number
values for the attribute (dnaPrefix)"
+ },
+ nextValue: {
+ name: "Next Value",
+ value: this.state.nextValue,
+ help: "Gives the next available number which can be assigned
(dnaNextValue)"
+ },
+ maxValue: {
+ name: "Max Value",
+ value: this.state.maxValue,
+ help: "Sets the maximum value that can be assigned for the range
(dnaMaxValue)"
+ },
+ interval: {
+ name: "Interval",
+ value: this.state.interval,
+ help:
+ "Sets an interval to use to increment through numbers in a range
(dnaInterval)"
+ },
+ magicRegen: {
+ name: "Magic Regen",
+ value: this.state.magicRegen,
+ help:
+ "Sets a user-defined value that instructs the plug-in to assign
a new value for the entry (dnaMagicRegen)"
+ },
+ filter: {
+ name: "Filter",
+ value: this.state.filter,
+ help:
+ "Sets an LDAP filter to use to search for and identify the
entries to which to apply the distributed numeric assignment range (dnaFilter)"
+ },
+ scope: {
+ name: "Scope",
+ value: this.state.scope,
+ help:
+ "Sets the base DN to search for entries to which to apply the
distributed numeric assignment (dnaScope)"
+ },
+ remoteBindDN: {
+ name: "Remote Bind DN",
+ value: this.state.remoteBindDN,
+ help: "Specifies the Replication Manager DN (dnaRemoteBindDN)"
+ },
+ remoteBindCred: {
+ name: "Remote Bind Credentials",
+ value: this.state.remoteBindCred,
+ help: "Specifies the Replication Manager's password
(dnaRemoteBindCred)"
+ },
+ threshold: {
+ name: "Threshold",
+ value: this.state.threshold,
+ help:
+ "Sets a threshold of remaining available numbers in the range.
When the server hits the threshold, it sends a request for a new range
(dnaThreshold)"
+ },
+ nextRange: {
+ name: "Next Range",
+ value: this.state.nextRange,
+ help:
+ "Defines the next range to use when the current range is
exhausted (dnaNextRange)"
+ },
+ rangeRequesTimeout: {
+ name: "Range Request Timeout",
+ value: this.state.rangeRequesTimeout,
+ help:
+ "Sets a timeout period, in seconds, for range requests so that
the server does not stall waiting on a new range from one server and can request a range
from a new server (dnaRangeRequestTimeout)"
+ }
+ };
+
+ const modalSharedConfigFields = {
+ sharedSecurePort: {
+ name: "Secure Port",
+ value: this.state.sharedSecurePort,
+ help:
+ "Gives the secure (TLS) port number to use to connect to the
host identified in dnaHostname (dnaSecurePortNum)"
+ },
+ sharedRemainingValues: {
+ name: "Remaining Values",
+ value: this.state.sharedRemainingValues,
+ help: "Specifies the remote bind method (dnaRemoteBindMethod)"
+ },
+ sharedRemoteBindMethod: {
+ name: "Remote Bind Method",
+ value: this.state.sharedRemoteBindMethod,
+ help: "Specifies the remote connection protocol
(dnaRemoteConnProtocol)"
+ },
+ sharedRemoteConnProtocol: {
+ name: "Remote Connection Protocol",
+ value: this.state.sharedRemoteConnProtocol,
+ help:
+ "Contains the number of values that are remaining and available
to a server to assign to entries (dnaRemainingValues)"
+ }
+ };
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} DNA
Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="configName"
controlId="configName">
+ <Col sm={4}>
+ <ControlLabel>Config
Name</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="sharedConfigEntry"
+ controlId="sharedConfigEntry"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Defines a shared identity
that the servers can use to transfer ranges to one another (dnaSharedCfgDN)"
+
+
Shared Config Entry
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ type="text"
+ value={sharedConfigEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.showConfirmSharedSave}
+
+
Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ <FormGroup key="type"
controlId="type">
+ <Col sm={4}>
+ <ControlLabel title="Sets which
attributes have unique numbers being generated for them (dnaType)">
+ Type
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ type: value
+ });
+ }}
+ selected={type}
+ options={attributes}
+ newSelectionPrefix="Add a
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalConfigFields).map(([id,
content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={4}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={sharedConfigListModalShow}
onHide={this.closeSharedListModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeSharedListModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>List DNA Plugin Shared Config
Entries</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={9}>
+ <ControlLabel>DNA Config: </ControlLabel>
{configName}
+ </Col>
+ <Col sm={9}>
+ <ControlLabel>Shared Config Base
DN:</ControlLabel>{" "}
+ {sharedConfigEntry}
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <DNASharedTable
+ rows={sharedConfigRows}
+ editConfig={this.showEditSharedConfigModal}
+ deleteConfig={this.deleteSharedConfig}
+ />
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeSharedListModal}
+
+ Close
+ </Button>
+ <Button bsStyle="primary"
onClick={this.showAddSharedConfigModal}>
+ Add Config
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={sharedConfigEntryModalShow}
onHide={this.closeSharedModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeSharedModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>Manage DNA Plugin Shared Config
Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="sharedHostname"
controlId="sharedHostname">
+ <Col sm={4}>
+ <ControlLabel>Config
Hostname</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={sharedHostname}
+ onChange={this.handleFieldChange}
+ disabled={!newSharedEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="sharedPort"
controlId="sharedPort">
+ <Col sm={4}>
+ <ControlLabel>Config
Port</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={sharedPort}
+ onChange={this.handleFieldChange}
+ disabled={!newSharedEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalSharedConfigFields).map(
+ ([id, content]) => (
+ <FormGroup key={id}
controlId={id}>
+ <Col sm={4}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeSharedModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={
+ newSharedEntry ? this.addSharedConfig :
this.editSharedConfig
+ }
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,6 +1118,31 @@ class DNA extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
+ >
+ <Row>
+ <Col sm={9}>
+ <DNATable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
+ <ConfirmPopup
+ showModal={this.state.showConfirmSharedSave}
+ closeHandler={this.closeConfirmSharedSave}
+ actionFunc={this.openSharedListModal}
+ actionParam={this.state.sharedConfigEntry}
+ msg="The current entry state will be saved to the directory.
Also, make sure you've set the shared config entry area."
+ msgContent="Are you ready to proceed?"
/>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
index 5216b15..8a7743c 100644
--- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
@@ -5,9 +5,425 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class LinkedAttributes extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ attributes: [],
+
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: "",
+
+ newEntry: false,
+ showConfigModal: false,
+ showConfirmDeleteConfig: false
+ };
+
+ this.getAttributes = this.getAttributes.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ // Get all the attributes and matching rules now
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "linked-attr",
+ "list"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Linked Attributes Plugin
configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: ""
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Linked Attributes Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ?
"" : configEntry["cn"][0],
+ linkType:
+ configEntry["linktype"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["linktype"][0],
+ label: configEntry["linktype"][0]
+ }
+ ],
+ managedType:
+ configEntry["managedtype"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["managedtype"][0],
+ label: configEntry["managedtype"][0]
+ }
+ ],
+ linkScope:
+ configEntry["linkscope"] === undefined
+ ? ""
+ : configEntry["linkscope"][0]
+ });
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const { configName, linkType, managedType, linkScope } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ configName,
+ action,
+ "--link-scope",
+ linkScope || action == "add" ? linkScope : "delete"
+ ];
+
+ cmd = [...cmd, "--link-type"];
+ if (linkType.length != 0) {
+ cmd = [...cmd, linkType[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--managed-type"];
+ if (managedType.length != 0) {
+ cmd = [...cmd, managedType[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "linkedAttributesOperation",
+ `Do the ${action} operation on the Linked Attributes Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("linkedAttributesOperation",
"Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Linked Attributes Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} Linked
Attributes Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
controlId="configName">
+ <Col sm={3}>
+ <ControlLabel title="The Linked
Attributes configuration name">
+ Config Name
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="linkType">
+ <Col sm={3}>
+ <ControlLabel title="Sets the
attribute that is managed manually by administrators (linkType)">
+ Link Type
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ linkType: value
+ });
+ }}
+ selected={linkType}
+ options={attributes}
+ newSelectionPrefix="Add a
managed attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
controlId="managedType">
+ <Col sm={3}>
+ <ControlLabel title="Sets the
attribute that is created dynamically by the plugin (managedType)">
+ Managed Type
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ managedType: value
+ });
+ }}
+ selected={managedType}
+ options={attributes}
+ newSelectionPrefix="Add a
dynamic attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
controlId="linkScope">
+ <Col sm={3}>
+ <ControlLabel title="Sets the
base DN that restricts the plugin to a specific part of the directory tree
(linkScope)">
+ Link Scope
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={linkScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +434,24 @@ class LinkedAttributes extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <LinkedAttributesTable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
index 11771b7..5433535 100644
--- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
@@ -1,24 +1,901 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { ManagedEntriesTable } from "./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class ManagedEntries extends React.Component {
+ componentWillMount() {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configArea: "",
+ configRows: [],
+ attributes: [],
+
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: "",
+
+ templateDN: "",
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: [],
+
+ newConfigEntry: false,
+ configEntryModalShow: false,
+ newTemplateEntry: false,
+ templateEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.updateFields = this.updateFields.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+
+ this.openTemplateModal = this.openTemplateModal.bind(this);
+ this.closeTemplateModal = this.closeTemplateModal.bind(this);
+ this.cmdTemplateOperation = this.cmdTemplateOperation.bind(this);
+ this.deleteTemplate = this.deleteTemplate.bind(this);
+ this.addTemplate = this.addTemplate.bind(this);
+ this.editTemplate = this.editTemplate.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "list",
+ "configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Managed Entries Plugin configs",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: true,
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: ""
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Managed Entries Plugin config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: false,
+ configName: configEntry["cn"] === undefined ?
"" : configEntry["cn"][0],
+ originScope:
+ configEntry["originscope"] === undefined
+ ? ""
+ : configEntry["originscope"][0],
+ originFilter:
+ configEntry["originfilter"] === undefined
+ ? ""
+ : configEntry["originfilter"][0],
+ managedBase:
+ configEntry["managedbase"] === undefined
+ ? ""
+ : configEntry["managedbase"][0],
+ managedTemplate:
+ configEntry["managedtemplate"] === undefined
+ ? ""
+ : configEntry["managedtemplate"][0]
+ });
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: true,
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Managed Entries Plugin config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ cmdOperation(action) {
+ const { configName, originScope, originFilter, managedBase, managedTemplate } =
this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ configName,
+ action,
+ "--scope",
+ originScope || action == "add" ? originScope : "delete",
+ "--filter",
+ originFilter || action == "add" ? originFilter :
"delete",
+ "--managed-base",
+ managedBase || action == "add" ? managedBase : "delete",
+ "--managed-template",
+ managedTemplate || action == "add" ? managedTemplate :
"delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("cmdOperation", `Do the ${action} operation on the Managed
Entries Plugin`, cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("cmdOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Managed
Entries");
+
+ this.setState({
+ configArea:
+ pluginRow["nsslapd-pluginConfigArea"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginConfigArea"][0]
+ });
+ this.loadConfigs();
+ }
+ }
+
+ openTemplateModal() {
+ this.getAttributes();
+ if (!this.state.managedTemplate) {
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: true,
+ templateDN: "",
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: []
+ });
+ } else {
+ let templateMappedAttrObjectList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ this.state.managedTemplate,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openTemplateModal", "Fetch the Managed Entries Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: false,
+ templateDN: this.state.managedTemplate,
+ templateRDNAttr:
+ configEntry["meprdnattr"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["meprdnattr"][0],
+ label: configEntry["meprdnattr"][0]
+ }
+ ],
+ templateStaticAttr:
+ configEntry["mepstaticattr"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["mepstaticattr"][0],
+ label: configEntry["mepstaticattr"][0]
+ }
+ ]
+ });
+ if (configEntry["mepmappedattr"] === undefined) {
+ this.setState({ templateMappedAttr: [] });
+ } else {
+ for (let value of configEntry["mepmappedattr"]) {
+ templateMappedAttrObjectList = [
+ ...templateMappedAttrObjectList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ templateMappedAttr: templateMappedAttrObjectList
+ });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: true,
+ templateDN: this.state.managedTemplate,
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeTemplateModal() {
+ this.setState({ templateEntryModalShow: false });
+ }
+
+ cmdTemplateOperation(action) {
+ const { templateDN, templateRDNAttr, templateStaticAttr, templateMappedAttr } =
this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ templateDN,
+ action
+ ];
+
+ cmd = [...cmd, "--rdn-attr"];
+ if (templateRDNAttr.length != 0) {
+ cmd = [...cmd, templateRDNAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--static-attr"];
+ if (templateStaticAttr.length != 0) {
+ cmd = [...cmd, templateStaticAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--mapped-attr"];
+ if (templateMappedAttr.length != 0) {
+ for (let value of templateMappedAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "managedEntriesOperation",
+ `Do the ${action} operation on the Managed Entries Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("managedEntriesOperation", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${templateDN} was successfully ${action}ed`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteTemplate() {
+ const { templateDN } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ templateDN,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Managed Entries Plugin config
entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${templateDN} was successfully deleted`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addTemplate() {
+ this.cmdTemplateOperation("add");
+ }
+
+ editTemplate() {
+ this.cmdTemplateOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ configArea,
+ configRows,
+ attributes,
+ configName,
+ originScope,
+ originFilter,
+ managedBase,
+ managedTemplate,
+ templateDN,
+ templateRDNAttr,
+ templateStaticAttr,
+ templateMappedAttr,
+ newConfigEntry,
+ configEntryModalShow,
+ newTemplateEntry,
+ templateEntryModalShow
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "managed-entries",
+ "set",
+ "--config-area",
+ configArea || "delete"
+ ];
+
+ const modalConfigFields = {
+ originScope: {
+ name: "Scope",
+ value: originScope,
+ help: `Sets the search base DN to use to see which entries the plug-in
monitors (originScope)`
+ },
+ originFilter: {
+ name: "Filter",
+ value: originFilter,
+ help: `Sets the search filter to use to search for and identify the
entries within the subtree which require a managed entry (originFilter)`
+ },
+ managedBase: {
+ name: "Managed Base",
+ value: managedBase,
+ help: "Sets the subtree under which to create the managed entries
(managedBase)"
+ }
+ };
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newConfigEntry ? "Add" : "Edit"}
Managed Entries Plugin Config
+ Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="configName"
controlId="configName">
+ <Col sm={3}>
+ <ControlLabel>Config
Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newConfigEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalConfigFields).map(([id,
content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={3}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+ <FormGroup
+ key="managedTemplate"
+ controlId="managedTemplate"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies the template entry
to use to create the managed entry (managedTemplate)"
+
+
Managed Template
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={managedTemplate}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openTemplateModal}
+
+
Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newConfigEntry ? this.addConfig :
this.editConfig}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={templateEntryModalShow}
onHide={this.closeTemplateModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeTemplateModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>Handle Managed Entries Plugin Template
Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
controlId="templateDN">
+ <Col sm={4}>
+ <ControlLabel title="DN of the
template entry">
+ Template DN
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={templateDN}
+ onChange={this.handleFieldChange}
+ disabled={!newTemplateEntry}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="templateRDNAttr"
+ controlId="templateRDNAttr"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Sets which attribute to use
as the naming attribute in the automatically-generated entry (mepRDNAttr)"
+
+
RDN Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ templateRDNAttr: value
+ });
+ }}
+ selected={templateRDNAttr}
+ options={attributes}
+ newSelectionPrefix="Add a RDN
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="templateStaticAttr"
+ controlId="templateStaticAttr"
+ disabled={false}
+
+
<Col sm={4}>
+ <ControlLabel title="Sets an
attribute with a defined value that must be added to the automatically-generated entry
(mepStaticAttr)">
+ Static Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ templateStaticAttr: value
+ });
+ }}
+ selected={templateStaticAttr}
+ options={attributes}
+ newSelectionPrefix="Add a static
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="templateMappedAttr"
+ controlId="templateMappedAttr"
+ disabled={false}
+
+
<Col sm={4}>
+ <ControlLabel title="Sets
attributes in the Managed Entries template entry which must exist in the generated entry
(mepMappedAttr)">
+ Mapped Attributes
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ templateMappedAttr: value
+ });
+ }}
+ selected={templateMappedAttr}
+ options={attributes}
+ newSelectionPrefix="Add a mapped
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeTemplateModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteTemplate}
+ disabled={newTemplateEntry}
+
+ Delete
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.editTemplate}
+ disabled={newTemplateEntry}
+
+ Save
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.addTemplate}
+ disabled={!newTemplateEntry}
+
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
cn="Managed Entries"
pluginName="Managed Entries"
cmdName="managed-entries"
+ specificPluginCMD={specificPluginCMD}
savePluginHandler={this.props.savePluginHandler}
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <FormGroup key="configArea"
controlId="configArea">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="DN of the shared config entry
(nsslapd-pluginConfigArea)"
+ >
+ Shared Config Area
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={configArea}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <Col sm={9}>
+ <ManagedEntriesTable
+ rows={configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index d838054..8117da7 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -116,9 +116,10 @@ class MemberOf extends React.Component {
});
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Fixup task for ${this.state.fixupDN} has failed ${err}`
+ `Fixup task for ${this.state.fixupDN} has failed
${errMsg.desc}`
);
this.props.toggleLoadingHandler();
this.setState({
@@ -171,8 +172,13 @@ class MemberOf extends React.Component {
configDN: this.state.memberOfConfigEntry,
configAutoAddOC:
configEntry["memberofautoaddoc"] === undefined
- ? ""
- : configEntry["memberofautoaddoc"][0],
+ ? []
+ : [
+ {
+ id:
configEntry["memberofautoaddoc"][0],
+ label:
configEntry["memberofautoaddoc"][0]
+ }
+ ],
configAllBackends: !(
configEntry["memberofallbackends"] ===
undefined ||
configEntry["memberofallbackends"][0] ==
"off"
@@ -281,6 +287,15 @@ class MemberOf extends React.Component {
configSkipNested ? "on" : "off"
];
+ cmd = [...cmd, "--autoaddoc"];
+ if (configAutoAddOC.length != 0) {
+ cmd = [...cmd, configAutoAddOC[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
// Delete attributes if the user set an empty value to the field
cmd = [...cmd, "--attr"];
if (configAttr.length != 0) {
@@ -313,9 +328,10 @@ class MemberOf extends React.Component {
this.props.toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Error during the config entry ${action} operation - ${err}`
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
);
this.props.pluginListHandler();
this.closeModal();
@@ -354,9 +370,10 @@ class MemberOf extends React.Component {
this.props.toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Error during the config entry removal operation - ${err}`
+ `Error during the config entry removal operation -
${errMsg.desc}`
);
this.props.pluginListHandler();
this.closeModal();
@@ -442,6 +459,37 @@ class MemberOf extends React.Component {
}
}
+ getObjectClasses() {
+ const oc_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "objectclasses",
+ "list"
+ ];
+ log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);
+ cockpit
+ .spawn(oc_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const ocContent = JSON.parse(content);
+ let ocs = [];
+ for (let content of ocContent["items"]) {
+ ocs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ objectClasses: ocs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
objectClasses - ${errMsg.desc}`);
+ });
+ }
+
render() {
const {
memberOfAttr,
@@ -488,6 +536,13 @@ class MemberOf extends React.Component {
memberOfSkipNested ? "on" : "off"
];
+ specificPluginCMD = [...specificPluginCMD, "--autoaddoc"];
+ if (memberOfAutoAddOC.length != 0) {
+ specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].label];
+ } else {
+ specificPluginCMD = [...specificPluginCMD, "delete"];
+ }
+
// Delete attributes if the user set an empty value to the field
specificPluginCMD = [...specificPluginCMD, "--attr"];
if (memberOfAttr.length != 0) {
@@ -682,6 +737,7 @@ class MemberOf extends React.Component {
id="configAllBackends"
checked={configAllBackends}
onChange={this.handleCheckboxChange}
+ title="Specifies whether to
search the local suffix for user entries on all available suffixes
(memberOfAllBackends)"
All
Backends
</Checkbox>
@@ -707,6 +763,7 @@ class MemberOf extends React.Component {
id="configSkipNested"
checked={configSkipNested}
onChange={this.handleCheckboxChange}
+ title="Specifies wherher to skip
nested groups or not (memberOfSkipNested)"
Skip
Nested
</Checkbox>
@@ -944,7 +1001,10 @@ class MemberOf extends React.Component {
<Col sm={9}>
<Form horizontal>
<FormGroup controlId="memberOfAutoAddOC"
disabled={false}>
- <Col sm={3}>
+ <Col
+ sm={3}
+ title="If an entry does not have an object
class that allows the memberOf attribute then the memberOf plugin will automatically add
the object class listed in the memberOfAutoAddOC parameter"
+
<ControlLabel>Auto Add OC</ControlLabel>
</Col>
<Col sm={9}>
diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
index 5b6f76c..5dc1ab6 100644
--- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
@@ -1,14 +1,1129 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ Radio,
+ noop,
+ FormGroup,
+ FormControl,
+ Checkbox,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { PassthroughAuthURLsTable, PassthroughAuthConfigsTable } from
"./pluginTables.jsx";
+import PluginBasicPAMConfig from "./pluginBasicConfig.jsx";
import PropTypes from "prop-types";
-import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class PassthroughAuthentication extends React.Component {
+ componentWillMount() {
+ this.loadPAMConfigs();
+ this.loadURLs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ pamConfigRows: [],
+ urlRows: [],
+ attributes: [],
+
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: "",
+
+ oldURL: "",
+ urlConnType: "ldap",
+ urlAuthDS: "",
+ urlSubtree: "",
+ urlMaxConns: "3",
+ urlMaxOps: "5",
+ urlTimeout: "300",
+ urlLDVer: "3",
+ urlConnLifeTime: "300",
+ urlStartTLS: false,
+
+ newPAMConfigEntry: false,
+ newURLEntry: false,
+ pamConfigEntryModalShow: false,
+ urlEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+
+ this.loadPAMConfigs = this.loadPAMConfigs.bind(this);
+ this.loadURLs = this.loadURLs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openPAMModal = this.openPAMModal.bind(this);
+ this.closePAMModal = this.closePAMModal.bind(this);
+ this.showEditPAMConfigModal = this.showEditPAMConfigModal.bind(this);
+ this.showAddPAMConfigModal = this.showAddPAMConfigModal.bind(this);
+ this.cmdPAMOperation = this.cmdPAMOperation.bind(this);
+ this.deletePAMConfig = this.deletePAMConfig.bind(this);
+ this.addPAMConfig = this.addPAMConfig.bind(this);
+ this.editPAMConfig = this.editPAMConfig.bind(this);
+
+ this.openURLModal = this.openURLModal.bind(this);
+ this.closeURLModal = this.closeURLModal.bind(this);
+ this.showEditURLModal = this.showEditURLModal.bind(this);
+ this.showAddURLModal = this.showAddURLModal.bind(this);
+ this.cmdURLOperation = this.cmdURLOperation.bind(this);
+ this.deleteURL = this.deleteURL.bind(this);
+ this.addURL = this.addURL.bind(this);
+ this.editURL = this.editURL.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ loadPAMConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "list",
+ "pam-configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadPAMConfigs", "Get PAM Passthough Authentication
Plugin pamConfigs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ pamConfigRows: myObject.items.map(item =>
JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadPAMConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadURLs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "list",
+ "urls"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadURLs", "Get PAM Passthough Authentication Plugin
pamConfigs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ urlRows: myObject.items
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadURLs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditPAMConfigModal(rowData) {
+ this.openPAMModal(rowData.cn[0]);
+ }
+
+ showAddPAMConfigModal(rowData) {
+ this.openPAMModal();
+ }
+
+ openPAMModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: true,
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: ""
+ });
+ } else {
+ let pamExcludeSuffixList = [];
+ let pamIncludeSuffixList = [];
+ let pamIDAttrList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "openModal",
+ "Fetch the PAM Passthough Authentication Plugin pamConfig
entry",
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let pamConfigEntry = JSON.parse(content).attrs;
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: false,
+ pamConfigName:
+ pamConfigEntry["cn"] === undefined ? "" :
pamConfigEntry["cn"][0],
+ pamMissingSuffix:
+ pamConfigEntry["pammissingsuffix"] === undefined
+ ? ""
+ : pamConfigEntry["pammissingsuffix"][0],
+ pamFilter:
+ pamConfigEntry["pamfilter"] === undefined
+ ? ""
+ : pamConfigEntry["pamfilter"][0],
+ pamIDMapMethod:
+ pamConfigEntry["pamidmapmethod"] === undefined
+ ? ""
+ : pamConfigEntry["pamidmapmethod"][0],
+ pamFallback: !(
+ pamConfigEntry["pamfallback"] === undefined ||
+ pamConfigEntry["pamfallback"][0] ==
"FALSE"
+ ),
+ pamSecure: !(
+ pamConfigEntry["pamsecure"] === undefined ||
+ pamConfigEntry["pamsecure"][0] ==
"FALSE"
+ ),
+ pamService:
+ pamConfigEntry["pamservice"] === undefined
+ ? ""
+ : pamConfigEntry["pamservice"][0]
+ });
+ if (pamConfigEntry["pamexcludesuffix"] === undefined)
{
+ this.setState({ pamExcludeSuffix: [] });
+ } else {
+ for (let value of
pamConfigEntry["pamexcludesuffix"]) {
+ pamExcludeSuffixList = [
+ ...pamExcludeSuffixList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ pamExcludeSuffix: pamExcludeSuffixList
+ });
+ }
+
+ if (pamConfigEntry["pamincludesuffix"] === undefined)
{
+ this.setState({ pamIncludeSuffix: [] });
+ } else {
+ for (let value of
pamConfigEntry["pamincludesuffix"]) {
+ pamIncludeSuffixList = [
+ ...pamIncludeSuffixList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ pamIncludeSuffix: pamIncludeSuffixList
+ });
+ }
+
+ if (pamConfigEntry["pamidattr"] === undefined) {
+ this.setState({ pamIDAttr: [] });
+ } else {
+ for (let value of pamConfigEntry["pamidattr"]) {
+ pamIDAttrList = [...pamIDAttrList, { id: value, label:
value }];
+ }
+ this.setState({ pamIDAttr: pamIDAttrList });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: true,
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closePAMModal() {
+ this.setState({ pamConfigEntryModalShow: false });
+ }
+
+ deletePAMConfig(rowData) {
+ let pamConfigName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ pamConfigName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "deletePAMConfig",
+ "Delete the PAM Passthough Authentication Plugin pamConfig entry",
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deletePAMConfig", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `PAMConfig entry ${pamConfigName} was successfully deleted`
+ );
+ this.loadPAMConfigs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the pamConfig entry removal operation -
${errMsg.desc}`
+ );
+ this.loadPAMConfigs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addPAMConfig() {
+ this.cmdPAMOperation("add");
+ }
+
+ editPAMConfig() {
+ this.cmdPAMOperation("set");
+ }
+
+ cmdPAMOperation(action) {
+ // Save table here too
+ const {
+ pamConfigName,
+ pamExcludeSuffix,
+ pamIncludeSuffix,
+ pamMissingSuffix,
+ pamFilter,
+ pamIDAttr,
+ pamIDMapMethod,
+ pamFallback,
+ pamSecure,
+ pamService
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ pamConfigName,
+ action,
+ "--missing-suffix",
+ pamMissingSuffix || action == "add" ? pamMissingSuffix :
"delete",
+ "--filter",
+ pamFilter || action == "add" ? pamFilter : "delete",
+ "--id_map_method",
+ pamIDMapMethod || action == "add" ? pamIDMapMethod :
"delete",
+ "--fallback",
+ pamFallback ? "TRUE" : "FALSE",
+ "--secure",
+ pamSecure ? "TRUE" : "FALSE",
+ "--service",
+ pamService || action == "add" ? pamService : "delete"
+ ];
+
+ cmd = [...cmd, "--exclude-suffix"];
+ if (pamExcludeSuffix.length != 0) {
+ for (let value of pamExcludeSuffix) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ cmd = [...cmd, "--include-suffix"];
+ if (pamIncludeSuffix.length != 0) {
+ for (let value of pamIncludeSuffix) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ cmd = [...cmd, "--id-attr"];
+ if (pamIDAttr.length != 0) {
+ for (let value of pamIDAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "pamPassthroughAuthOperation",
+ `Do the ${action} operation on the PAM Passthough Authentication Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("pamPassthroughAuthOperation",
"Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${pamConfigName}" entry`
+ );
+ this.loadPAMConfigs();
+ this.closePAMModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the pamConfig entry ${action} operation -
${errMsg.desc}`
+ );
+ this.loadPAMConfigs();
+ this.closePAMModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditURLModal(rowData) {
+ this.openURLModal(rowData.url);
+ }
+
+ showAddURLModal(rowData) {
+ this.openURLModal();
+ }
+
+ openURLModal(url) {
+ if (!url) {
+ this.setState({
+ urlEntryModalShow: true,
+ newURLEntry: true,
+ oldURL: "",
+ urlConnType: "ldap",
+ urlAuthDS: "",
+ urlSubtree: "",
+ urlMaxConns: "3",
+ urlMaxOps: "5",
+ urlTimeout: "300",
+ urlLDVer: "3",
+ urlConnLifeTime: "300",
+ urlStartTLS: false
+ });
+ } else {
+ let link = url.split(" ")[0];
+ let params = url.split(" ")[1];
+ this.setState({
+ urlEntryModalShow: true,
+ oldURL: url,
+ newURLEntry: false,
+ urlConnType: link.split(":")[0],
+ urlAuthDS: link.split("/")[2],
+ urlSubtree: link.split("/")[3],
+ urlMaxConns: params.split(",")[0],
+ urlMaxOps: params.split(",")[1],
+ urlTimeout: params.split(",")[2],
+ urlLDVer: params.split(",")[3],
+ urlConnLifeTime: params.split(",")[4],
+ urlStartTLS: !(params.split(",")[5] == "0")
+ });
+ }
+ }
+
+ closeURLModal() {
+ this.setState({ urlEntryModalShow: false });
+ }
+
+ deleteURL(rowData) {
+ let url = rowData.url;
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "url",
+ "delete",
+ url
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteURL", "Delete the Passthough Authentication Plugin
URL entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteURL", "Result", content);
+ this.props.addNotification("success", `URL ${url} was
successfully deleted`);
+ this.loadURLs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the URL removal operation - ${errMsg.desc}`
+ );
+ this.loadURLs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addURL() {
+ this.cmdURLOperation("add");
+ }
+
+ editURL() {
+ this.cmdURLOperation("modify");
+ }
+
+ cmdURLOperation(action) {
+ const {
+ oldURL,
+ urlConnType,
+ urlAuthDS,
+ urlSubtree,
+ urlMaxConns,
+ urlMaxOps,
+ urlTimeout,
+ urlLDVer,
+ urlConnLifeTime,
+ urlStartTLS
+ } = this.state;
+
+ if (!urlAuthDS || !urlSubtree) {
+ this.props.addNotification(
+ "warning",
+ "Authentication Hostname and Subtree fields are required."
+ );
+ } else {
+ const constructedURL = `${urlConnType}://${urlAuthDS}/${urlSubtree}
${urlMaxConns},${urlMaxOps},${urlTimeout},${urlLDVer},${urlConnLifeTime},${
+ urlStartTLS ? "1" : "0"
+ }`;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "pass-through-auth",
+ "url",
+ action
+ ];
+ if (oldURL != "" && action == "modify") {
+ cmd = [...cmd, oldURL, constructedURL];
+ } else {
+ cmd = [...cmd, constructedURL];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "PassthroughAuthOperation",
+ `Do the ${action} operation on the Passthough Authentication Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("PassthroughAuthOperation",
"Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on
"${constructedURL}" entry`
+ );
+ this.loadURLs();
+ this.closeURLModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the URL ${action} operation - ${errMsg.desc}`
+ );
+ this.loadURLs();
+ this.closeURLModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ urlRows,
+ pamConfigRows,
+ attributes,
+ pamConfigName,
+ pamMissingSuffix,
+ pamExcludeSuffix,
+ pamIncludeSuffix,
+ pamFilter,
+ pamIDAttr,
+ pamIDMapMethod,
+ pamFallback,
+ pamSecure,
+ pamService,
+ urlConnType,
+ urlLDVer,
+ urlAuthDS,
+ urlSubtree,
+ urlMaxConns,
+ urlMaxOps,
+ urlTimeout,
+ urlConnLifeTime,
+ urlStartTLS,
+ newPAMConfigEntry,
+ newURLEntry,
+ pamConfigEntryModalShow,
+ urlEntryModalShow
+ } = this.state;
+
+ const modalPAMConfigFields = {
+ pamFilter: {
+ name: "Filter",
+ value: pamFilter,
+ help: `Sets an LDAP filter to use to identify specific entries within the
included suffixes for which to use PAM pass-through authentication (pamFilter)`
+ },
+ pamIDMapMethod: {
+ name: "Map Method",
+ value: pamIDMapMethod,
+ help: `Gives the method to use to map the LDAP bind DN to a PAM identity
(pamIDMapMethod)`
+ },
+ pamService: {
+ name: "Service",
+ value: pamService,
+ help: `Contains the service name to pass to PAM (pamService)`
+ }
+ };
+
+ const modalURLFields = {
+ urlAuthDS: {
+ name: "Authentication Hostname",
+ value: urlAuthDS,
+ help: `The authenticating directory host name. The port number of the
Directory Server can be given by adding a colon and then the port number. For example,
dirserver.example.com:389. If the port number is not specified, the PTA server attempts to
connect using either of the standard ports: Port 389 if ldap:// is specified in the URL.
Port 636 if ldaps:// is specified in the URL.`
+ },
+ urlSubtree: {
+ name: "Subtree",
+ value: urlSubtree,
+ help: `The pass-through subtree. The PTA Directory Server passes through
bind requests to the authenticating Directory Server from all clients whose DN is in this
subtree.`
+ },
+ urlMaxConns: {
+ name: "Maximum Number of Connections",
+ value: urlMaxConns,
+ help: `The maximum number of connections the PTA directory can
simultaneously open to the authenticating directory.`
+ },
+ urlMaxOps: {
+ name: "Maximum Number of Simultaneous Operations",
+ value: urlMaxOps,
+ help: `The maximum number of simultaneous operations (usually bind
requests) the PTA directory can send to the authenticating directory within a single
connection.`
+ },
+ urlTimeout: {
+ name: "Timeout",
+ value: urlTimeout,
+ help: `The time limit, in seconds, that the PTA directory waits for a
response from the authenticating Directory Server. If this timeout is exceeded, the server
returns an error to the client. The default is 300 seconds (five minutes). Specify zero
(0) to indicate no time limit should be enforced.`
+ },
+ urlConnLifeTime: {
+ name: "Connection Life Time",
+ value: urlConnLifeTime,
+ help: `The time limit, in seconds, within which a connection may be
used.`
+ }
+ };
return (
<div>
- <PluginBasicConfig
+ <Modal show={pamConfigEntryModalShow} onHide={this.closePAMModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closePAMModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newPAMConfigEntry ? "Add" : "Edit"}
PAM Passthough Authentication
+ Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="pamConfigName"
controlId="pamConfigName">
+ <Col sm={3}>
+ <ControlLabel>Config
Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={pamConfigName}
+ onChange={this.handleFieldChange}
+ disabled={!newPAMConfigEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamExcludeSuffix"
+ controlId="pamExcludeSuffix"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies a suffix to exclude
from PAM authentication (pamExcludeSuffix)"
+
+
Exclude Suffix
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ pamExcludeSuffix: values
+ });
+ }}
+ selected={pamExcludeSuffix}
+ options={[""]}
+ newSelectionPrefix="Add a
suffix: "
+ placeholder="Type a suffix
DN..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamIncludeSuffix"
+ controlId="pamIncludeSuffix"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets a suffix to include for
PAM authentication (pamIncludeSuffix)"
+
+
Include Suffix
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ pamIncludeSuffix: values
+ });
+ }}
+ selected={pamIncludeSuffix}
+ options={[""]}
+ newSelectionPrefix="Add a
suffix: "
+ placeholder="Type a suffix
DN..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamIDAttr"
+ controlId="pamIDAttr"
+ disabled={false}
+
+
<Col sm={3}>
+ <ControlLabel title="Contains the
attribute name which is used to hold the PAM user ID (pamIDAttr)">
+ ID Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ pamIDAttr: value
+ });
+ }}
+ selected={pamIDAttr}
+ options={attributes}
+ newSelectionPrefix="Add an
attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamMissingSuffix"
+ controlId="pamMissingSuffix"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies how to handle
missing include or exclude suffixes (pamMissingSuffix)"
+
+
Missing Suffix
+ </Col>
+ <Col sm={9}>
+ <div>
+ <Radio
+ id="pamMissingSuffix"
+ value="ERROR"
+ name="ERROR"
+ inline
+ checked={pamMissingSuffix ===
"ERROR"}
+
onChange={this.handleFieldChange}
+
+ ERROR
+ </Radio>
+ <Radio
+ id="pamMissingSuffix"
+ value="ALLOW"
+ name="ALLOW"
+ inline
+ checked={pamMissingSuffix ===
"ALLOW"}
+
onChange={this.handleFieldChange}
+
+ ALLOW
+ </Radio>
+ <Radio
+ id="pamMissingSuffix"
+ value="IGNORE"
+ name="IGNORE"
+ inline
+ checked={pamMissingSuffix ===
"IGNORE"}
+
onChange={this.handleFieldChange}
+
+
IGNORE
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ {Object.entries(modalPAMConfigFields).map(
+ ([id, content]) => (
+ <FormGroup key={id}
controlId={id}>
+ <Col sm={3}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+
+ <FormGroup key="pamCheckboxes"
controlId="pamCheckboxes">
+ <Col sm={4}>
+ <Checkbox
+ id="pamFallback"
+ checked={pamFallback}
+ onChange={this.handleCheckboxChange}
+ title={`Sets whether to fallback to
regular LDAP authentication if PAM authentication fails (pamFallback)`}
+
+
Fallback
+ </Checkbox>
+ </Col>
+ <Col sm={4}>
+ <Checkbox
+ id="pamSecure"
+ checked={pamSecure}
+ onChange={this.handleCheckboxChange}
+ title="Requires secure TLS
connection for PAM authentication (pamSecure)"
+
+
Secure
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closePAMModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newPAMConfigEntry ? this.addPAMConfig :
this.editPAMConfig}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={urlEntryModalShow} onHide={this.closeURLModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeURLModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ {newPAMConfigEntry ? "Add" : "Edit"}
+ Passthough Authentication Plugin URL
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="urlConnType"
+ controlId="urlConnType"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={5}
+ title="Defines whether TLS is used
for communication between the two Directory Servers."
+
+
Connection Type
+ </Col>
+ <Col sm={7}>
+ <div>
+ <Radio
+ id="urlConnType"
+ value="ldap"
+ name="ldap"
+ inline
+ checked={urlConnType ===
"ldap"}
+
onChange={this.handleFieldChange}
+
+ ldap
+ </Radio>
+ <Radio
+ id="urlConnType"
+ value="ldaps"
+ name="ldaps"
+ inline
+ checked={urlConnType ===
"ldaps"}
+
onChange={this.handleFieldChange}
+
+ ldaps
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ {Object.entries(modalURLFields).map(([id,
content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={5}>
+ <ControlLabel
title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={content.value}
+
onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+
+ <FormGroup
+ key="urlLDVer"
+ controlId="urlLDVer"
+ disabled={false}
+
+
<Col
+ componentClass={ControlLabel}
+ sm={5}
+ title={`The version of the LDAP protocol
used to connect to the authenticating directory. Directory Server supports LDAP version 2
and 3. The default is version 3, and Red Hat strongly recommends against using LDAPv2,
which is old and will be deprecated.`}
+
+
Version
+ </Col>
+ <Col sm={7}>
+ <div>
+ <Radio
+ id="urlLDVer"
+ value="2"
+ name="LDAPv2"
+ inline
+ checked={urlLDVer ===
"2"}
+
onChange={this.handleFieldChange}
+
+
LDAPv2
+ </Radio>
+ <Radio
+ id="urlLDVer"
+ value="3"
+ name="LDAPv3"
+ inline
+ checked={urlLDVer ===
"3"}
+
onChange={this.handleFieldChange}
+
+
LDAPv3
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ <FormGroup key="urlStartTLS"
controlId="urlStartTLS">
+ <Col sm={4}>
+ <Checkbox
+ id="urlStartTLS"
+ checked={urlStartTLS}
+ onChange={this.handleCheckboxChange}
+ title={`A flag of whether to use
Start TLS for the connection to the authenticating directory. Start TLS establishes a
secure connection over the standard port, so it is useful for connecting using LDAP
instead of LDAPS. The TLS server and CA certificates need to be available on both of the
servers. To use Start TLS, the LDAP URL must use ldap:, not ldaps:.`}
+
+
Enable StartTLS
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup key="resultURL"
controlId="resultURL">
+ <Col sm={5}>
+ <ControlLabel title="The URL that
will be added or modified after you click 'Save'">
+ Result URL
+ </ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <ControlLabel>
+
{urlConnType}://{urlAuthDS}/{urlSubtree}{" "}
+
{urlMaxConns},{urlMaxOps},{urlTimeout},
+ {urlLDVer},{urlConnLifeTime},
+ {urlStartTLS ? "1" :
"0"}
+ </ControlLabel>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeURLModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newURLEntry ? this.addURL : this.editURL}
+
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <PluginBasicPAMConfig
rows={this.props.rows}
serverId={this.props.serverId}
cn="Pass Through Authentication"
@@ -18,7 +1133,40 @@ class PassthroughAuthentication extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <PassthroughAuthURLsTable
+ rows={urlRows}
+ editConfig={this.showEditURLModal}
+ deleteConfig={this.deleteURL}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddURLModal}
+
+ Add URL
+ </Button>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ <PassthroughAuthConfigsTable
+ rows={pamConfigRows}
+ editConfig={this.showEditPAMConfigModal}
+ deleteConfig={this.deletePAMConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddPAMConfigModal}
+
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicPAMConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
index ae37334..e2cb6a7 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
@@ -45,7 +45,8 @@ class PluginBasicConfig extends React.Component {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
};
}
@@ -90,9 +91,10 @@ class PluginBasicConfig extends React.Component {
toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
addNotification(
"error",
- `Error during ${pluginName} plugin modification - ${err}`
+ `Error during ${pluginName} plugin modification -
${errMsg.desc}`
);
toggleLoadingHandler();
this.setState({ disableSwitch: false });
@@ -118,7 +120,11 @@ class PluginBasicConfig extends React.Component {
currentPluginDependsOnNamed:
pluginRow["nsslapd-plugin-depends-on-named"] === undefined
? ""
- : pluginRow["nsslapd-plugin-depends-on-named"][0]
+ : pluginRow["nsslapd-plugin-depends-on-named"][0],
+ currentPluginPrecedence:
+ pluginRow["nsslapd-pluginprecedence"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginprecedence"][0]
});
}
this.updateSwitch();
@@ -160,6 +166,7 @@ class PluginBasicConfig extends React.Component {
currentPluginDescription,
currentPluginDependsOnType,
currentPluginDependsOnNamed,
+ currentPluginPrecedence,
disableSwitch
} = this.state;
@@ -172,7 +179,8 @@ class PluginBasicConfig extends React.Component {
currentPluginVendor: this.state.currentPluginVendor,
currentPluginVersion: this.state.currentPluginVersion,
currentPluginDescription: this.state.currentPluginDescription,
- currentPluginId: this.state.currentPluginId
+ currentPluginId: this.state.currentPluginId,
+ currentPluginPrecedence: this.state.currentPluginPrecedence
};
return (
<div>
@@ -185,24 +193,26 @@ class PluginBasicConfig extends React.Component {
</ControlLabel>
</h3>
</Col>
- <Col smOffset={1} sm={3}>
- <FormGroup key="switchPluginStatus"
controlId="switchPluginStatus">
- <ControlLabel
- className="toolbar-pf-find ds-float-left
ds-right-indent"
-
- Status
- </ControlLabel>
- <Switch
- bsSize="normal"
- title="normal"
- id="bsSize-example"
- value={currentPluginEnabled}
- onChange={() =>
this.handleSwitchChange(currentPluginEnabled)}
- animate={false}
- disabled={disableSwitch}
- />
- </FormGroup>
- </Col>
+ {this.props.removeSwitch || (
+ <Col smOffset={1} sm={3}>
+ <FormGroup key="switchPluginStatus"
controlId="switchPluginStatus">
+ <ControlLabel className="toolbar-pf-find
ds-float-left ds-right-indent">
+ Status
+ </ControlLabel>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="bsSize-example"
+ value={currentPluginEnabled}
+ onChange={() =>
+
this.handleSwitchChange(currentPluginEnabled)
+ }
+ animate={false}
+ disabled={disableSwitch}
+ />
+ </FormGroup>
+ </Col>
+ )}
</Row>
</Form>
{this.props.children}
@@ -296,6 +306,7 @@ class PluginBasicConfig extends React.Component {
description: currentPluginDescription,
dependsOnType: currentPluginDependsOnType,
dependsOnNamed: currentPluginDependsOnNamed,
+ precedence: currentPluginPrecedence,
specificPluginCMD: this.props.specificPluginCMD
})
}
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
index 7b21201..9ef5025 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
@@ -34,7 +34,8 @@ class PluginEditModal extends React.Component {
currentPluginVersion,
currentPluginDescription,
currentPluginDependsOnType,
- currentPluginDependsOnNamed
+ currentPluginDependsOnNamed,
+ currentPluginPrecedence
} = this.props.pluginData;
const modalFields = {
currentPluginType: currentPluginType,
@@ -43,7 +44,8 @@ class PluginEditModal extends React.Component {
currentPluginId: currentPluginId,
currentPluginVendor: currentPluginVendor,
currentPluginVersion: currentPluginVersion,
- currentPluginDescription: currentPluginDescription
+ currentPluginDescription: currentPluginDescription,
+ currentPluginPrecedence: currentPluginPrecedence
};
return (
@@ -100,7 +102,7 @@ class PluginEditModal extends React.Component {
controlId="currentPluginDependsOnType"
disabled={false}
- <Col
componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={5}>
Plugin Depends On Type
</Col>
<Col sm={6}>
@@ -116,8 +118,8 @@ class PluginEditModal extends React.Component {
controlId="currentPluginDependsOnNamed"
disabled={false}
- <Col
componentClass={ControlLabel} sm={6}>
- Plugin Depends On Type
+ <Col componentClass={ControlLabel} sm={5}>
+ Plugin Depends On Named
</Col>
<Col sm={6}>
<FormControl
@@ -147,7 +149,8 @@ class PluginEditModal extends React.Component {
version: currentPluginVersion,
description: currentPluginDescription,
dependsOnType: currentPluginDependsOnType,
- dependsOnNamed: currentPluginDependsOnNamed
+ dependsOnNamed: currentPluginDependsOnNamed,
+ precedence: currentPluginPrecedence
})
}
@@ -174,7 +177,8 @@ PluginEditModal.propTypes = {
currentPluginVersion: PropTypes.string,
currentPluginDescription: PropTypes.string,
currentPluginDependsOnType: PropTypes.string,
- currentPluginDependsOnNamed: PropTypes.string
+ currentPluginDependsOnNamed: PropTypes.string,
+ currentPluginPrecedence: PropTypes.string
}),
closeHandler: PropTypes.func,
savePluginHandler: PropTypes.func,
@@ -195,7 +199,8 @@ PluginEditModal.defaultProps = {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
},
closeHandler: noop,
savePluginHandler: noop,
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
new file mode 100644
index 0000000..49abfa1
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
@@ -0,0 +1,1688 @@
+import React from "react";
+import {
+ Button,
+ DropdownButton,
+ MenuItem,
+ actionHeaderCellFormatter,
+ sortableHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import { DSTable } from "../dsTable.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+class PluginTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ searchFilterValue: "",
+ fieldsToSearch: ["cn", "nsslapd-pluginType"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Plugin Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginType",
+ header: {
+ label: "Plugin Type",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginEnabled",
+ header: {
+ label: "Enabled",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "Actions",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <Button
+ onClick={() => {
+ this.props.loadModalHandler(rowData);
+ }}
+
+
Edit Plugin
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div>
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ rowKey="cn"
+ rows={this.props.rows}
+ toolBarSearchField="Plugins"
+ toolBarDisableLoadingSpinner
+ />
+ </div>
+ );
+ }
+}
+
+PluginTable.propTypes = {
+ rows: PropTypes.array,
+ loadModalHandler: PropTypes.func
+};
+
+PluginTable.defaultProps = {
+ rows: [],
+ loadModalHandler: noop
+};
+
+class AttrUniqConfigTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "uniqueness-attribute-name"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "uniqueness-attribute-name",
+ header: {
+ label: "Attribute",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginenabled",
+ header: {
+ label: "Enabled",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AttrUniqConfigTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AttrUniqConfigTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class LinkedAttributesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "linkType",
"managedType", "linkScope"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "linktype",
+ header: {
+ label: "Link Type",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "managedtype",
+ header: {
+ label: "Managed Type",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "linkscope",
+ header: {
+ label: "Link Scope",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+LinkedAttributesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+LinkedAttributesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class DNATable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "dnanextvalue",
"dnafilter", "dnascope"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnascope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnafilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnanextvalue",
+ header: {
+ label: "Next Value",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+DNATable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+DNATable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class DNASharedTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["dnahostname", "dnaportnum",
"dnaremainingvalues"],
+
+ columns: [
+ {
+ property: "dnahostname",
+ header: {
+ label: "Hostname",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnaportnum",
+ header: {
+ label: "Port",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnaremainingvalues",
+ header: {
+ label: "Remaining Values",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.entrydn[0]}>
+ <DropdownButton
+ id={rowData.entrydn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="entrydn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+DNASharedTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+DNASharedTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class AutoMembershipDefinitionTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Definitions",
+ fieldsToSearch: [
+ "cn",
+ "automemberdefaultgroup",
+ "automemberfilter",
+ "automembergroupingattr",
+ "automemberscope"
+ ],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Definition Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberdefaultgroup",
+ header: {
+ label: "Default Group",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberscope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Definition
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Definition
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AutoMembershipDefinitionTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AutoMembershipDefinitionTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class AutoMembershipRegexTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: [
+ "cn",
+ "automemberexclusiveregex",
+ "automemberinclusiveregex",
+ "automembertargetgroup"
+ ],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberexclusiveregex",
+ header: {
+ label: "Exclusive Regex",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberinclusiveregex",
+ header: {
+ label: "Inclusive Regex",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automembertargetgroup",
+ header: {
+ label: "Target Group",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Regex
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Regex
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AutoMembershipRegexTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AutoMembershipRegexTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class ManagedEntriesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "originscope",
"originfilter", "managedbase"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "originscope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "originfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "managedbase",
+ header: {
+ label: "Managed Base",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+ManagedEntriesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ManagedEntriesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class PassthroughAuthURLsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "URLs",
+ fieldsToSearch: ["url"],
+
+ columns: [
+ {
+ property: "url",
+ header: {
+ label: "URL",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.id[0]}>
+ <DropdownButton
+ id={rowData.id[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit URL
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete URL
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="id"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+PassthroughAuthURLsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+PassthroughAuthURLsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class PassthroughAuthConfigsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "pamfilter",
"pamidattr", "pamidmapmethod"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamidattr",
+ header: {
+ label: "Attribute",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamidmapmethod",
+ header: {
+ label: "Map Method",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+
+
<MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+
+
Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+
+
Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+PassthroughAuthConfigsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+PassthroughAuthConfigsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+export {
+ PluginTable,
+ AttrUniqConfigTable,
+ LinkedAttributesTable,
+ DNATable,
+ DNASharedTable,
+ AutoMembershipDefinitionTable,
+ AutoMembershipRegexTable,
+ ManagedEntriesTable,
+ PassthroughAuthURLsTable,
+ PassthroughAuthConfigsTable
+};
diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
index 96e8464..bf3deee 100644
--- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
@@ -1,13 +1,612 @@
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ noop,
+ FormGroup,
+ FormControl,
+ Modal,
+ Icon,
+ Row,
+ Col,
+ Form,
+ Button,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class ReferentialIntegrity extends React.Component {
+ componentWillMount(prevProps) {
+ this.getAttributes();
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ updateDelay: "",
+ membershipAttr: [],
+ entryScope: "",
+ excludeEntryScope: "",
+ containerScope: "",
+ logFile: "",
+ referintConfigEntry: "",
+ configEntryModalShow: false,
+
+ configDN: "",
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: "",
+ newEntry: true,
+
+ attributes: []
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ openModal() {
+ this.getAttributes();
+ if (!this.state.referintConfigEntry) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: "",
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: ""
+ });
+ } else {
+ let membershipAttrList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ "show",
+ this.state.referintConfigEntry
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Referential Integrity Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let pluginRow = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configDN: this.state.referintConfigEntry,
+ configUpdateDelay:
+ pluginRow["referint-update-delay"] === undefined
+ ? ""
+ : pluginRow["referint-update-delay"][0],
+ configEntryScope:
+ pluginRow["nsslapd-pluginentryscope"] ===
undefined
+ ? ""
+ : pluginRow["nsslapd-pluginentryscope"][0],
+ configExcludeEntryScope:
+ pluginRow["nsslapd-pluginexcludeentryscope"] ===
undefined
+ ? ""
+ :
pluginRow["nsslapd-pluginexcludeentryscope"][0],
+ configContainerScope:
+ pluginRow["nsslapd-plugincontainerscope"] ===
undefined
+ ? ""
+ :
pluginRow["nsslapd-plugincontainerscope"][0],
+ configLogFile:
+ pluginRow["referint-logfile"] === undefined
+ ? ""
+ : pluginRow["referint-logfile"][0]
+ });
+
+ if (pluginRow["referint-membership-attr"] ===
undefined) {
+ this.setState({ configMembershipAttr: [] });
+ } else {
+ for (let value of
pluginRow["referint-membership-attr"]) {
+ membershipAttrList = [
+ ...membershipAttrList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ configMembershipAttr: membershipAttrList
+ });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: this.state.referintConfigEntry,
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configDN,
+ configUpdateDelay,
+ configMembershipAttr,
+ configEntryScope,
+ configExcludeEntryScope,
+ configContainerScope,
+ configLogFile
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ action,
+ configDN,
+ "--update-delay",
+ configUpdateDelay || action == "add" ? configUpdateDelay :
"delete",
+ "--entry-scope",
+ configEntryScope || action == "add" ? configEntryScope :
"delete",
+ "--exclude-entry-scope",
+ configExcludeEntryScope || action == "add" ?
configExcludeEntryScope : "delete",
+ "--container-scope",
+ configContainerScope || action == "add" ? configContainerScope :
"delete",
+ "--log-file",
+ configLogFile || action == "add" ? configLogFile :
"delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ cmd = [...cmd, "--membership-attr"];
+ if (configMembershipAttr.length != 0) {
+ for (let value of configMembershipAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "referintOperation",
+ `Do the ${action} operation on the Referential Integrity Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("referintOperation", "Result",
content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configDN} was successfully ${action}ed`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation -
${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig() {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ "delete",
+ this.state.configDN
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Referential Integrity Plugin
config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${this.state.configDN} was successfully deleted`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation -
${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ updateFields() {
+ let membershipAttrList = [];
+
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(
+ row => row.cn[0] === "referential integrity postoperation"
+ );
+
+ this.setState({
+ updateDelay:
+ pluginRow["referint-update-delay"] === undefined
+ ? ""
+ : pluginRow["referint-update-delay"][0],
+ entryScope:
+ pluginRow["nsslapd-pluginEntryScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginEntryScope"][0],
+ excludeEntryScope:
+ pluginRow["nsslapd-pluginExcludeEntryScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginExcludeEntryScope"][0],
+ containerScope:
+ pluginRow["nsslapd-pluginContainerScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginContainerScope"][0],
+ referintConfigEntry:
+ pluginRow["nsslapd-pluginConfigArea"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginConfigArea"][0],
+ logFile:
+ pluginRow["referint-logfile"] === undefined
+ ? ""
+ : pluginRow["referint-logfile"][0]
+ });
+
+ if (pluginRow["referint-membership-attr"] === undefined) {
+ this.setState({ membershipAttr: [] });
+ } else {
+ for (let value of pluginRow["referint-membership-attr"]) {
+ membershipAttrList = [...membershipAttrList, { id: value, label:
value }];
+ }
+ this.setState({ membershipAttr: membershipAttrList });
+ }
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ updateDelay,
+ membershipAttr,
+ entryScope,
+ excludeEntryScope,
+ containerScope,
+ logFile,
+ referintConfigEntry,
+ attributes,
+ configDN,
+ configUpdateDelay,
+ configMembershipAttr,
+ configEntryScope,
+ configExcludeEntryScope,
+ configContainerScope,
+ configEntryModalShow,
+ configLogFile,
+ newEntry
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "referential-integrity",
+ "set",
+ "--update-delay",
+ updateDelay || "delete",
+ "--entry-scope",
+ entryScope || "delete",
+ "--exclude-entry-scope",
+ excludeEntryScope || "delete",
+ "--container-scope",
+ containerScope || "delete",
+ "--config-entry",
+ referintConfigEntry || "delete",
+ "--log-file",
+ logFile || "delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ specificPluginCMD = [...specificPluginCMD, "--membership-attr"];
+ if (membershipAttr.length != 0) {
+ for (let value of membershipAttr) {
+ specificPluginCMD = [...specificPluginCMD, value.label];
+ }
+ } else {
+ specificPluginCMD = [...specificPluginCMD, "delete"];
+ }
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>
+ Manage Referential Integrity Plugin Shared Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configDN">
+ <Col sm={3}>
+ <ControlLabel title="The config
entry full DN">
+ Config DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configDN}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configUpdateDelay"
+ controlId="configUpdateDelay"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the update interval.
Special values: 0 - The check is performed immediately, -1 - No check is performed
(referint-update-delay)"
+
+
Update Delay
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configUpdateDelay}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configMembershipAttr"
+ controlId="configMembershipAttr"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies attributes to check
for and update (referint-membership-attr)"
+
+
Membership Attribute
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ configMembershipAttr: value
+ });
+ }}
+ selected={configMembershipAttr}
+ options={attributes}
+ newSelectionPrefix="Add a
membership attribute: "
+ placeholder="Type an
attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configEntryScope"
+ controlId="configEntryScope"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines the subtree in which
the plug-in looks for the delete or rename operations of a user entry
(nsslapd-pluginEntryScope)"
+
+
Entry Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configExcludeEntryScope"
+
controlId="configExcludeEntryScope"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines the subtree in which
the plug-in ignores any operations for deleting or renaming a user
(nsslapd-pluginExcludeEntryScope)"
+
+
Exclude Entry Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configExcludeEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configContainerScope"
+ controlId="configContainerScope"
+
+
<Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies which branch the
plug-in searches for the groups to which the user belongs. It only updates groups that are
under the specified container branch, and leaves all other groups not updated
(nsslapd-pluginContainerScope)"
+
+
Container Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configContainerScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="configLogFile"
controlId="configLogFile">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title={`Specifies a path to the
Referential integrity logfile. For example: /var/log/dirsrv/slapd-${
+ this.props.serverId
+ }/referint`}
+
+
Logfile
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configLogFile}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteConfig}
+ disabled={newEntry}
+
+ Delete
+ </Button>
+ <Button bsStyle="primary"
onClick={this.editConfig} disabled={newEntry}>
+ Save
+ </Button>
+ <Button bsStyle="primary"
onClick={this.addConfig} disabled={!newEntry}>
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +617,148 @@ class ReferentialIntegrity extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="updateDelay"
controlId="updateDelay">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets the update interval. Special
values: 0 - The check is performed immediately, -1 - No check is performed
(referint-update-delay)"
+
+
Update Delay
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={updateDelay}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="membershipAttr"
controlId="membershipAttr">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies attributes to check for and
update (referint-membership-attr)"
+
+
Membership Attribute
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ membershipAttr: value
+ });
+ }}
+ selected={membershipAttr}
+ options={attributes}
+ newSelectionPrefix="Add a membership
attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="entryScope"
controlId="entryScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Defines the subtree in which the
plug-in looks for the delete or rename operations of a user entry
(nsslapd-pluginEntryScope)"
+
+
Entry Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={entryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="excludeEntryScope"
controlId="excludeEntryScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Defines the subtree in which the
plug-in ignores any operations for deleting or renaming a user
(nsslapd-pluginExcludeEntryScope)"
+
+
Exclude Entry Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={excludeEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="containerScope"
controlId="containerScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies which branch the plug-in
searches for the groups to which the user belongs. It only updates groups that are under
the specified container branch, and leaves all other groups not updated
(nsslapd-pluginContainerScope)"
+
+
Container Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={containerScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="logFile"
controlId="logFile">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title={`Specifies a path to the Referential
integrity logfile. For example: /var/log/dirsrv/slapd-${
+ this.props.serverId
+ }/referint`}
+
+
Logfile
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={logFile}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="referintConfigEntry"
+ controlId="referintConfigEntry"
+
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="The value to set as
nsslapd-pluginConfigArea"
+
+
Shared Config Entry
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={referintConfigEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openModal}
+
+
Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
index 4e3490b..41eb859 100644
--- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
@@ -5,7 +5,140 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class RetroChangelog extends React.Component {
+ componentWillMount(prevProps) {
+ this.getAttributes();
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isReplicated: false,
+ attribute: [],
+ directory: "",
+ maxAge: "",
+ excludeSuffix: "",
+ attributes: []
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Retro
Changelog Plugin");
+
+ this.setState({
+ isReplicated: !(
+ pluginRow["isReplicated"] === undefined ||
+ pluginRow["isReplicated"][0] == "FALSE"
+ ),
+ attribute:
+ pluginRow["nsslapd-attribute"] === undefined
+ ? []
+ : [
+ {
+ id: pluginRow["nsslapd-attribute"][0],
+ label: pluginRow["nsslapd-attribute"][0]
+ }
+ ],
+ directory:
+ pluginRow["nsslapd-changelogdir"] === undefined
+ ? ""
+ : pluginRow["nsslapd-changelogdir"][0],
+ maxAge:
+ pluginRow["nsslapd-changelogmaxage"] === undefined
+ ? ""
+ : pluginRow["nsslapd-changelogmaxage"][0],
+ excludeSuffix:
+ pluginRow["nsslapd-exclude-suffix"] === undefined
+ ? ""
+ : pluginRow["nsslapd-exclude-suffix"][0]
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ isReplicated,
+ attribute,
+ directory,
+ maxAge,
+ excludeSuffix,
+ attributes
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "retro-changelog",
+ "set",
+ "--is-replicated",
+ isReplicated ? "TRUE" : "FALSE",
+ "--attribute",
+ attribute.length != 0 ? attribute[0].label : "delete",
+ "--directory",
+ directory || "delete",
+ "--max-age",
+ maxAge || "delete",
+ "--exclude-suffix",
+ excludeSuffix || "delete"
+ ];
+
return (
<div>
<PluginBasicConfig
@@ -18,7 +151,95 @@ class RetroChangelog extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="attribute"
controlId="attribute">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies another Directory Server
attribute which must be included in the retro changelog entries (nsslapd-attribute)"
+
+
Attribute
+ </Col>
+ <Col sm={7}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ attribute: value
+ });
+ }}
+ selected={attribute}
+ options={attributes}
+ newSelectionPrefix="Add an attribute:
"
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="directory"
controlId="directory">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies the name of the directory
in which the changelog database is created the first time the plug-in is run"
+
+
Directory
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={directory}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="maxAge"
controlId="maxAge">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="This attribute specifies the maximum
age of any entry in the changelog (nsslapd-changelogmaxage)"
+
+
Max Age
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={maxAge}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="excludeSuffix"
controlId="excludeSuffix">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="This attribute specifies the suffix
which will be excluded from the scope of the plugin (nsslapd-exclude-suffix)"
+
+
Exclude Suffix
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ type="text"
+ value={excludeSuffix}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="isReplicated"
+ checked={isReplicated}
+ onChange={this.handleCheckboxChange}
+ title="Sets a flag to indicate on a
change in the changelog whether the change is newly made on that server or whether it was
replicated over from another server (isReplicated)"
+
+
Is Replicated
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
index 3e4d820..d1bac89 100644
--- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
@@ -1,10 +1,102 @@
import React from "react";
-import { noop } from "patternfly-react";
+import { noop, FormGroup, FormControl, Row, Col, Form, ControlLabel } from
"patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class RootDNAccessControl extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ allowHost: [],
+ denyHost: [],
+ allowIP: [],
+ denyIP: [],
+ openTime: "",
+ closeTime: "",
+ daysAllowed: ""
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ let allowHostList = [];
+ let denyHostList = [];
+ let allowIPList = [];
+ let denyIPList = [];
+
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "RootDN
Access Control");
+ this.setState({
+ openTime:
+ pluginRow["rootdn-open-time"] === undefined
+ ? ""
+ : pluginRow["rootdn-open-time"][0],
+ closeTime:
+ pluginRow["rootdn-close-time"] === undefined
+ ? ""
+ : pluginRow["rootdn-close-time"][0],
+ daysAllowed:
+ pluginRow["rootdn-days-allowed"] === undefined
+ ? ""
+ : pluginRow["rootdn-days-allowed"][0]
+ });
+
+ if (pluginRow["rootdn-allow-host"] === undefined) {
+ this.setState({ allowHost: [] });
+ } else {
+ for (let value of pluginRow["rootdn-allow-host"]) {
+ allowHostList = [...allowHostList, { id: value, label: value }];
+ }
+ this.setState({ allowHost: allowHostList });
+ }
+ if (pluginRow["rootdn-deny-host"] === undefined) {
+ this.setState({ denyHost: [] });
+ } else {
+ for (let value of pluginRow["rootdn-deny-host"]) {
+ denyHostList = [...denyHostList, { id: value, label: value }];
+ }
+ this.setState({ denyHost: denyHostList });
+ }
+ if (pluginRow["rootdn-allow-ip"] === undefined) {
+ this.setState({ allowIP: [] });
+ } else {
+ for (let value of pluginRow["rootdn-allow-ip"]) {
+ allowIPList = [...allowIPList, { id: value, label: value }];
+ }
+ this.setState({ allowIP: allowIPList });
+ }
+ if (pluginRow["rootdn-deny-ip"] === undefined) {
+ this.setState({ denyIP: [] });
+ } else {
+ for (let value of pluginRow["rootdn-deny-ip"]) {
+ denyIPList = [...denyIPList, { id: value, label: value }];
+ }
+ this.setState({ denyIP: denyIPList });
+ }
+ }
+ }
+
render() {
return (
<div>
@@ -18,7 +110,158 @@ class RootDNAccessControl extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="allowHost"
controlId="allowHost">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what hosts, by fully-qualified
domain name, the root user is allowed to use to access the Directory Server. Any hosts not
listed are implicitly denied (rootdn-allow-host)"
+
+
Allow Host
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ allowHost: value
+ });
+ }}
+ selected={allowHost}
+ options={[]}
+ newSelectionPrefix="Add a host to allow:
"
+ placeholder="Type a hostname (wild cards
are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="denyHost"
controlId="denyHost">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what hosts, by fully-qualified
domain name, the root user is not allowed to use to access the Directory Server Any hosts
not listed are implicitly allowed (rootdn-deny-host). If an host address is listed in both
the rootdn-allow-host and rootdn-deny-host attributes, it is denied access."
+
+
Deny Host
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ denyHost: value
+ });
+ }}
+ selected={denyHost}
+ options={[]}
+ newSelectionPrefix="Add a host to deny:
"
+ placeholder="Type a hostname (wild cards
are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="allowIP"
controlId="allowIP">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what IP addresses, either IPv4
or IPv6, for machines the root user is allowed to use to access the Directory Server Any
IP addresses not listed are implicitly denied (rootdn-allow-ip)"
+
+
Allow IP address
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ allowIP: value
+ });
+ }}
+ selected={allowIP}
+ options={[]}
+ newSelectionPrefix="Add an IP address to
allow: "
+ placeholder="Type an IP address (wild
cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="denyIP"
controlId="denyIP">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what IP addresses, either IPv4
or IPv6, for machines the root user is not allowed to use to access the Directory Server.
Any IP addresses not listed are implicitly allowed (rootdn-deny-ip) If an IP address is
listed in both the rootdn-allow-ip and rootdn-deny-ip attributes, it is denied
access."
+
+
Deny IP address
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ denyIP: value
+ });
+ }}
+ selected={denyIP}
+ options={[]}
+ newSelectionPrefix="Add an IP address to
deny: "
+ placeholder="Type an IP address (wild
cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="openTime"
controlId="openTime">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets part of a time period or range
when the root user is allowed to access the Directory Server. This sets when the
time-based access begins (rootdn-open-time)"
+
+
Open Time
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={openTime}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="closeTime"
controlId="closeTime">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets part of a time period or range
when the root user is allowed to access the Directory Server. This sets when the
time-based access ends (rootdn-close-time)"
+
+
Close Time
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={closeTime}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="daysAllowed"
controlId="daysAllowed">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Gives a comma-separated list of what
days the root user is allowed to use to access the Directory Server. Any days listed are
implicitly denied (rootdn-days-allowed)"
+
+
Days Allowed
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={daysAllowed}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx
b/src/cockpit/389-console/src/lib/plugins/usn.jsx
index bb4d321..08d7beb 100644
--- a/src/cockpit/389-console/src/lib/plugins/usn.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx
@@ -1,13 +1,284 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ Switch,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class USN extends React.Component {
+ componentWillMount() {
+ this.updateSwitch();
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.runCleanup = this.runCleanup.bind(this);
+ this.toggleCleanupModal = this.toggleCleanupModal.bind(this);
+ this.updateSwitch = this.updateSwitch.bind(this);
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.state = {
+ globalMode: false,
+ disableSwitch: false,
+ cleanupModalShow: false,
+ cleanupSuffix: "",
+ cleanupBackend: "",
+ cleanupMaxUSN: ""
+ };
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ handleSwitchChange(value) {
+ const { serverId, addNotification, toggleLoadingHandler } = this.props;
+ const new_status = this.state.globalMode ? "off" : "on";
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + serverId + ".socket",
+ "plugin",
+ "usn",
+ "global",
+ new_status
+ ];
+
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: true });
+ log_cmd("handleSwitchChange", "Switch global USN mode from the USN
plugin tab", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ console.info("savePlugin", "Result", content);
+ this.updateSwitch();
+ addNotification(
+ "success",
+ `Global USN mode was successfully set to ${new_status}.`
+ );
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: false });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ addNotification(
+ "error",
+ `Error during global USN mode modification - ${errMsg.desc}`
+ );
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: false });
+ });
+ }
+
+ updateSwitch() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "config",
+ "get",
+ "nsslapd-entryusn-global"
+ ];
+
+ this.setState({
+ disableSwitch: true
+ });
+ this.props.toggleLoadingHandler();
+ log_cmd("updateSwitch", "Get global USN status", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ let usnGlobalAttr =
myObject.attrs["nsslapd-entryusn-global"][0];
+ this.setState({
+ globalMode: !(usnGlobalAttr == "off")
+ });
+ this.setState({
+ disableSwitch: false
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ if (err != 0) {
+ let errMsg = JSON.parse(err);
+ console.log("Get global USN failed", errMsg.desc);
+ }
+ this.setState({
+ disableSwitch: false
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ toggleCleanupModal() {
+ this.setState(prevState => ({
+ cleanupModalShow: !prevState.cleanupModalShow,
+ cleanupSuffix: "",
+ cleanupBackend: "",
+ cleanupMaxUSN: ""
+ }));
+ }
+
+ runCleanup() {
+ if (!this.state.cleanupSuffix && !this.state.cleanupBackend) {
+ this.props.addNotification("warning", "Suffix or backend name
is required.");
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "usn",
+ "cleanup"
+ ];
+
+ if (this.state.cleanupSuffix) {
+ cmd = [...cmd, "--suffix", this.state.cleanupSuffix];
+ }
+ if (this.state.cleanupBackend) {
+ cmd = [...cmd, "--backend", this.state.cleanupBackend];
+ }
+ if (this.state.cleanupMaxUSN) {
+ cmd = [...cmd, "--max-usn", this.state.cleanupMaxUSN];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd("runCleanup", "Run cleanup USN tombstones",
cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Cleanup USN Tombstones task was successfull`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ cleanupModalShow: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Cleanup USN Tombstones task has failed ${errMsg.desc}`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ cleanupModalShow: false
+ });
+ });
+ }
+ }
+
render() {
+ const {
+ globalMode,
+ disableSwitch,
+ cleanupModalShow,
+ cleanupSuffix,
+ cleanupBackend,
+ cleanupMaxUSN
+ } = this.state;
+
return (
<div>
+ <Modal show={cleanupModalShow} onHide={this.toggleCleanupModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.toggleCleanupModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>Fixup MemberOf Task</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="cleanupSuffix"
key="cleanupSuffix">
+ <Col sm={3}>
+ <ControlLabel title="Gives the
suffix or subtree in the Directory Server to run the cleanup operation against">
+ Cleanup Suffix
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupSuffix}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
controlId="cleanupBackend" key="cleanupBackend">
+ <Col sm={3}>
+ <ControlLabel title="Gives the
Directory Server instance back end, or database, to run the cleanup operation
against">
+ Cleanup Backend
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupBackend}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="cleanupMaxUSN"
key="cleanupMaxUSN">
+ <Col sm={3}>
+ <ControlLabel title="Gives the
highest USN value to delete when removing tombstone entries. All tombstone entries up to
and including that number are deleted. Tombstone entries with higher USN values (that
means more recent entries) are not deleted">
+ Cleanup Max USN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupMaxUSN}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.toggleCleanupModal}
+
+ Cancel
+ </Button>
+ <Button bsStyle="primary"
onClick={this.runCleanup}>
+ Run
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +289,44 @@ class USN extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <FormGroup key="globalMode"
controlId="globalMode">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines if the USN plug-in assigns unique
update sequence numbers (USN) across all back end databases or to each database
individually"
+
+ USN Global
+ </Col>
+ <Col sm={3}>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="bsSize-example"
+ value={globalMode}
+ onChange={() =>
this.handleSwitchChange(globalMode)}
+ animate={false}
+ disabled={disableSwitch}
+ />
+ </Col>
+ </FormGroup>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ <Button
+ className="ds-margin-top"
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.toggleCleanupModal}
+
+ Run Fixup Task
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/winsync.jsx
b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
new file mode 100644
index 0000000..39be7a9
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
@@ -0,0 +1,234 @@
+import React from "react";
+import { Row, Col, Form, noop, FormGroup, Checkbox, ControlLabel } from
"patternfly-react";
+import PropTypes from "prop-types";
+import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import "../../css/ds.css";
+
+class WinSync extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.updateFields = this.updateFields.bind(this);
+
+ this.state = {
+ posixWinsyncCreateMemberOfTask: false,
+ posixWinsyncLowerCaseUID: false,
+ posixWinsyncMapMemberUID: false,
+ posixWinsyncMapNestedGrouping: false,
+ posixWinsyncMsSFUSchema: false
+ };
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Posix
Winsync API");
+
+ this.setState({
+ posixWinsyncCreateMemberOfTask: !(
+ pluginRow["posixwinsynccreatememberoftask"] === undefined
||
+ pluginRow["posixwinsynccreatememberoftask"][0] ==
"false"
+ ),
+ posixWinsyncLowerCaseUID: !(
+ pluginRow["posixwinsynclowercaseuid"] === undefined ||
+ pluginRow["posixwinsynclowercaseuid"][0] ==
"false"
+ ),
+ posixWinsyncMapMemberUID: !(
+ pluginRow["posixwinsyncmapmemberuid"] === undefined ||
+ pluginRow["posixwinsyncmapmemberuid"][0] ==
"false"
+ ),
+ posixWinsyncMapNestedGrouping: !(
+ pluginRow["posixwinsyncmapnestedgrouping"] === undefined
||
+ pluginRow["posixwinsyncmapnestedgrouping"][0] ==
"false"
+ ),
+ posixWinsyncMsSFUSchema: !(
+ pluginRow["posixwinsyncmssfuschema"] === undefined ||
+ pluginRow["posixwinsyncmssfuschema"][0] ==
"false"
+ )
+ });
+ }
+ }
+
+ render() {
+ const {
+ posixWinsyncCreateMemberOfTask,
+ posixWinsyncLowerCaseUID,
+ posixWinsyncMapMemberUID,
+ posixWinsyncMapNestedGrouping,
+ posixWinsyncMsSFUSchema
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "posix-winsync",
+ "set",
+ "--create-memberof-task",
+ posixWinsyncCreateMemberOfTask ? "true" : "false",
+ "--lower-case-uid",
+ posixWinsyncLowerCaseUID ? "true" : "false",
+ "--map-member-uid",
+ posixWinsyncMapMemberUID ? "true" : "false",
+ "--map-nested-grouping",
+ posixWinsyncMapNestedGrouping ? "true" : "false",
+ "--ms-sfu-schema",
+ posixWinsyncMsSFUSchema ? "true" : "false"
+ ];
+ return (
+ <div>
+ <PluginBasicConfig
+ rows={this.props.rows}
+ serverId={this.props.serverId}
+ cn="Posix Winsync API"
+ pluginName="Posix Winsync API"
+ cmdName="posix-winsync"
+ specificPluginCMD={specificPluginCMD}
+ savePluginHandler={this.props.savePluginHandler}
+ pluginListHandler={this.props.pluginListHandler}
+ addNotification={this.props.addNotification}
+ toggleLoadingHandler={this.props.toggleLoadingHandler}
+ >
+ <Row>
+ <Col sm={9}>
+ <Form horizontal>
+ <FormGroup
+ key="posixWinsyncCreateMemberOfTask"
+ controlId="posixWinsyncCreateMemberOfTask"
+
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to run the memberOf
fix-up task immediately after a sync run in order to update group memberships for synced
users"
+
+
Create MemberOf Task
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+
id="posixWinsyncCreateMemberOfTask"
+ checked={posixWinsyncCreateMemberOfTask}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncLowerCaseUID"
+ controlId="posixWinsyncLowerCaseUID"
+
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to store (and, if
necessary, convert) the UID value in the memberUID attribute in lower case"
+
+
Lower Case UID
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncLowerCaseUID"
+ checked={posixWinsyncLowerCaseUID}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMapMemberUID"
+ controlId="posixWinsyncMapMemberUID"
+
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to map the memberUID
attribute in an Active Directory group to the uniqueMember attribute in a Directory Server
group"
+
+
Map Member UID
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMapMemberUID"
+ checked={posixWinsyncMapMemberUID}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMapNestedGrouping"
+ controlId="posixWinsyncMapNestedGrouping"
+
+ <Col
+ title="Manages if nested groups are updated
when memberUID attributes in an Active Directory POSIX group change"
+ componentClass={ControlLabel}
+ sm={3}
+
+
Map Nested Grouping
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMapNestedGrouping"
+ checked={posixWinsyncMapNestedGrouping}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMsSFUSchema"
+ controlId="posixWinsyncMsSFUSchema"
+
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to the older Microsoft
System Services for Unix 3.0 (msSFU30) schema when syncing Posix attributes from Active
Directory"
+
+
Microsoft System Services for Unix 3.0 (msSFU30) schema
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMsSFUSchema"
+ checked={posixWinsyncMsSFUSchema}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
+ </div>
+ );
+ }
+}
+
+WinSync.propTypes = {
+ rows: PropTypes.array,
+ serverId: PropTypes.string,
+ savePluginHandler: PropTypes.func,
+ pluginListHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ toggleLoadingHandler: PropTypes.func
+};
+
+WinSync.defaultProps = {
+ rows: [],
+ serverId: "",
+ savePluginHandler: noop,
+ pluginListHandler: noop,
+ addNotification: noop,
+ toggleLoadingHandler: noop
+};
+
+export default WinSync;
diff --git a/src/cockpit/389-console/src/plugins.jsx
b/src/cockpit/389-console/src/plugins.jsx
index ac9f8b7..1dc267c 100644
--- a/src/cockpit/389-console/src/plugins.jsx
+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -71,7 +71,8 @@ export class Plugins extends React.Component {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
};
}
@@ -157,6 +158,10 @@ export class Plugins extends React.Component {
rowData["nsslapd-plugin-depends-on-named"] === undefined
? ""
: rowData["nsslapd-plugin-depends-on-named"][0],
+ currentPluginPrecedence:
+ rowData["nsslapd-pluginprecedence"] === undefined
+ ? ""
+ : rowData["nsslapd-pluginprecedence"][0],
showPluginModal: true
});
}
@@ -216,7 +221,9 @@ export class Plugins extends React.Component {
"--depends-on-type",
data.dependsOnType || "delete",
"--depends-on-named",
- data.dependsOnNamed || "delete"
+ data.dependsOnNamed || "delete",
+ "--precedence",
+ data.precedence || "delete"
];
if ("enabled" in data) {
@@ -231,10 +238,8 @@ export class Plugins extends React.Component {
.done(content => {
console.info("savePlugin", "Result", content);
basicPluginSuccess = true;
- this.addNotification(
- "success",
- `Plugin ${data.name} was successfully modified`
- );
+ this.addNotification("success", `Plugin ${data.name} was
successfully modified`);
+ this.pluginList();
this.closePluginModal();
this.toggleLoading();
})
@@ -252,7 +257,7 @@ export class Plugins extends React.Component {
this.toggleLoading();
})
.always(() => {
- if ("specificPluginCMD" in data) {
+ if ("specificPluginCMD" in data &&
data.specificPluginCMD.length != 0) {
this.toggleLoading();
log_cmd(
"savePlugin",
@@ -279,11 +284,8 @@ export class Plugins extends React.Component {
.fail(err => {
let errMsg = JSON.parse(err);
if (
- (errMsg.desc.indexOf(
- "nothing to set"
- ) >= 0 &&
- nothingToSetErr) ||
- errMsg.desc.indexOf("nothing to set")
< 0
+ (errMsg.desc.indexOf("nothing to set")
>= 0 && nothingToSetErr) ||
+ errMsg.desc.indexOf("nothing to set") < 0
) {
if (basicPluginSuccess) {
this.addNotification(
@@ -528,7 +530,8 @@ export class Plugins extends React.Component {
currentPluginVersion: this.state.currentPluginVersion,
currentPluginDescription: this.state.currentPluginDescription,
currentPluginDependsOnType:
this.state.currentPluginDependsOnType,
- currentPluginDependsOnNamed:
this.state.currentPluginDependsOnNamed
+ currentPluginDependsOnNamed:
this.state.currentPluginDependsOnNamed,
+ currentPluginPrecedence: this.state.currentPluginPrecedence
}}
closeHandler={this.closePluginModal}
showModal={this.state.showPluginModal}
diff --git a/src/lib389/lib389/cli_conf/__init__.py
b/src/lib389/lib389/cli_conf/__init__.py
index 2567f1e..2e8f2a6 100644
--- a/src/lib389/lib389/cli_conf/__init__.py
+++ b/src/lib389/lib389/cli_conf/__init__.py
@@ -35,7 +35,7 @@ def generic_object_add(dsldap_objects_class, inst, log, args,
arg_to_attr, dn=No
rdn = None
# Gather the attributes
attrs = _args_to_attrs(args, arg_to_attr)
- props.update({attr: value for (attr, value) in attrs.items() if value !=
""})
+ props.update({attr: value for (attr, value) in attrs.items() if value != ""
and value != [""]})
# Get RDN attribute and Base DN from the DN if Base DN is not specified
if basedn is None:
diff --git a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
index e585d2a..60134c7 100644
--- a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
+++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
@@ -32,6 +32,8 @@ def accountpolicy_edit(inst, basedn, log, args):
def accountpolicy_add_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_add_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = generic_object_add(AccountPolicyConfig, inst, log, args, arg_to_attr_config,
dn=targetdn)
plugin = AccountPolicyPlugin(inst)
plugin.replace('nsslapd_pluginConfigArea', config.dn)
@@ -42,6 +44,8 @@ def accountpolicy_add_config(inst, basedn, log, args):
def accountpolicy_edit_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_edit_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
generic_object_edit(config, log, args, arg_to_attr_config)
@@ -49,6 +53,8 @@ def accountpolicy_edit_config(inst, basedn, log, args):
def accountpolicy_show_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_show_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
if not config.exists():
@@ -64,6 +70,8 @@ def accountpolicy_show_config(inst, basedn, log, args):
def accountpolicy_del_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_del_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
config.delete()
log.info("Successfully deleted the %s", targetdn)
diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py
b/src/lib389/lib389/cli_conf/plugins/automember.py
index fbd270a..bc5e55e 100644
--- a/src/lib389/lib389/cli_conf/plugins/automember.py
+++ b/src/lib389/lib389/cli_conf/plugins/automember.py
@@ -29,43 +29,44 @@ arg_to_attr_regex = {
def definition_list(inst, basedn, log, args):
automembers = AutoMembershipDefinitions(inst)
- all_definitions = automembers.list()
+ result = []
+ result_json = []
+ for definition in automembers.list():
+ if args.json:
+ result_json.append(definition.get_all_attrs_json())
+ else:
+ result.append(definition.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(all_definitions) > 0:
- for definition in all_definitions:
- if args.json:
- result['items'].append(definition)
- else:
- log.info(definition.rdn)
+ log.info(json.dumps({"type": "list", "items":
result_json}))
else:
- log.info("No automember definitions were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No Automember definitions were found")
def definition_add(inst, basedn, log, args):
log = log.getChild('definition_add')
plugin = AutoMembershipPlugin(inst)
- props = {'cn': args.DEF_NAME}
+ props = {'cn': args.DEFNAME}
generic_object_add(AutoMembershipDefinition, inst, log, args, arg_to_attr_definition,
basedn=plugin.dn, props=props)
def definition_edit(inst, basedn, log, args):
log = log.getChild('definition_edit')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
generic_object_edit(definition, log, args, arg_to_attr_definition)
def definition_show(inst, basedn, log, args):
log = log.getChild('definition_show')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
if not definition.exists():
- raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" %
args.DEFNAME)
if args and args.json:
o_str = definition.get_all_attrs_json()
log.info(o_str)
@@ -76,57 +77,58 @@ def definition_show(inst, basedn, log, args):
def definition_del(inst, basedn, log, args):
log = log.getChild('definition_del')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
definition.delete()
- log.info("Successfully deleted the %s definition", args.name)
+ log.info("Successfully deleted the %s definition", args.DEFNAME)
def regex_list(inst, basedn, log, args):
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- all_regexes = regexes.list()
+ result = []
+ result_json = []
+ for regex in regexes.list():
+ if args.json:
+ result_json.append(regex.get_all_attrs_json())
+ else:
+ result.append(regex.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(all_regexes) > 0:
- for regex in all_regexes:
- if args.json:
- result['items'].append(regex)
- else:
- log.info(regex.rdn)
+ log.info(json.dumps({"type": "list", "items":
result_json}))
else:
- log.info("No automember regexes were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No Automember regexes were found")
def regex_add(inst, basedn, log, args):
log = log.getChild('regex_add')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
- props = {'cn': args.REGEX_NAME}
+ definition = definitions.get(args.DEFNAME)
+ props = {'cn': args.REGEXNAME}
generic_object_add(AutoMembershipRegexRule, inst, log, args, arg_to_attr_regex,
basedn=definition.dn, props=props)
def regex_edit(inst, basedn, log, args):
log = log.getChild('regex_edit')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
generic_object_edit(regex, log, args, arg_to_attr_regex)
def regex_show(inst, basedn, log, args):
log = log.getChild('regex_show')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
if not regex.exists():
- raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" %
args.REGEXNAME)
if args and args.json:
o_str = regex.get_all_attrs_json()
log.info(o_str)
@@ -137,11 +139,11 @@ def regex_show(inst, basedn, log, args):
def regex_del(inst, basedn, log, args):
log = log.getChild('regex_del')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
regex.delete()
- log.info("Successfully deleted the %s regex", regex.dn)
+ log.info("Successfully deleted the %s regex", args.REGEXNAME)
def fixup(inst, basedn, log, args):
@@ -159,25 +161,25 @@ def fixup(inst, basedn, log, args):
def _add_parser_args_definition(parser):
- parser.add_argument('--grouping-attr',
+ parser.add_argument('--grouping-attr', required=True,
help='Specifies the name of the member attribute in the group
entry and '
'the attribute in the object entry that supplies the
member attribute value, '
'in the format group_member_attr:entry_attr
(autoMemberGroupingAttr)')
- parser.add_argument('--default-group', required=True,
+ parser.add_argument('--default-group',
help='Sets default or fallback group to add the entry to as a
member '
- 'member attribute in group entry
(autoMemberDefaultGroup)')
+ 'attribute in group entry
(autoMemberDefaultGroup)')
parser.add_argument('--scope', required=True,
help='Sets the subtree DN to search for entries
(autoMemberScope)')
- parser.add_argument('--filter',
+ parser.add_argument('--filter', required=True,
help='Sets a standard LDAP search filter to use to search for
'
'matching entries (autoMemberFilter)')
def _add_parser_args_regex(parser):
- parser.add_argument("--exclusive",
+ parser.add_argument("--exclusive", nargs='+',
help='Sets a single regular expression to use to identify
'
'entries to exclude (autoMemberExclusiveRegex)')
- parser.add_argument('--inclusive', required=True,
+ parser.add_argument('--inclusive', nargs='+',
help='Sets a single regular expression to use to identify
'
'entries to include (autoMemberInclusiveRegex)')
parser.add_argument('--target-group', required=True,
@@ -195,11 +197,11 @@ def create_parser(subparsers):
list_definitions = subcommands_list.add_parser('definitions', help='List
Automembership definitions.')
list_definitions.set_defaults(func=definition_list)
list_regexes = subcommands_list.add_parser('regexes', help='List
Automembership regex rules.')
- list_regexes.add_argument('DEF-NAME', help='The definition entry
CN.')
+ list_regexes.add_argument('DEFNAME', help='The definition entry
CN.')
list_regexes.set_defaults(func=regex_list)
definition = subcommands.add_parser('definition', help='Manage
Automembership definition.')
- definition.add_argument('DEF-NAME', help='The definition entry CN.')
+ definition.add_argument('DEFNAME', help='The definition entry CN.')
subcommands_definition = definition.add_subparsers(help='action')
add_def = subcommands_definition.add_parser('add', help='Create
Automembership definition.')
@@ -210,19 +212,23 @@ def create_parser(subparsers):
_add_parser_args_definition(edit_def)
delete_def = subcommands_definition.add_parser('delete', help='Remove
Automembership definition.')
delete_def.set_defaults(func=definition_del)
+ show_def = subcommands_definition.add_parser('show', help='Display
Automembership definition.')
+ show_def.set_defaults(func=definition_show)
regex = subcommands_definition.add_parser('regex', help='Manage
Automembership regex rules.')
- regex.add_argument('REGEX-NAME', help='The regex entry CN.')
+ regex.add_argument('REGEXNAME', help='The regex entry CN.')
subcommands_regex = regex.add_subparsers(help='action')
add_regex = subcommands_regex.add_parser('add', help='Create
Automembership regex.')
add_regex.set_defaults(func=regex_add)
- _add_parser_args_definition(add_regex)
+ _add_parser_args_regex(add_regex)
edit_regex = subcommands_regex.add_parser('set', help='Edit
Automembership regex.')
edit_regex.set_defaults(func=regex_edit)
- _add_parser_args_definition(edit_regex)
+ _add_parser_args_regex(edit_regex)
delete_regex = subcommands_regex.add_parser('delete', help='Remove
Automembership regex.')
delete_regex.set_defaults(func=regex_del)
+ show_regex = subcommands_regex.add_parser('show', help='Display
Automembership regex.')
+ show_regex.set_defaults(func=regex_show)
fixup = subcommands.add_parser('fixup', help='Run a rebuild membership
task.')
fixup.set_defaults(func=fixup)
diff --git a/src/lib389/lib389/cli_conf/plugins/dna.py
b/src/lib389/lib389/cli_conf/plugins/dna.py
index 6033c17..a86034d 100644
--- a/src/lib389/lib389/cli_conf/plugins/dna.py
+++ b/src/lib389/lib389/cli_conf/plugins/dna.py
@@ -29,8 +29,8 @@ arg_to_attr = {
}
arg_to_attr_config = {
- 'hostname': 'dnaHostname',
- 'port': 'dnaPortNum',
+ 'HOSTNAME': 'dnaHostname',
+ 'PORT': 'dnaPortNum',
'secure_port': 'dnaSecurePortNum',
'remaining_values': 'dnaRemainingValues',
'remote_bind_method': 'dnaRemoteBindMethod',
@@ -38,23 +38,37 @@ arg_to_attr_config = {
}
+def _get_shared_config_dn(inst, args):
+ configs = DNAPluginConfigs(inst)
+ config = configs.get(args.NAME)
+ if config.present('dnaSharedCfgDN'):
+ basedn = config.get_attr_val_utf8_l('dnaSharedCfgDN')
+ else:
+ raise ValueError('dnaSharedCfgDN should be set at the "%s" config
entry' % args.NAME)
+
+ decomposed_dn = [[('dnaHostname', args.HOSTNAME, 1),
+ ('dnaPortNum', args.PORT, 1)]] + ldap.dn.str2dn(basedn)
+ return ldap.dn.dn2str(decomposed_dn)
+
+
def dna_list(inst, basedn, log, args):
log = log.getChild('dna_list')
configs = DNAPluginConfigs(inst)
- config_list = configs.list()
+ result = []
+ result_json = []
+ for config in configs.list():
+ if args.json:
+ result_json.append(config.get_all_attrs_json())
+ else:
+ result.append(config.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(config_list) > 0:
- for config in config_list:
- if args.json:
- result['items'].append(config)
- else:
- log.info(config.rdn)
+ log.info(json.dumps({"type": "list", "items":
result_json}))
else:
- log.info("No DNA configurations were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No DNA configurations were found")
def dna_add(inst, basedn, log, args):
@@ -96,25 +110,42 @@ def dna_del(inst, basedn, log, args):
def dna_config_list(inst, basedn, log, args):
log = log.getChild('dna_list')
configs = DNAPluginSharedConfigs(inst, args.BASEDN)
- config_list = configs.list()
+ result = []
+ result_json = []
+ parent_config_entries = []
+
+ parent_configs = DNAPluginConfigs(inst)
+ for config in parent_configs.list():
+ if config.present("dnaSharedCfgDN") and
config.get_attr_val_utf8("dnaSharedCfgDN") == args.BASEDN:
+ parent_config_entries.append(config.rdn)
+
+ for config in configs.list():
+ if args.json:
+ result_json.append(config.get_all_attrs_json())
+ else:
+ result.append(config.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(config_list) > 0:
- for config in config_list:
- if args.json:
- result['items'].append(config.get_all_attrs_json())
- else:
- log.info(config.dn)
+ log.info(json.dumps({"type": "list", "items":
result_json}))
else:
- log.info("No DNA shared configurations were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ if parent_config_entries:
+ log.info("DNA plugin configs which have the shared config entry set
as a dnaSharedCfgDN attribute: " + " ".join(parent_config_entries))
+ else:
+ log.info("No DNA plugin configs have the shared config entry set as
a dnaSharedCfgDN attribute.")
+ else:
+ log.info("No DNA shared configurations were found")
def dna_config_add(inst, basedn, log, args):
log = log.getChild('dna_config_add')
- targetdn = args.BASEDN
+ configs = DNAPluginConfigs(inst)
+ config = configs.get(args.NAME)
+ if config.present('dnaSharedCfgDN'):
+ targetdn = config.get_attr_val_utf8_l('dnaSharedCfgDN')
+ else:
+ raise ValueError('dnaSharedCfgDN should be set at the "%s" config
entry' % args.NAME)
shared_configs = DNAPluginSharedConfigs(inst, targetdn)
attrs = _args_to_attrs(args, arg_to_attr_config)
@@ -123,23 +154,19 @@ def dna_config_add(inst, basedn, log, args):
shared_config = shared_configs.create(properties=props)
log.info("Successfully created the %s" % shared_config.dn)
- configs = DNAPluginConfigs(inst)
- config = configs.get(args.NAME)
- config.replace('dnaSharedCfgDN', config.dn)
- log.info('DNA attribute dnaSharedCfgDN (shared-config-entry) '
- 'was set in the %s plugin config' % config.rdn)
-
def dna_config_edit(inst, basedn, log, args):
log = log.getChild('dna_config_edit')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
+
shared_config = DNAPluginSharedConfig(inst, targetdn)
generic_object_edit(shared_config, log, args, arg_to_attr_config)
def dna_config_show(inst, basedn, log, args):
log = log.getChild('dna_config_show')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
+
shared_config = DNAPluginSharedConfig(inst, targetdn)
if not shared_config.exists():
@@ -153,7 +180,7 @@ def dna_config_show(inst, basedn, log, args):
def dna_config_del(inst, basedn, log, args):
log = log.getChild('dna_config_del')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
shared_config = DNAPluginSharedConfig(inst, targetdn)
shared_config.delete()
log.info("Successfully deleted the %s", targetdn)
@@ -188,11 +215,6 @@ def _add_parser_args(parser):
def _add_parser_args_config(parser):
- parser.add_argument('--hostname',
- help='Identifies the host name of a server in a shared range,
as part of the DNA '
- 'range configuration for that specific host in
multi-master replication (dnaHostname)')
- parser.add_argument('--port', help='Gives the standard port number to use
to connect to '
- 'the host identified in dnaHostname
(dnaPortNum)')
parser.add_argument('--secure-port', help='Gives the secure (TLS) port
number to use to connect '
'to the host identified in dnaHostname
(dnaSecurePortNum)')
parser.add_argument('--remote-bind-method', help='Specifies the remote
bind method (dnaRemoteBindMethod)')
@@ -227,21 +249,22 @@ def create_parser(subparsers):
show.set_defaults(func=dna_show)
delete = config_subcommands.add_parser('delete', help='Delete the config
entry')
delete.set_defaults(func=dna_del)
+
shared_config = config_subcommands.add_parser('shared-config-entry',
help='Manage the shared config entry')
+ shared_config.add_argument('HOSTNAME',
+ help='Identifies the host name of a server in a shared
range, as part of the DNA range '
+ 'configuration for that specific host in
multi-master replication (dnaHostname)')
+ shared_config.add_argument('PORT', help='Gives the standard port number
to use to connect to '
+ 'the host identified in dnaHostname
(dnaPortNum)')
shared_config_subcommands = shared_config.add_subparsers(help='action')
add_config = shared_config_subcommands.add_parser('add', help='Add the
shared config entry')
- add_config.add_argument('BASEDN', help='The shared config entry BASE DN.
The new DN will be constructed with '
- 'dnaHostname and dnaPortNum')
add_config.set_defaults(func=dna_config_add)
_add_parser_args_config(add_config)
- edit_config = shared_config_subcommands.add_parser('edit', help='Edit the
shared config entry')
- edit_config.add_argument('DN', help='The shared config entry DN')
+ edit_config = shared_config_subcommands.add_parser('set', help='Edit the
shared config entry')
edit_config.set_defaults(func=dna_config_edit)
_add_parser_args_config(edit_config)
show_config_parser = shared_config_subcommands.add_parser('show',
help='Display the shared config entry')
- show_config_parser.add_argument('DN', help='The shared config entry
DN')
show_config_parser.set_defaults(func=dna_config_show)
del_config_parser = shared_config_subcommands.add_parser('delete',
help='Delete the shared config entry')
- del_config_parser.add_argument('DN', help='The shared config entry
DN')
del_config_parser.set_defaults(func=dna_config_del)
diff --git a/src/lib389/lib389/cli_conf/plugins/managedentries.py
b/src/lib389/lib389/cli_conf/plugins/managedentries.py
index 071df7b..99ffc94 100644
--- a/src/lib389/lib389/cli_conf/plugins/managedentries.py
+++ b/src/lib389/lib389/cli_conf/plugins/managedentries.py
@@ -12,7 +12,7 @@ from lib389.plugins import ManagedEntriesPlugin, MEPConfig, MEPConfigs,
MEPTempl
from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit,
generic_object_add
arg_to_attr = {
- 'config_area': 'nsslapd-pluginConfigArea'
+ 'config_area': 'nsslapd-pluginconfigarea'
}
arg_to_attr_config = {
@@ -23,9 +23,9 @@ arg_to_attr_config = {
}
arg_to_attr_template = {
- 'rdn_attr': 'mepRDNAttr',
- 'static_attr': 'mepStaticAttr',
- 'mapped_attr': 'mepMappedAttr'
+ 'rdn_attr': 'meprdnattr',
+ 'static_attr': 'mepstaticattr',
+ 'mapped_attr': 'mepmappedattr'
}
@@ -125,7 +125,9 @@ def mep_template_list(inst, basedn, log, args):
def mep_template_add(inst, basedn, log, args):
log = log.getChild('mep_template_add')
targetdn = args.DN
- generic_object_add(MEPTemplate, inst, log, args, arg_to_attr_config, dn=targetdn)
+ if not targetdn or not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ generic_object_add(MEPTemplate, inst, log, args, arg_to_attr_template, dn=targetdn)
log.info('Don\'t forget to assign the template to Managed Entry Plugin config
'
'attribute - managedTemplate')
@@ -133,16 +135,18 @@ def mep_template_add(inst, basedn, log, args):
def mep_template_edit(inst, basedn, log, args):
log = log.getChild('mep_template_edit')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
- generic_object_edit(template, log, args, arg_to_attr_config)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
+ generic_object_edit(template, log, args, arg_to_attr_template)
def mep_template_show(inst, basedn, log, args):
log = log.getChild('mep_template_show')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
if not template.exists():
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)
@@ -156,8 +160,9 @@ def mep_template_show(inst, basedn, log, args):
def mep_template_del(inst, basedn, log, args):
log = log.getChild('mep_template_del')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
template.delete()
log.info("Successfully deleted the %s", targetdn)
@@ -179,7 +184,7 @@ def _add_parser_args_template(parser):
parser.add_argument('--static-attr', help='Sets an attribute with a
defined value that must be added '
'to the automatically-generated entry
(mepStaticAttr)')
parser.add_argument('--mapped-attr', nargs='+',
- help='Sets an attribute in the Managed Entries template entry
which must exist '
+ help='Sets attributes in the Managed Entries template entry
which must exist '
'in the generated entry (mepMappedAttr)')
diff --git a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
index c5d0646..dc115e0 100644
--- a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
+++ b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
@@ -26,48 +26,99 @@ arg_to_attr_pam = {
}
+def _get_url_next_num(url_attrs):
+ existing_nums = list(map(lambda url: int(url.split('nsslapd-pluginarg')[1]),
+ [i for i, _ in url_attrs.items()]))
+ if len(existing_nums) > 0:
+ existing_nums.sort()
+ full_num_list = list(range(existing_nums[-1]+2))
+ if not full_num_list:
+ next_num_list = ["0"]
+ else:
+ next_num_list = list(filter(lambda x: x not in existing_nums,
full_num_list))
+ else:
+ next_num_list = ["0"]
+
+ return next_num_list[0]
+
+
+def _validate_url(url):
+ failed = False
+ if len(url.split(" ")) == 2:
+ link = url.split(" ")[0]
+ params = url.split(" ")[1]
+ else:
+ link = url
+ params = ""
+
+ if (":" not in link) or ("//" not in link) or ("/" not
in link) or (params and "," not in params):
+ failed = False
+
+ if ldap.dn.is_dn(link.split("/")[-1]):
+ raise ValueError("Subtree is an invalid DN")
+
+ if params and len(params.split(",")) != 6 and not all(map(str.isdigit,
params.split(","))):
+ failed = False
+
+ if failed:
+ raise ValueError("URL should be in one of the next formats (all parameters
after a space should be digits): "
+ "'ldap|ldaps://authDS/subtree
maxconns,maxops,timeout,ldver,connlifetime,startTLS' or "
+ "'ldap|ldaps://authDS/subtree'")
+ return url
+
+
def pta_list(inst, basedn, log, args):
log = log.getChild('pta_list')
plugin = PassThroughAuthenticationPlugin(inst)
- result = []
urls = plugin.get_urls()
if args.json:
- log.info(json.dumps({"type": "list", "items":
urls}))
+ log.info(json.dumps({"type": "list",
+ "items": [{"id": id, "url":
value} for id, value in urls.items()]}))
else:
if len(urls) > 0:
- for i in result:
- log.info(i)
+ for _, value in urls.items():
+ log.info(value)
else:
log.info("No Pass Through Auth URLs were found")
def pta_add(inst, basedn, log, args):
log = log.getChild('pta_add')
+ new_url_l = _validate_url(args.URL.lower())
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
- if args.URL.lower() in urls:
+ url_attrs = plugin.get_urls()
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in url_attrs.items()]))
+ next_num = _get_url_next_num(url_attrs)
+ if new_url_l in urls:
raise ldap.ALREADY_EXISTS("Entry %s already exists" % args.URL)
- plugin.add("nsslapd-pluginarg%s" % len(urls), args.URL)
+ plugin.add("nsslapd-pluginarg%s" % next_num, args.URL)
def pta_edit(inst, basedn, log, args):
log = log.getChild('pta_edit')
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
+ url_attrs = plugin.get_urls()
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in url_attrs.items()]))
+ next_num = _get_url_next_num(url_attrs)
+ _validate_url(args.NEW_URL.lower())
old_url_l = args.OLD_URL.lower()
+ import pdb; pdb.set_trace()
if old_url_l not in urls:
log.info("Entry %s doesn't exists. Adding a new value." %
args.OLD_URL)
- url_num = len(urls)
else:
- url_num = urls.index(old_url_l)
- plugin.remove("nsslapd-pluginarg%s" % url_num, old_url_l)
- plugin.add("nsslapd-pluginarg%s" % url_num, args.NEW_URL)
+ for attr, value in url_attrs:
+ if value.lower() == old_url_l:
+ plugin.remove(attr, old_url_l)
+ plugin.add("nsslapd-pluginarg%s" % next_num, args.NEW_URL)
def pta_del(inst, basedn, log, args):
log = log.getChild('pta_del')
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in plugin.get_urls().items()]))
old_url_l = args.URL.lower()
if old_url_l not in urls:
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.URL)
@@ -137,7 +188,7 @@ def _add_parser_args_pam(parser):
help='Specifies a suffix to exclude from PAM authentication
(pamExcludeSuffix)')
parser.add_argument('--include-suffix', nargs='+',
help='Sets a suffix to include for PAM authentication
(pamIncludeSuffix)')
- parser.add_argument('--missing-suffix', choices=['ERROR',
'ALLOW', 'IGNORE'],
+ parser.add_argument('--missing-suffix', choices=['ERROR',
'ALLOW', 'IGNORE', 'delete', ''],
help='Identifies how to handle missing include or exclude
suffixes (pamMissingSuffix)')
parser.add_argument('--filter',
help='Sets an LDAP filter to use to identify specific entries
within '
diff --git a/src/lib389/lib389/cli_conf/plugins/referint.py
b/src/lib389/lib389/cli_conf/plugins/referint.py
index 9482a14..01bd4f0 100644
--- a/src/lib389/lib389/cli_conf/plugins/referint.py
+++ b/src/lib389/lib389/cli_conf/plugins/referint.py
@@ -6,8 +6,9 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
-from lib389.plugins import ReferentialIntegrityPlugin
-from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit
+import ldap
+from lib389.plugins import ReferentialIntegrityPlugin, ReferentialIntegrityConfig
+from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit,
generic_object_add
arg_to_attr = {
'update_delay': 'referint-update-delay',
@@ -15,6 +16,8 @@ arg_to_attr = {
'entry_scope': 'nsslapd-pluginEntryScope',
'exclude_entry_scope': 'nsslapd-pluginExcludeEntryScope',
'container_scope': 'nsslapd-pluginContainerScope',
+ 'config_entry': 'nsslapd-pluginConfigArea',
+ 'log_file': 'referint-logfile'
}
@@ -24,6 +27,46 @@ def referint_edit(inst, basedn, log, args):
generic_object_edit(plugin, log, args, arg_to_attr)
+def referint_add_config(inst, basedn, log, args):
+ log = log.getChild('referint_add_config')
+ targetdn = args.DN
+ config = generic_object_add(ReferentialIntegrityConfig, inst, log, args, arg_to_attr,
dn=targetdn)
+ plugin = ReferentialIntegrityPlugin(inst)
+ plugin.replace('nsslapd-pluginConfigArea', config.dn)
+ import pdb; pdb.set_trace()
+ log.info('ReferentialIntegrity attribute nsslapd-pluginConfigArea (config-entry)
'
+ 'was set in the main plugin config')
+
+
+def referint_edit_config(inst, basedn, log, args):
+ log = log.getChild('referint_edit_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+ generic_object_edit(config, log, args, arg_to_attr)
+
+
+def referint_show_config(inst, basedn, log, args):
+ log = log.getChild('referint_show_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+
+ if not config.exists():
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)
+ if args and args.json:
+ o_str = config.get_all_attrs_json()
+ log.info(o_str)
+ else:
+ log.info(config.display())
+
+
+def referint_del_config(inst, basedn, log, args):
+ log = log.getChild('referint_del_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+ config.delete()
+ log.info("Successfully deleted the %s", targetdn)
+
+
def _add_parser_args(parser):
parser.add_argument('--update-delay',
help='Sets the update interval. Special values: 0 - The check
is performed immediately, '
@@ -40,6 +83,9 @@ def _add_parser_args(parser):
help='Specifies which branch the plug-in searches for the
groups to which the user belongs. '
'It only updates groups that are under the specified
container branch, '
'and leaves all other groups not updated
(nsslapd-pluginContainerScope)')
+ parser.add_argument('--log-file',
+ help='Specifies a path to the Referential integrity
logfile.'
+ 'For example:
/var/log/dirsrv/slapd-YOUR_INSTANCE/referint')
def create_parser(subparsers):
@@ -53,5 +99,21 @@ def create_parser(subparsers):
edit = subcommands.add_parser('set', help='Edit the plugin')
edit.set_defaults(func=referint_edit)
_add_parser_args(edit)
+ edit.add_argument('--config-entry', help='The value to set as
nsslapd-pluginConfigArea')
-
+ config = subcommands.add_parser('config-entry', help='Manage the config
entry')
+ config_subcommands = config.add_subparsers(help='action')
+ add_config = config_subcommands.add_parser('add', help='Add the config
entry')
+ add_config.set_defaults(func=referint_add_config)
+ add_config.add_argument('DN', help='The config entry full DN')
+ _add_parser_args(add_config)
+ edit_config = config_subcommands.add_parser('set', help='Edit the config
entry')
+ edit_config.set_defaults(func=referint_edit_config)
+ edit_config.add_argument('DN', help='The config entry full DN')
+ _add_parser_args(edit_config)
+ show_config = config_subcommands.add_parser('show', help='Display the
config entry')
+ show_config.set_defaults(func=referint_show_config)
+ show_config.add_argument('DN', help='The config entry full DN')
+ del_config_ = config_subcommands.add_parser('delete', help='Delete the
config entry')
+ del_config_.set_defaults(func=referint_del_config)
+ del_config_.add_argument('DN', help='The config entry full DN')
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index 53e3448..0691e4e 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -319,6 +319,35 @@ class MEPConfigs(DSLdapObjects):
basedn = "cn=managed entries,cn=plugins,cn=config"
self._basedn = basedn
+ def list(self):
+ """Get a list of children entries (DSLdapObject, Replica, etc.)
using a base DN
+ and objectClasses of our object (DSLdapObjects, Replicas, etc.)
+
+ :returns: A list of children entries
+ """
+
+ # Filter based on the objectclasses and the basedn
+ insts = None
+ # This will yield and & filter for objectClass with as many terms as needed.
+ filterstr = self._get_objectclass_filter()
+ self._log.debug('list filter = %s' % filterstr)
+ try:
+ results = self._instance.search_ext_s(
+ base=self._basedn,
+ scope=self._scope,
+ filterstr=filterstr,
+ attrlist=self._list_attrlist,
+ serverctrls=self._server_controls, clientctrls=self._client_controls,
+ escapehatch='i am sure'
+ )
+ # def __init__(self, instance, dn=None):
+ insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results]
+ except ldap.NO_SUCH_OBJECT:
+ # There are no objects to select from, se we return an empty array
+ insts = []
+ return insts
+
+
class MEPTemplate(DSLdapObject):
"""A single instance of MEP template entry
@@ -1276,10 +1305,10 @@ class PassThroughAuthenticationPlugin(Plugin):
"""
attr_dict = collections.OrderedDict(sorted(self.get_all_attrs().items()))
- result = []
+ result = {}
for attr, value in attr_dict.items():
if attr.startswith("nsslapd-pluginarg"):
- result.append(ensure_str(value[0]))
+ result[attr] = ensure_str(value[0])
return result
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.