From ba46b9a8204732ed2c1d9680f8ffefdb6a042a06 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: May 24 2019 17:11:29 +0000 Subject: 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!) --- 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 8468877..3225f5a 100644 --- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx +++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx @@ -80,9 +80,7 @@ class AccountPolicy extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "account-policy", "config-entry", @@ -91,11 +89,7 @@ class AccountPolicy extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "openModal", - "Fetch the Account Policy Plugin config entry", - cmd - ); + log_cmd("openModal", "Fetch the Account Policy Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -110,8 +104,12 @@ class AccountPolicy extends React.Component { altStateAttrName: configEntry["altstateattrname"] === undefined ? [] - : [{id: configEntry["altstateattrname"][0], - label: configEntry["altstateattrname"][0]}], + : [ + { + id: configEntry["altstateattrname"][0], + label: configEntry["altstateattrname"][0] + } + ], alwaysRecordLogin: !( configEntry["alwaysrecordlogin"] === undefined || configEntry["alwaysrecordlogin"][0] == "no" @@ -119,23 +117,39 @@ class AccountPolicy extends React.Component { alwaysRecordLoginAttr: configEntry["alwaysrecordloginattr"] === undefined ? [] - : [{id: configEntry["alwaysrecordloginattr"][0], - label: configEntry["alwaysrecordloginattr"][0]}], + : [ + { + id: configEntry["alwaysrecordloginattr"][0], + label: configEntry["alwaysrecordloginattr"][0] + } + ], limitAttrName: configEntry["limitattrname"] === undefined ? [] - : [{id: configEntry["limitattrname"][0], - label: configEntry["limitattrname"][0]}], + : [ + { + id: configEntry["limitattrname"][0], + label: configEntry["limitattrname"][0] + } + ], specAttrName: configEntry["specattrname"] === undefined ? [] - : [{id: configEntry["specattrname"][0], - label: configEntry["specattrname"][0]}], + : [ + { + id: configEntry["specattrname"][0], + label: configEntry["specattrname"][0] + } + ], stateAttrName: configEntry["stateattrname"] === undefined ? [] - : [{id: configEntry["stateattrname"][0], - label: configEntry["stateattrname"][0]}], + : [ + { + id: configEntry["stateattrname"][0], + label: configEntry["stateattrname"][0] + } + ] }); this.props.toggleLoadingHandler(); }) @@ -181,12 +195,12 @@ class AccountPolicy extends React.Component { action, configDN, "--always-record-login", - alwaysRecordLogin ? "yes" : "no", + alwaysRecordLogin ? "yes" : "no" ]; cmd = [...cmd, "--alt-state-attr"]; if (altStateAttrName.length != 0) { - cmd = [...cmd, altStateAttrName[0].id]; + cmd = [...cmd, altStateAttrName[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -195,7 +209,7 @@ class AccountPolicy extends React.Component { cmd = [...cmd, "--always-record-login-attr"]; if (alwaysRecordLoginAttr.length != 0) { - cmd = [...cmd, alwaysRecordLoginAttr[0].id]; + cmd = [...cmd, alwaysRecordLoginAttr[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -204,7 +218,7 @@ class AccountPolicy extends React.Component { cmd = [...cmd, "--limit-attr"]; if (limitAttrName.length != 0) { - cmd = [...cmd, limitAttrName[0].id]; + cmd = [...cmd, limitAttrName[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -213,7 +227,7 @@ class AccountPolicy extends React.Component { cmd = [...cmd, "--spec-attr"]; if (specAttrName.length != 0) { - cmd = [...cmd, specAttrName[0].id]; + cmd = [...cmd, specAttrName[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -222,7 +236,7 @@ class AccountPolicy extends React.Component { cmd = [...cmd, "--state-attr"]; if (stateAttrName.length != 0) { - cmd = [...cmd, stateAttrName[0].id]; + cmd = [...cmd, stateAttrName[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -251,9 +265,10 @@ class AccountPolicy 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(); @@ -274,11 +289,7 @@ class AccountPolicy extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "deleteConfig", - "Delete the Account Policy Plugin config entry", - cmd - ); + log_cmd("deleteConfig", "Delete the Account Policy Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -288,18 +299,17 @@ class AccountPolicy extends React.Component { console.info("deleteConfig", "Result", content); this.props.addNotification( "success", - `Config entry ${ - this.state.configDN - } was successfully deleted` + `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 - ${err}` + `Error during the config entry removal operation - ${errMsg.desc}` ); this.props.pluginListHandler(); this.closeModal(); @@ -329,9 +339,7 @@ class AccountPolicy extends React.Component { updateFields() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === "Account Policy Plugin" - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === "Account Policy Plugin"); this.setState({ configArea: @@ -368,10 +376,8 @@ class AccountPolicy extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get attributes - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`); }); } @@ -432,9 +438,7 @@ class AccountPolicy extends React.Component { @@ -476,10 +480,7 @@ class AccountPolicy extends React.Component { id="alwaysRecordLogin" checked={alwaysRecordLogin} title="Sets that every entry records its last login time (alwaysRecordLogin)" - onChange={ - this - .handleCheckboxChange - } + onChange={this.handleCheckboxChange} > Always Record Login @@ -576,10 +577,7 @@ class AccountPolicy extends React.Component { /> - + Limit Attribute @@ -619,18 +617,10 @@ class AccountPolicy extends React.Component { > Delete - - @@ -651,10 +641,7 @@ class AccountPolicy extends React.Component {
- + { let myObject = JSON.parse(content); this.setState({ - configRows: myObject.items.map( - item => JSON.parse(item).attrs - ) + configRows: myObject.items.map(item => JSON.parse(item).attrs) }); this.props.toggleLoadingHandler(); }) .fail(err => { if (err != 0) { - console.log("loadConfigs failed", err); + let errMsg = JSON.parse(err); + console.log("loadConfigs failed", errMsg.desc); } this.props.toggleLoadingHandler(); }); @@ -139,9 +138,7 @@ class AttributeUniqueness extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "attr-uniq", "show", @@ -149,11 +146,7 @@ class AttributeUniqueness extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "openModal", - "Fetch the Attribute Uniqueness Plugin config entry", - cmd - ); + log_cmd("openModal", "Fetch the Attribute Uniqueness Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -164,37 +157,36 @@ class AttributeUniqueness extends React.Component { this.setState({ configEntryModalShow: true, newEntry: false, - configName: - configEntry["cn"] === undefined - ? "" - : configEntry["cn"][0], + configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0], configEnabled: !( - configEntry["nsslapd-pluginenabled"] === - undefined || + configEntry["nsslapd-pluginenabled"] === undefined || configEntry["nsslapd-pluginenabled"][0] == "off" ), acrossAllSubtrees: !( - configEntry["uniqueness-across-all-subtrees"] === - undefined || - configEntry["uniqueness-across-all-subtrees"][0] == - "off" + 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]}], + : [ + { + id: configEntry["uniqueness-top-entry-oc"][0], + label: configEntry["uniqueness-top-entry-oc"][0] + } + ], subtreeEnriesOc: - configEntry["uniqueness-subtree-entries-oc"] === - undefined + configEntry["uniqueness-subtree-entries-oc"] === undefined ? [] - : [{id: configEntry["uniqueness-subtree-entries-oc"][0], - label: configEntry["uniqueness-subtree-entries-oc"][0]}] + : [ + { + id: configEntry["uniqueness-subtree-entries-oc"][0], + label: configEntry["uniqueness-subtree-entries-oc"][0] + } + ] }); - if ( - configEntry["uniqueness-attribute-name"] === undefined - ) { + if (configEntry["uniqueness-attribute-name"] === undefined) { this.setState({ attrNames: [] }); } else { for (let value of configEntry["uniqueness-attribute-name"]) { @@ -268,7 +260,7 @@ class AttributeUniqueness extends React.Component { cmd = [...cmd, "--attr-name"]; if (attrNames.length != 0) { for (let value of attrNames) { - cmd = [...cmd, value.id]; + cmd = [...cmd, value.label]; } } else if (action == "add") { cmd = [...cmd, ""]; @@ -281,7 +273,7 @@ class AttributeUniqueness extends React.Component { cmd = [...cmd, "--subtree"]; if (subtrees.length != 0) { for (let value of subtrees) { - cmd = [...cmd, value.id]; + cmd = [...cmd, value.label]; } } else if (action == "add") { cmd = [...cmd, ""]; @@ -292,7 +284,7 @@ class AttributeUniqueness extends React.Component { cmd = [...cmd, "--top-entry-oc"]; if (topEntryOc.length != 0) { - cmd = [...cmd, topEntryOc[0].id]; + cmd = [...cmd, topEntryOc[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -301,7 +293,7 @@ class AttributeUniqueness extends React.Component { cmd = [...cmd, "--subtree-entries-oc"]; if (subtreeEnriesOc.length != 0) { - cmd = [...cmd, subtreeEnriesOc[0].id]; + cmd = [...cmd, subtreeEnriesOc[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -330,9 +322,10 @@ class AttributeUniqueness 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.loadConfigs(); this.closeModal(); @@ -353,11 +346,7 @@ class AttributeUniqueness extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "deleteConfig", - "Delete the Attribute Uniqueness Plugin config entry", - cmd - ); + log_cmd("deleteConfig", "Delete the Attribute Uniqueness Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -374,9 +363,10 @@ class AttributeUniqueness 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.loadConfigs(); this.closeModal(); @@ -418,10 +408,8 @@ class AttributeUniqueness extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get attributes - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`); }); } @@ -451,10 +439,8 @@ class AttributeUniqueness extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get objectClasses - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get objectClasses - ${errMsg.desc}`); }); } @@ -487,8 +473,7 @@ class AttributeUniqueness extends React.Component { - {newEntry ? "Add" : "Edit"} Attribute Uniqueness - Plugin Config Entry + {newEntry ? "Add" : "Edit"} Attribute Uniqueness Plugin Config Entry @@ -497,7 +482,7 @@ class AttributeUniqueness extends React.Component { - + Config Name @@ -505,9 +490,7 @@ class AttributeUniqueness extends React.Component { @@ -632,10 +615,7 @@ class AttributeUniqueness extends React.Component { 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 - } + onChange={this.handleCheckboxChange} > Across All Subtrees @@ -660,9 +640,7 @@ class AttributeUniqueness extends React.Component { id="configEnabled" value={configEnabled} onChange={() => - this.handleSwitchChange( - configEnabled - ) + this.handleSwitchChange(configEnabled) } animate={false} /> @@ -682,9 +660,7 @@ class AttributeUniqueness extends React.Component { @@ -711,6 +687,7 @@ class AttributeUniqueness extends React.Component { deleteConfig={this.deleteConfig} /> + + {newDefinitionEntry ? "Add" : "Edit"} Auto Membership Plugin + Definition Entry + + + + + + + + + Definition Name + + + + + + {Object.entries(modalDefinitionFields).map( + ([id, content]) => ( + + + + {content.name} + + + + + + + ) + )} + + + + Grouping Attributes + + + + { + this.setState({ + groupingAttrMember: value + }); + }} + selected={groupingAttrMember} + options={attributes} + newSelectionPrefix="Set an attribute: " + placeholder="Type an attribute..." + /> + + : + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + Manage Auto Membership Plugin Regex Entry + + + + +
+ + + Regex Name + + + + + + + + + Exclusive Regex + + + + { + this.setState({ + regexExclusive: value + }); + }} + selected={regexExclusive} + options={[]} + newSelectionPrefix="Set an exclusive regex: " + placeholder="Type a regex..." + /> + + + + + + Inclusive Regex + + + + { + this.setState({ + regexInclusive: value + }); + }} + selected={regexInclusive} + options={[]} + newSelectionPrefix="Set an inclusive regex: " + placeholder="Type a regex..." + /> + + + + + + Target Group + + + + + + +
+ +
+
+ + + + +
+
+ > + + + + + + + ); } 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 (
+ +
+ + + + {newEntry ? "Add" : "Edit"} DNA Plugin Config Entry + + + + + +
+ + + Config Name + + + + + + + + Shared Config Entry + + + + + + + + + + + + Type + + + + { + this.setState({ + type: value + }); + }} + selected={type} + options={attributes} + newSelectionPrefix="Add a attribute: " + placeholder="Type an attribute..." + /> + + + {Object.entries(modalConfigFields).map(([id, content]) => ( + + + + {content.name} + + + + + + + ))} +
+ +
+
+ + + + +
+
+ +
+ + + List DNA Plugin Shared Config Entries + + + + + DNA Config: {configName} + + + Shared Config Base DN:{" "} + {sharedConfigEntry} + + + + + + + + + + + + +
+
+ +
+ + + Manage DNA Plugin Shared Config Entry + + + + +
+ + + Config Hostname + + + + + + + + Config Port + + + + + + {Object.entries(modalSharedConfigFields).map( + ([id, content]) => ( + + + + {content.name} + + + + + + + ) + )} +
+ +
+
+ + + + +
+
+ + + + + + + +
); diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx index a799e54..a36ac21 100644 --- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx +++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx @@ -76,15 +76,14 @@ class LinkedAttributes extends React.Component { .done(content => { let myObject = JSON.parse(content); this.setState({ - configRows: myObject.items.map( - item => JSON.parse(item).attrs - ) + 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", err); + console.log("loadConfigs failed", errMsg.desc); } this.props.toggleLoadingHandler(); }); @@ -113,9 +112,7 @@ class LinkedAttributes extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "linked-attr", "config", @@ -124,11 +121,7 @@ class LinkedAttributes extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "openModal", - "Fetch the Linked Attributes Plugin config entry", - cmd - ); + log_cmd("openModal", "Fetch the Linked Attributes Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -139,20 +132,25 @@ class LinkedAttributes extends React.Component { this.setState({ configEntryModalShow: true, newEntry: false, - configName: - configEntry["cn"] === undefined - ? "" - : configEntry["cn"][0], + configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0], linkType: configEntry["linktype"] === undefined ? [] - : [{id: configEntry["linktype"][0], - label: configEntry["linktype"][0]}], + : [ + { + id: configEntry["linktype"][0], + label: configEntry["linktype"][0] + } + ], managedType: configEntry["managedtype"] === undefined ? [] - : [{id: configEntry["managedtype"][0], - label: configEntry["managedtype"][0]}], + : [ + { + id: configEntry["managedtype"][0], + label: configEntry["managedtype"][0] + } + ], linkScope: configEntry["linkscope"] === undefined ? "" @@ -197,7 +195,7 @@ class LinkedAttributes extends React.Component { cmd = [...cmd, "--link-type"]; if (linkType.length != 0) { - cmd = [...cmd, linkType[0].id]; + cmd = [...cmd, linkType[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -206,7 +204,7 @@ class LinkedAttributes extends React.Component { cmd = [...cmd, "--managed-type"]; if (managedType.length != 0) { - cmd = [...cmd, managedType[0].id]; + cmd = [...cmd, managedType[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -235,9 +233,10 @@ class LinkedAttributes 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.loadConfigs(); this.closeModal(); @@ -259,11 +258,7 @@ class LinkedAttributes extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "deleteConfig", - "Delete the Linked Attributes Plugin config entry", - cmd - ); + log_cmd("deleteConfig", "Delete the Linked Attributes Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -280,9 +275,10 @@ class LinkedAttributes 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.loadConfigs(); this.closeModal(); @@ -324,10 +320,8 @@ class LinkedAttributes extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get attributes - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`); }); } @@ -356,8 +350,7 @@ class LinkedAttributes extends React.Component { - {newEntry ? "Add" : "Edit"} Linked Attributes - Plugin Config Entry + {newEntry ? "Add" : "Edit"} Linked Attributes Plugin Config Entry @@ -374,9 +367,7 @@ class LinkedAttributes extends React.Component { @@ -425,7 +416,7 @@ class LinkedAttributes extends React.Component {
- + Link Scope @@ -433,9 +424,7 @@ class LinkedAttributes extends React.Component { @@ -453,9 +442,7 @@ class LinkedAttributes extends React.Component { @@ -481,6 +468,7 @@ class LinkedAttributes extends React.Component { deleteConfig={this.deleteConfig} /> + + {newConfigEntry ? "Add" : "Edit"} Managed Entries Plugin Config + Entry + + + + + +
+ + + Config Name + + + + + + {Object.entries(modalConfigFields).map(([id, content]) => ( + + + + {content.name} + + + + + + + ))} + + + Managed Template + + + + + + + + +
+ +
+
+ + + + + + + +
+ + + Handle Managed Entries Plugin Template Entry + + + + +
+ + + + Template DN + + + + + + +
+ +
+ + +
+ + + RDN Attribute + + + { + this.setState({ + templateRDNAttr: value + }); + }} + selected={templateRDNAttr} + options={attributes} + newSelectionPrefix="Add a RDN attribute: " + placeholder="Type an attribute..." + /> + + + + + + Static Attribute + + + + { + this.setState({ + templateStaticAttr: value + }); + }} + selected={templateStaticAttr} + options={attributes} + newSelectionPrefix="Add a static attribute: " + placeholder="Type an attribute..." + /> + + + + + + Mapped Attributes + + + + { + this.setState({ + templateMappedAttr: value + }); + }} + selected={templateMappedAttr} + options={attributes} + newSelectionPrefix="Add a mapped attribute: " + placeholder="Type an attribute..." + /> + + +
+ +
+
+ + + + + + +
+
+ > + + + Shared Config Area + + + + + + + + + + ); } diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx index 68a0862..b816944 100644 --- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx +++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx @@ -91,9 +91,7 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "memberof", "fixup", @@ -122,9 +120,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({ @@ -155,9 +154,7 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "memberof", "config-entry", @@ -166,11 +163,7 @@ class MemberOf extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd( - "openMemberOfModal", - "Fetch the MemberOf Plugin config entry", - cmd - ); + log_cmd("openMemberOfModal", "Fetch the MemberOf Plugin config entry", cmd); cockpit .spawn(cmd, { superuser: true, @@ -185,8 +178,12 @@ class MemberOf extends React.Component { configAutoAddOC: configEntry["memberofautoaddoc"] === undefined ? [] - : [{id:configEntry["memberofautoaddoc"][0], - label: configEntry["memberofautoaddoc"][0]}], + : [ + { + id: configEntry["memberofautoaddoc"][0], + label: configEntry["memberofautoaddoc"][0] + } + ], configAllBackends: !( configEntry["memberofallbackends"] === undefined || configEntry["memberofallbackends"][0] == "off" @@ -196,8 +193,7 @@ class MemberOf extends React.Component { configEntry["memberofskipnested"][0] == "off" ), configConfigEntry: - configEntry["nsslapd-pluginConfigArea"] === - undefined + configEntry["nsslapd-pluginConfigArea"] === undefined ? "" : configEntry["nsslapd-pluginConfigArea"][0], configEntryScope: @@ -205,8 +201,7 @@ class MemberOf extends React.Component { ? "" : configEntry["memberofentryscope"][0], configEntryScopeExcludeSubtree: - configEntry["memberofentryscopeexcludesubtree"] === - undefined + configEntry["memberofentryscopeexcludesubtree"] === undefined ? "" : configEntry["memberofentryscopeexcludesubtree"][0] }); @@ -279,18 +274,14 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + - this.props.serverId + - ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "memberof", "config-entry", action, configDN, "--scope", - configEntryScope || action == "add" - ? configEntryScope - : "delete", + configEntryScope || action == "add" ? configEntryScope : "delete", "--exclude", configEntryScopeExcludeSubtree || action == "add" ? configEntryScopeExcludeSubtree @@ -303,7 +294,7 @@ class MemberOf extends React.Component { cmd = [...cmd, "--autoaddoc"]; if (configAutoAddOC.length != 0) { - cmd = [...cmd, configAutoAddOC[0].id]; + cmd = [...cmd, configAutoAddOC[0].label]; } else if (action == "add") { cmd = [...cmd, ""]; } else { @@ -325,11 +316,7 @@ class MemberOf extends React.Component { } this.props.toggleLoadingHandler(); - log_cmd( - "memberOfOperation", - `Do the ${action} operation on the MemberOf Plugin`, - cmd - ); + log_cmd("memberOfOperation", `Do the ${action} operation on the MemberOf Plugin`, cmd); cockpit .spawn(cmd, { superuser: true, @@ -346,9 +333,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(); @@ -380,18 +368,17 @@ class MemberOf extends React.Component { console.info("deleteConfig", "Result", content); this.props.addNotification( "success", - `Config entry ${ - this.state.configDN - } was successfully deleted` + `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 - ${err}` + `Error during the config entry removal operation - ${errMsg.desc}` ); this.props.pluginListHandler(); this.closeModal(); @@ -424,9 +411,7 @@ class MemberOf extends React.Component { let memberOfGroupAttrObjectList = []; if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === "MemberOf Plugin" - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === "MemberOf Plugin"); this.setState({ memberOfAutoAddOC: @@ -512,10 +497,8 @@ class MemberOf extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get objectClasses - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get objectClasses - ${errMsg.desc}`); }); } @@ -566,7 +549,7 @@ class MemberOf extends React.Component { specificPluginCMD = [...specificPluginCMD, "--autoaddoc"]; if (memberOfAutoAddOC.length != 0) { - specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].id]; + specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].label]; } else { specificPluginCMD = [...specificPluginCMD, "delete"]; } @@ -609,10 +592,7 @@ class MemberOf extends React.Component {
- + Base DN @@ -622,16 +602,11 @@ class MemberOf extends React.Component { - + Filter DN @@ -641,9 +616,7 @@ class MemberOf extends React.Component { @@ -676,9 +649,7 @@ class MemberOf extends React.Component { > - - Manage MemberOf Plugin Shared Config Entry - + Manage MemberOf Plugin Shared Config Entry @@ -694,9 +665,7 @@ class MemberOf extends React.Component { @@ -789,19 +758,14 @@ class MemberOf extends React.Component { All Backends @@ -823,22 +787,15 @@ class MemberOf extends React.Component { Skip Nested @@ -851,10 +808,7 @@ class MemberOf extends React.Component { - + Auto Add OC @@ -894,18 +848,10 @@ class MemberOf extends React.Component { > Delete - - @@ -1063,9 +1009,7 @@ class MemberOf extends React.Component { @@ -1120,10 +1064,7 @@ class MemberOf extends React.Component { - + { + 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 (
- +
+ + + + {newPAMConfigEntry ? "Add" : "Edit"} PAM Passthough Authentication + Plugin Config Entry + + + + + + + + + Config Name + + + + + + + + Exclude Suffix + + + { + this.setState({ + pamExcludeSuffix: values + }); + }} + selected={pamExcludeSuffix} + options={[""]} + newSelectionPrefix="Add a suffix: " + placeholder="Type a suffix DN..." + /> + + + + + Include Suffix + + + { + this.setState({ + pamIncludeSuffix: values + }); + }} + selected={pamIncludeSuffix} + options={[""]} + newSelectionPrefix="Add a suffix: " + placeholder="Type a suffix DN..." + /> + + + + + + ID Attribute + + + + { + this.setState({ + pamIDAttr: value + }); + }} + selected={pamIDAttr} + options={attributes} + newSelectionPrefix="Add an attribute: " + placeholder="Type an attribute..." + /> + + + + + Missing Suffix + + +
+ + ERROR + + + ALLOW + + + IGNORE + +
+ +
+ {Object.entries(modalPAMConfigFields).map( + ([id, content]) => ( + + + + {content.name} + + + + + + + ) + )} + + + + + Fallback + + + + + Secure + + + + + +
+
+ + + + +
+ + +
+ + + + {newPAMConfigEntry ? "Add" : "Edit"} + Passthough Authentication Plugin URL + + + + + +
+ + + Connection Type + + +
+ + ldap + + + ldaps + +
+ +
+ {Object.entries(modalURLFields).map(([id, content]) => ( + + + + {content.name} + + + + + + + ))} + + + + Version + + +
+ + LDAPv2 + + + LDAPv3 + +
+ +
+ + + + Enable StartTLS + + + + + + + Result URL + + + + + {urlConnType}://{urlAuthDS}/{urlSubtree}{" "} + {urlMaxConns},{urlMaxOps},{urlTimeout}, + {urlLDVer},{urlConnLifeTime}, + {urlStartTLS ? "1" : "0"} + + + +
+ +
+
+ + + + +
+
+ + > + + + + + + + + + + + + +
); } diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx index 64925ce..fe6b962 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: "" }; } @@ -64,9 +65,7 @@ class PluginBasicConfig extends React.Component { addNotification, toggleLoadingHandler } = this.props; - const new_status = this.state.currentPluginEnabled - ? "disable" - : "enable"; + const new_status = this.state.currentPluginEnabled ? "disable" : "enable"; const cmd = [ "dsconf", "-j", @@ -78,11 +77,7 @@ class PluginBasicConfig extends React.Component { toggleLoadingHandler(); this.setState({ disableSwitch: true }); - log_cmd( - "handleSwitchChange", - "Switch plugin states from the plugin tab", - cmd - ); + log_cmd("handleSwitchChange", "Switch plugin states from the plugin tab", cmd); cockpit .spawn(cmd, { superuser: true, err: "message" }) .done(content => { @@ -96,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 }); @@ -107,9 +103,7 @@ class PluginBasicConfig extends React.Component { updateFields() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === this.props.cn - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn); this.setState({ currentPluginType: pluginRow["nsslapd-pluginType"][0], @@ -118,8 +112,7 @@ class PluginBasicConfig extends React.Component { currentPluginId: pluginRow["nsslapd-pluginId"][0], currentPluginVendor: pluginRow["nsslapd-pluginVendor"][0], currentPluginVersion: pluginRow["nsslapd-pluginVersion"][0], - currentPluginDescription: - pluginRow["nsslapd-pluginDescription"][0], + currentPluginDescription: pluginRow["nsslapd-pluginDescription"][0], currentPluginDependsOnType: pluginRow["nsslapd-plugin-depends-on-type"] === undefined ? "" @@ -127,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(); @@ -135,9 +132,7 @@ class PluginBasicConfig extends React.Component { updateSwitch() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === this.props.cn - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn); var pluginEnabled; if (pluginRow["nsslapd-pluginEnabled"][0] === "on") { @@ -171,6 +166,7 @@ class PluginBasicConfig extends React.Component { currentPluginDescription, currentPluginDependsOnType, currentPluginDependsOnNamed, + currentPluginPrecedence, disableSwitch } = this.state; @@ -183,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 (
@@ -199,10 +196,7 @@ class PluginBasicConfig extends React.Component { {this.props.removeSwitch || ( - + Status @@ -212,9 +206,7 @@ class PluginBasicConfig extends React.Component { id="bsSize-example" value={currentPluginEnabled} onChange={() => - this.handleSwitchChange( - currentPluginEnabled - ) + this.handleSwitchChange(currentPluginEnabled) } animate={false} disabled={disableSwitch} @@ -229,35 +221,21 @@ class PluginBasicConfig extends React.Component {
- {Object.entries(modalFieldsCol1).map( - ([id, value]) => ( - - - {this.props.memberOfAttr} Plugin{" "} - {id.replace( - "currentPlugin", - "" - )} - - - - - - ) - )} + {Object.entries(modalFieldsCol1).map(([id, value]) => ( + + + {this.props.memberOfAttr} Plugin{" "} + {id.replace("currentPlugin", "")} + + + + + + ))} @@ -288,10 +263,7 @@ class PluginBasicConfig extends React.Component { @@ -300,35 +272,20 @@ class PluginBasicConfig extends React.Component { - {Object.entries(modalFieldsCol2).map( - ([id, value]) => ( - - - Plugin{" "} - {id.replace( - "currentPlugin", - "" - )} - - - - - - ) - )} + {Object.entries(modalFieldsCol2).map(([id, value]) => ( + + + Plugin {id.replace("currentPlugin", "")} + + + + + + ))}
@@ -350,8 +307,8 @@ class PluginBasicConfig extends React.Component { description: currentPluginDescription, dependsOnType: currentPluginDependsOnType, dependsOnNamed: currentPluginDependsOnNamed, - specificPluginCMD: this.props - .specificPluginCMD + 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} > - + Plugin Depends On Type @@ -116,8 +118,8 @@ class PluginEditModal extends React.Component { controlId="currentPluginDependsOnNamed" disabled={false} > - - Plugin Depends On Type + + Plugin Depends On Named @@ -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 index a9bc3f4..49abfa1 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx @@ -104,9 +104,7 @@ class PluginTable extends React.Component { + + Manage Referential Integrity Plugin Shared Config Entry + + + + + +
+ + + + Config DN + + + + + + + + + Update Delay + + + + + + + + Membership Attribute + + + { + this.setState({ + configMembershipAttr: value + }); + }} + selected={configMembershipAttr} + options={attributes} + newSelectionPrefix="Add a membership attribute: " + placeholder="Type an attribute..." + /> + + + + + Entry Scope + + + + + + + + Exclude Entry Scope + + + + + + + + Container Scope + + + + + + + + Logfile + + + + + +
+ +
+
+ + + + + + +
+
- + - + - + - + - + + + + Logfile + + + + + + + + Shared Config Entry + + + + + + + +
diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx index 560cd7f..237d9aa 100644 --- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx +++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx @@ -59,9 +59,7 @@ class RetroChangelog extends React.Component { updateFields() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === "Retro Changelog Plugin" - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === "Retro Changelog Plugin"); this.setState({ isReplicated: !( @@ -119,10 +117,8 @@ class RetroChangelog extends React.Component { }); }) .fail(err => { - this.props.addNotification( - "error", - `Failed to get attributes - ${err}` - ); + let errMsg = JSON.parse(err); + this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`); }); } @@ -146,7 +142,7 @@ class RetroChangelog extends React.Component { "--is-replicated", isReplicated ? "TRUE" : "FALSE", "--attribute", - attribute.length != 0 ? attribute[0].id : "delete", + attribute.length != 0 ? attribute[0].label : "delete", "--directory", directory || "delete", "--max-age", @@ -172,10 +168,7 @@ class RetroChangelog extends React.Component {
- + - + - + 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === "RootDN Access Control" - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === "RootDN Access Control"); this.setState({ openTime: pluginRow["rootdn-open-time"] === undefined @@ -76,10 +66,7 @@ class RootDNAccessControl extends React.Component { this.setState({ allowHost: [] }); } else { for (let value of pluginRow["rootdn-allow-host"]) { - allowHostList = [ - ...allowHostList, - { id: value, label: value } - ]; + allowHostList = [...allowHostList, { id: value, label: value }]; } this.setState({ allowHost: allowHostList }); } @@ -87,10 +74,7 @@ class RootDNAccessControl extends React.Component { this.setState({ denyHost: [] }); } else { for (let value of pluginRow["rootdn-deny-host"]) { - denyHostList = [ - ...denyHostList, - { id: value, label: value } - ]; + denyHostList = [...denyHostList, { id: value, label: value }]; } this.setState({ denyHost: denyHostList }); } @@ -190,14 +174,11 @@ class RootDNAccessControl extends React.Component { - + Allow Host @@ -221,7 +202,7 @@ class RootDNAccessControl extends React.Component { Deny Host @@ -245,7 +226,7 @@ class RootDNAccessControl extends React.Component { Allow IP address @@ -269,7 +250,7 @@ class RootDNAccessControl extends React.Component { Deny IP address @@ -293,7 +274,7 @@ class RootDNAccessControl extends React.Component { Open Time @@ -305,14 +286,11 @@ class RootDNAccessControl extends React.Component { /> - + Close Time @@ -324,14 +302,11 @@ class RootDNAccessControl extends React.Component { /> - + Days Allowed 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 (
+ +
+ + + Fixup MemberOf Task + + + + + + + + + Cleanup Suffix + + + + + + + + + + Cleanup Backend + + + + + + + + + + Cleanup Max USN + + + + + + + + + + + + + + +
+
+ > + + + + + USN Global + + + this.handleSwitchChange(globalMode)} + animate={false} + disabled={disableSwitch} + /> + + + + + + + + + +
); } diff --git a/src/cockpit/389-console/src/lib/plugins/winsync.jsx b/src/cockpit/389-console/src/lib/plugins/winsync.jsx index 37b38eb..39be7a9 100644 --- a/src/cockpit/389-console/src/lib/plugins/winsync.jsx +++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx @@ -1,13 +1,5 @@ import React from "react"; -import { - Row, - Col, - Form, - noop, - FormGroup, - Checkbox, - ControlLabel -} from "patternfly-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"; @@ -46,9 +38,7 @@ class WinSync extends React.Component { updateFields() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find( - row => row.cn[0] === "Posix Winsync API" - ); + const pluginRow = this.props.rows.find(row => row.cn[0] === "Posix Winsync API"); this.setState({ posixWinsyncCreateMemberOfTask: !( @@ -130,12 +120,10 @@ class WinSync extends React.Component { > Create MemberOf Task - + @@ -151,7 +139,7 @@ class WinSync extends React.Component { > Lower Case UID - + Map Member UID - + - + Map Nested Grouping - + @@ -206,10 +194,9 @@ class WinSync extends React.Component { 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 + Microsoft System Services for Unix 3.0 (msSFU30) schema - + { 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(); }) @@ -253,7 +258,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", @@ -280,11 +285,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( @@ -542,7 +544,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 a3a0d03..b74247d 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 4cdf531..d28ef2f 100644 --- a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py +++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py @@ -33,6 +33,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) @@ -43,6 +45,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) @@ -50,6 +54,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(): @@ -65,6 +71,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 c0481cd..3044231 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 599156d..a5f4320 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