This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.1 in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push: new f710e6a Issue 50604 - Fix UI validation f710e6a is described below
commit f710e6a526134598db47fbb502a117cecd4a9bd7 Author: Mark Reynolds mreynolds@redhat.com AuthorDate: Mon Sep 16 09:22:38 2019 -0400
Issue 50604 - Fix UI validation
Description:
This issue has been opened to track a series of bugzillas that were filed by our QE group during a massive UI testing day. Here are the issues being addressed in this issue:
- Replication agreement disappears from table after browser refresh - https://bugzilla.redhat.com/show_bug.cgi?id=1751128 - Fix log rotation time validation - https://bugzilla.redhat.com/show_bug.cgi?id=1751004 - Check backup/ldif name to see if it already exists - https://bugzilla.redhat.com/show_bug.cgi?id=1751007 - https://bugzilla.redhat.com/show_bug.cgi?id=1751009 - Root DN should not be editable - https://bugzilla.redhat.com/show_bug.cgi?id=1751011 - Backup should check if there is a database available - https://bugzilla.redhat.com/show_bug.cgi?id=1751019 - Also fixed backup duplicate timestamp issue - Fixed instance creation error handing - https://bugzilla.redhat.com/show_bug.cgi?id=1751026 - Fixed export/inout issues. Check for existing back or ldif - https://bugzilla.redhat.com/show_bug.cgi?id=1751019 - Validate SSL version min and max - https://bugzilla.redhat.com/show_bug.cgi?id=1751072 - Can not promte/demote replica - https://bugzilla.redhat.com/show_bug.cgi?id=1751145 - Database link creation and deletion issue - https://bugzilla.redhat.com/show_bug.cgi?id=1751157 - Agreement name validation during creation - https://bugzilla.redhat.com/show_bug.cgi?id=1751165 - Validate referral port - https://bugzilla.redhat.com/show_bug.cgi?id=1751173 - Fix deleteion of config attributes - https://bugzilla.redhat.com/show_bug.cgi?id=1751190
There was an overall improvement when creating suffixes/databases on how to initialize them
relates: https://pagure.io/389-ds-base/issue/50604
Reviewed by: spichugi(Thanks!)
(cherry picked from commit c403a39c8db68243524bd0cc50529167ac0d9fb2) --- src/cockpit/389-console/src/css/ds.css | 8 ++ src/cockpit/389-console/src/database.jsx | 104 +++++++++++++++---- src/cockpit/389-console/src/ds.js | 10 +- src/cockpit/389-console/src/index.html | 59 ++++++----- .../389-console/src/lib/database/backups.jsx | 78 ++++++++++++-- .../389-console/src/lib/database/databaseModal.jsx | 29 +++++- .../389-console/src/lib/database/referrals.jsx | 9 +- .../389-console/src/lib/database/suffix.jsx | 39 ++++++- .../389-console/src/lib/security/ciphers.jsx | 2 +- src/cockpit/389-console/src/lib/tools.jsx | 11 ++ src/cockpit/389-console/src/replication.js | 15 ++- src/cockpit/389-console/src/security.jsx | 58 ++++++++--- src/cockpit/389-console/src/servers.html | 22 ++-- src/cockpit/389-console/src/servers.js | 112 +++++++++++++++------ src/lib389/lib389/__init__.py | 7 +- src/lib389/lib389/_mapped_object.py | 2 +- src/lib389/lib389/chaining.py | 2 +- src/lib389/lib389/cli_conf/backend.py | 31 ++++++ src/lib389/lib389/cli_conf/security.py | 5 +- src/lib389/lib389/configurations/sample.py | 53 ++++++++++ src/lib389/lib389/replica.py | 8 +- 21 files changed, 535 insertions(+), 129 deletions(-)
diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css index 6da4b9d..f5b1e4f 100644 --- a/src/cockpit/389-console/src/css/ds.css +++ b/src/cockpit/389-console/src/css/ds.css @@ -76,6 +76,10 @@ font-size: 13px !important; }
+.ds-switch { + margin-top: 2px; +} + .ds-refresh:hover { color: DarkGray; background-color: white; @@ -741,6 +745,10 @@ option { width: 100%; }
+.ds-inst-indent { + margin-left: 240px; +} + .ds-left-margin { margin-left: 10px !important; } diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx index e5adf79..36b38be 100644 --- a/src/cockpit/389-console/src/database.jsx +++ b/src/cockpit/389-console/src/database.jsx @@ -13,9 +13,13 @@ import { Modal, Icon, Form, + Row, + Col, + ControlLabel, Button, noop, TreeView, + Radio, Spinner } from "patternfly-react"; import PropTypes from "prop-types"; @@ -41,7 +45,10 @@ export class Database extends React.Component { showSuffixModal: false, createSuffix: "", createBeName: "", - createRootNode: false, + createSuffixEntry: false, + createSampleEntries: false, + noSuffixInit: true, + // DB config globalDBConfig: {}, configUpdated: 0, @@ -67,6 +74,7 @@ export class Database extends React.Component { this.removeNotification = this.removeNotification.bind(this); this.addNotification = this.addNotification.bind(this); this.handleChange = this.handleChange.bind(this); + this.handleRadioChange = this.handleRadioChange.bind(this); this.loadGlobalConfig = this.loadGlobalConfig.bind(this); this.loadLDIFs = this.loadLDIFs.bind(this); this.loadBackups = this.loadBackups.bind(this); @@ -541,10 +549,32 @@ export class Database extends React.Component { showSuffixModal () { this.setState({ showSuffixModal: true, + createSuffixEntry: false, + createSampleEntries: false, + noSuffixInit: true, errObj: {}, }); }
+ handleRadioChange(e) { + // Handle the create suffix init option radio button group + let noInit = false; + let addSuffix = false; + let addSample = false; + if (e.target.id == "noSuffixInit") { + noInit = true; + } else if (e.target.id == "createSuffixEntry") { + addSuffix = true; + } else { // createSampleEntries + addSample = true; + } + this.setState({ + noSuffixInit: noInit, + createSuffixEntry: addSuffix, + createSampleEntries: addSample + }); + } + handleChange(e) { const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; let valueErr = false; @@ -570,7 +600,7 @@ export class Database extends React.Component { let errors = false; let missingArgs = { createSuffix: false, - createBeName: false + createBeName: false, };
if (this.state.createSuffix == "") { @@ -601,9 +631,12 @@ export class Database extends React.Component { "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "backend", "create", "--be-name", this.state.createBeName, '--suffix', this.state.createSuffix, ]; - if (this.state.createSampleEntries == true) { + if (this.state.createSampleEntries) { cmd.push('--create-entries'); } + if (this.state.createSuffixEntry) { + cmd.push('--create-suffix'); + }
log_cmd("createSuffix", "Create a new backend", cmd); cockpit @@ -616,6 +649,7 @@ export class Database extends React.Component { ); // Refresh tree this.loadSuffixTree(false); + this.loadSuffixList(); }) .fail(err => { let errMsg = JSON.parse(err); @@ -1133,7 +1167,11 @@ export class Database extends React.Component { showModal={this.state.showSuffixModal} closeHandler={this.closeSuffixModal} handleChange={this.handleChange} + handleRadioChange={this.handleRadioChange} saveHandler={this.createSuffix} + noInit={this.state.noSuffixInit} + addSuffix={this.state.createSuffixEntry} + addSample={this.state.createSampleEntries} error={this.state.errObj} /> </div> @@ -1147,7 +1185,11 @@ class CreateSuffixModal extends React.Component { showModal, closeHandler, handleChange, + handleRadioChange, saveHandler, + noInit, + addSuffix, + addSample, error } = this.props;
@@ -1169,20 +1211,40 @@ class CreateSuffixModal extends React.Component { </Modal.Header> <Modal.Body> <Form horizontal autoComplete="off"> - <div className="ds-inline"> - <div> - <label htmlFor="createSuffix" className="ds-config-label" title="Database Suffix DN (nsslapd-suffix)"> - Suffix DN</label><input onChange={handleChange} className={error.createSuffix ? "ds-input-bad" : "ds-input"} type="text" id="createSuffix" size="40" /> - </div> - <div> - <label htmlFor="createBeName" className="ds-config-label" title="Database backend name (nsslapd-backend)"> - Backend Name</label><input onChange={handleChange} className={error.createBeName ? "ds-input-bad" : "ds-input"} type="text" id="createBeName" size="40" /> - </div> - <div> - <p /> - <input type="checkbox" className="ds-config-checkbox" id="createSampleEntries" onChange={handleChange} /><label - htmlFor="createSampleEntries" className="ds-label" title="Create the datbase with sample entries"> Create Sample Entries</label> - </div> + <Row title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)"> + <Col sm={3}> + <ControlLabel>Suffix DN</ControlLabel> + </Col> + <Col sm={5}> + <input onChange={handleChange} className={error.createSuffix ? "ds-input-bad" : "ds-input"} type="text" id="createSuffix" size="40" /> + </Col> + </Row> + <p /> + <Row title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends."> + <Col sm={3}> + <ControlLabel>Database Name</ControlLabel> + </Col> + <Col sm={5}> + <input onChange={handleChange} className={error.createBeName ? "ds-input-bad" : "ds-input"} type="text" id="createBeName" size="40" /> + </Col> + </Row> + <hr /> + <div> + <Row className="ds-indent"> + <Radio name="radioGroup" id="noSuffixInit" onChange={handleRadioChange} checked={noInit} inline> + Do Not Initialize Database + </Radio> + </Row> + <Row className="ds-indent"> + <Radio name="radioGroup" id="createSuffixEntry" onChange={handleRadioChange} checked={addSuffix} inline> + Create The Top Suffix Entry + </Radio> + </Row> + <Row className="ds-indent"> + <Radio name="radioGroup" id="createSampleEntries" onChange={handleRadioChange} checked={addSample} inline> + Add Sample Entries + </Radio> + </Row> </div> </Form> </Modal.Body> @@ -1221,7 +1283,11 @@ CreateSuffixModal.propTypes = { showModal: PropTypes.bool, closeHandler: PropTypes.func, handleChange: PropTypes.func, + handleRadioChange: PropTypes.func, saveHandler: PropTypes.func, + noInit: PropTypes.bool, + addSuffix: PropTypes.bool, + addSample: PropTypes.bool, error: PropTypes.object, };
@@ -1229,6 +1295,10 @@ CreateSuffixModal.defaultProps = { showModal: false, closeHandler: noop, handleChange: noop, + handleRadioChange: noop, saveHandler: noop, + noInit: true, + addSuffix: false, + addSample: false, error: {}, }; diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js index 702ff88..1274b3f 100644 --- a/src/cockpit/389-console/src/ds.js +++ b/src/cockpit/389-console/src/ds.js @@ -76,6 +76,12 @@ function valid_dn (dn){ }
function valid_num (val){ + // Validate value is a number + let result = !isNaN(val); + return result; +} + +function valid_port (val){ // Validate value is a number and between 1 and 65535 let result = !isNaN(val); if (result) { @@ -366,6 +372,8 @@ function load_repl_suffix_dropdowns() { $("#" + repl_dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>'); } } + get_and_set_repl_agmts(); + get_and_set_repl_winsync_agmts(); if (obj['items'].length == 0){ // Disable create agmt buttons $("#create-agmt").prop("disabled", true); @@ -443,8 +451,6 @@ function load_config (refresh){
// Replication page get_and_set_repl_config(); - get_and_set_repl_agmts(); - get_and_set_repl_winsync_agmts(); get_and_set_cleanallruv(); update_progress();
diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html index 91993cc..3eef2a7 100644 --- a/src/cockpit/389-console/src/index.html +++ b/src/cockpit/389-console/src/index.html @@ -386,47 +386,56 @@ <p class="ds-modal-error"></p> <div class="ds-inline"> <div> - <label for="create-inst-serverid" class="ds-config-label" title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"> - Instance Name</label><input class="ds-input ds-inst-input" size="40" type="text" id="create-inst-serverid" placeholder="Your_Instance_Name" required /> + <label for="create-inst-serverid" class="ds-config-label ds-input-right" title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"> + Instance Name</label><input class="ds-input ds-inst-input ds-left-margin" size="40" type="text" id="create-inst-serverid" placeholder="Your_Instance_Name" required /> </div> <div> - <label for="create-inst-port" class="ds-config-label" title="The server port number"> - Port</label><input class="ds-input ds-inst-input" size="40" type="text" value="389" id="create-inst-port" required /> + <label for="create-inst-port" class="ds-config-label ds-input-right" title="The server port number"> + Port</label><input class="ds-input ds-inst-input ds-left-margin" size="40" type="text" value="389" id="create-inst-port" required /> </div> <div> - <label for="create-inst-secureport" class="ds-config-label" title="The secure port number for TLS connections"> - Secure Port</label><input class="ds-input ds-inst-input" size="40" type="text" value="636" id="create-inst-secureport" required /> + <label for="create-inst-secureport" class="ds-config-label ds-input-right" title="The secure port number for TLS connections"> + Secure Port</label><input class="ds-input ds-inst-input ds-left-margin" size="40" type="text" value="636" id="create-inst-secureport" required /> </div> - <div> - <label for="create-inst-rootdn" class="ds-config-label" title="The DN for the unrestricted user"> - Directory Manager DN</label><input class="ds-input ds-inst-input" size="40" autocomplete="username" value="cn=Directory Manager" type="text" id="create-inst-rootdn" required /> + <div class="ds-inst-indent"> + <input type="checkbox" class="ds-config-checkbox" id="create-inst-tls" checked><label + for="create-inst-tls" class="ds-label" title="Create a self-signed certificate database">Create Self-Signed TLS Certificate DB</label> </div> <div> - <label for="rootdn-pw" class="ds-config-label" title="Directory Manager password.">Directory Manager Password</label><input - class="ds-input ds-inst-input" size="40" type="password" autocomplete="new-password" placeholder="Enter password" id="rootdn-pw" name="name" required> + <label for="create-inst-rootdn" class="ds-config-label ds-input-right" title="The DN for the unrestricted user"> + Directory Manager DN</label><input class="ds-input ds-inst-input ds-left-margin" size="40" autocomplete="username" value="cn=Directory Manager" type="text" id="create-inst-rootdn" required /> </div> <div> - <label for="rootdn-pw-confirm" class="ds-config-label" title="Confirm password">Confirm Password</label><input - class="ds-input ds-inst-input" size="40" type="password" autocomplete="new-password" placeholder="Confirm password" id="rootdn-pw-confirm" name="name" required> + <label for="rootdn-pw" class="ds-config-label ds-input-right" title="Directory Manager password.">Directory Manager Password</label><input + class="ds-input ds-inst-input ds-left-margin" size="40" type="password" autocomplete="new-password" placeholder="Enter password" id="rootdn-pw" name="name" required> </div> - <hr> <div> - <label for="backend-suffix" class="ds-config-label" title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)">Database Suffix</label><input - class="ds-input ds-inst-input" size="40" placeholder="e.g. dc=example,dc=com" type="text" id="backend-suffix"> + <label for="rootdn-pw-confirm" class="ds-config-label ds-input-right" title="Confirm password">Confirm Password</label><input + class="ds-input ds-inst-input ds-left-margin" size="40" type="password" autocomplete="new-password" placeholder="Confirm password" id="rootdn-pw-confirm" name="name" required> </div> + <hr> + <h5 class="ds-center">Optional Database Settings</h5> <div> - <label for="backend-name" class="ds-config-label" title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed.">Database Name</label><input - class="ds-input ds-inst-input" placeholder="e.g. userRoot" size="40" type="text" id="backend-name"> + <label for="backend-suffix" class="ds-config-label ds-input-right" title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)">Database Suffix</label><input + class="ds-input ds-inst-input ds-left-margin" size="40" placeholder="e.g. dc=example,dc=com" type="text" id="backend-suffix"> </div> - <div> - <label for="create-sample-entries" class="ds-config-label" title="Create sample entries in the suffix">Create Sample Entries </label><input - type="checkbox" class="ds-input ds-config-checkbox" id="create-sample-entries"> + <label for="backend-name" class="ds-config-label ds-input-right" title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.">Database Name</label><input + class="ds-input ds-inst-input ds-left-margin" placeholder="e.g. userRoot" size="40" type="text" id="backend-name"> </div> - <hr> - <div> - <input type="checkbox" class="ds-config-checkbox" id="create-inst-tls" checked><label - for="create-inst-tls" class="ds-label" title="Create a self-signed certificate database">Create Self Signed Certificate DB</label> + <div class="ds-inst-indent ds-margin-top"> + <div> + <input type="radio" name="ds-radio-group" id="no-init" checked /><label class="ds-left-margin" for="no-init" + title="Do not initialize the backend database"> Do Not Initialize Database</label> + </div> + <div> + <input type="radio" name="ds-radio-group" id="create-suffix-entry" /><label class="ds-left-margin" for="create-suffix-entry" + title="Create the suffix entry with a basic READ ACI"> Create Suffix Entry</label> + </div> + <div> + <input type="radio" name="ds-radio-group" id="create-sample-entries" /><label class="ds-left-margin" for="create-sample-entries" + title="Create sample entries under the suffix"> Create Suffix Entry And Add Sample Entries</label> + </div> </div> <div id="create-inst-spinner" class="ds-center" hidden> <hr> diff --git a/src/cockpit/389-console/src/lib/database/backups.jsx b/src/cockpit/389-console/src/lib/database/backups.jsx index 96d6e97..f04c348 100644 --- a/src/cockpit/389-console/src/lib/database/backups.jsx +++ b/src/cockpit/389-console/src/lib/database/backups.jsx @@ -30,7 +30,8 @@ export class Backups extends React.Component { activeKey: 1, showConfirmBackupDelete: false, showConfirmBackup: false, - showConfirmRestore: false, + showConfirmRestoreReplace: false, + showConfirmLDIFReplace: false, showRestoreSpinningModal: false, showDelBackupSpinningModal: false, showBackupModal: false, @@ -40,6 +41,7 @@ export class Backups extends React.Component { // LDIF showConfirmLDIFDelete: false, showConfirmLDIFImport: false, + showConfirmRestore: false, showLDIFSpinningModal: false, showLDIFDeleteSpinningModal: false, showExportModal: false, @@ -68,6 +70,8 @@ export class Backups extends React.Component { this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this); this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this); this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this); + this.validateBackup = this.validateBackup.bind(this); + this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this); // LDIFS this.importLDIF = this.importLDIF.bind(this); this.deleteLDIF = this.deleteLDIF.bind(this); @@ -80,6 +84,8 @@ export class Backups extends React.Component { this.doExport = this.doExport.bind(this); this.showExportModal = this.showExportModal.bind(this); this.closeExportModal = this.closeExportModal.bind(this); + this.validateLDIF = this.validateLDIF.bind(this); + this.closeConfirmLDIFReplace = this.closeConfirmLDIFReplace.bind(this); }
showExportModal () { @@ -97,6 +103,12 @@ export class Backups extends React.Component { }); }
+ closeConfirmLDIFReplace () { + this.setState({ + showConfirmLDIFReplace: false + }); + } + showLDIFSpinningModal () { this.setState({ showLDIFSpinningModal: true @@ -233,6 +245,12 @@ export class Backups extends React.Component { }); }
+ closeConfirmRestoreReplace () { + this.setState({ + showConfirmRestoreReplace: false, + }); + } + importLDIF() { this.showLDIFSpinningModal();
@@ -288,12 +306,23 @@ export class Backups extends React.Component { }); }
+ validateBackup() { + for (let i = 0; i < this.props.backups.length; i++) { + if (this.state.backupName == this.props.backups[i]['name']) { + this.setState({ + showConfirmRestoreReplace: true + }); + return; + } + } + this.doBackup(); + } + doBackup () { let cmd = [ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "backup", "create" ]; - if (this.state.backupName != "") { if (bad_file_name(this.state.backupName)) { this.props.addNotification( @@ -332,8 +361,15 @@ export class Backups extends React.Component { }
restoreBackup () { - this.showRestoreSpinningModal(); + if (this.props.suffixes.length == 0) { + this.props.addNotification( + "error", + `There are no databases defined to restore` + ); + return; + }
+ this.showRestoreSpinningModal(); const cmd = [ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "backup", "restore", this.state.backupName @@ -405,6 +441,23 @@ export class Backups extends React.Component { }); }
+ validateLDIF() { + let ldifname = this.state.ldifName; + if (!ldifname.endsWith(".ldif")) { + // dsconf/dsctl adds ".ldif" if not set, so that's what we need to check + ldifname = ldifname + ".ldif"; + } + for (let i = 0; i < this.props.ldifs.length; i++) { + if (ldifname == this.props.ldifs[i]['name']) { + this.setState({ + showConfirmLDIFReplace: true + }); + return; + } + } + this.doExport(); + } + doExport() { let missingArgs = {ldifName: false}; if (this.state.ldifName == "") { @@ -525,7 +578,7 @@ export class Backups extends React.Component { showModal={this.state.showExportModal} closeHandler={this.closeExportModal} handleChange={this.handleChange} - saveHandler={this.doExport} + saveHandler={this.validateLDIF} spinning={this.state.exportSpinner} error={this.state.errObj} suffixes={this.props.suffixes} @@ -534,7 +587,7 @@ export class Backups extends React.Component { showModal={this.state.showBackupModal} closeHandler={this.closeBackupModal} handleChange={this.handleChange} - saveHandler={this.doBackup} + saveHandler={this.validateBackup} spinning={this.state.backupSpinning} error={this.state.errObj} /> @@ -590,7 +643,20 @@ export class Backups extends React.Component { msg="Are you sure you want to delete this backup?" msgContent={this.state.backupName} /> - + <ConfirmPopup + showModal={this.state.showConfirmRestoreReplace} + closeHandler={this.closeConfirmRestoreReplace} + actionFunc={this.doBackup} + msg="Replace Existing Backup" + msgContent="A backup already eixsts with the same name, do you want to replace it?" + /> + <ConfirmPopup + showModal={this.state.showConfirmLDIFReplace} + closeHandler={this.closeConfirmLDIFReplace} + actionFunc={this.doExport} + msg="Replace Existing LDIF File" + msgContent="A LDIF file already eixsts with the same name, do you want to replace it?" + /> </div> ); } diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx b/src/cockpit/389-console/src/lib/database/databaseModal.jsx index 01db9bb..092f22d 100644 --- a/src/cockpit/389-console/src/lib/database/databaseModal.jsx +++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx @@ -4,6 +4,7 @@ import { Row, Col, ControlLabel, + Radio, Icon, Button, Form, @@ -110,8 +111,12 @@ class CreateSubSuffixModal extends React.Component { showModal, closeHandler, handleChange, + handleRadioChange, saveHandler, suffix, + noInit, + addSuffix, + addSample, error } = this.props;
@@ -133,7 +138,7 @@ class CreateSubSuffixModal extends React.Component { </Modal.Header> <Modal.Body> <Form horizontal autoComplete="off"> - <Row title="Database Suffix DN (nsslapd-suffix)"> + <Row title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)"> <Col sm={3}> <ControlLabel>Sub-Suffix DN</ControlLabel> </Col> @@ -150,9 +155,9 @@ class CreateSubSuffixModal extends React.Component { </Col> </Row> <p /> - <Row title="Database backend name (nsslapd-backend)"> + <Row title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends."> <Col sm={3}> - <ControlLabel>Backend Name</ControlLabel> + <ControlLabel>Database Name</ControlLabel> </Col> <Col sm={9}> <FormControl @@ -163,6 +168,24 @@ class CreateSubSuffixModal extends React.Component { /> </Col> </Row> + <hr /> + <div> + <Row className="ds-indent"> + <Radio name="radioGroup" id="noSuffixInit" onChange={handleRadioChange} checked={noInit} inline> + Do Not Initialize Database + </Radio> + </Row> + <Row className="ds-indent"> + <Radio name="radioGroup" id="createSuffixEntry" onChange={handleRadioChange} checked={addSuffix} inline> + Create The Top Sub-Suffix Entry + </Radio> + </Row> + <Row className="ds-indent"> + <Radio name="radioGroup" id="createSampleEntries" onChange={handleRadioChange} checked={addSample} inline> + Add Sample Entries + </Radio> + </Row> + </div> </Form> </Modal.Body> <Modal.Footer> diff --git a/src/cockpit/389-console/src/lib/database/referrals.jsx b/src/cockpit/389-console/src/lib/database/referrals.jsx index 22f9c50..a44569b 100644 --- a/src/cockpit/389-console/src/lib/database/referrals.jsx +++ b/src/cockpit/389-console/src/lib/database/referrals.jsx @@ -12,7 +12,7 @@ import { Form, noop } from "patternfly-react"; -import { log_cmd } from "../tools.jsx"; +import { log_cmd, valid_port } from "../tools.jsx"; import PropTypes from "prop-types"; import "../../css/ds.css";
@@ -122,6 +122,13 @@ export class SuffixReferrals extends React.Component { ); missingArgs.refPort = true; errors = true; + } else if (!valid_port(this.state.refPort)) { + this.props.addNotification( + "error", + `Invalid port number, please use a number between 1 and 65535` + ); + missingArgs.refPort = true; + errors = true; } if (errors) { this.setState({ diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx b/src/cockpit/389-console/src/lib/database/suffix.jsx index 4366c19..8413799 100644 --- a/src/cockpit/389-console/src/lib/database/suffix.jsx +++ b/src/cockpit/389-console/src/lib/database/suffix.jsx @@ -79,6 +79,10 @@ export class Suffix extends React.Component { showSubSuffixModal: false, subSuffixValue: "", subSuffixBeName: "", + createSuffixEntry: false, + noSuffixInit: true, + createSampleEntries: false, + // Create Link showLinkModal: false, createLinkSuffix: "", @@ -100,6 +104,7 @@ export class Suffix extends React.Component { this.showImportModal = this.showImportModal.bind(this); this.closeImportModal = this.closeImportModal.bind(this); this.handleChange = this.handleChange.bind(this); + this.handleRadioChange = this.handleRadioChange.bind(this); this.doImport = this.doImport.bind(this); this.importLDIF = this.importLDIF.bind(this); this.showConfirmLDIFImport = this.showConfirmLDIFImport.bind(this); @@ -411,13 +416,20 @@ export class Suffix extends React.Component { }
// Create a new suffix - const cmd = [ + let cmd = [ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "backend", "create", "--be-name", this.state.subSuffixBeName, "--suffix=" + this.state.subSuffixValue + "," + this.props.suffix, "--parent-suffix=" + this.props.suffix ];
+ if (this.state.createSampleEntries) { + cmd.push('--create-entries'); + } + if (this.state.createSuffixEntry) { + cmd.push('--create-suffix'); + } + log_cmd("createSubSuffix", "Create a sub suffix", cmd); cockpit .spawn(cmd, { superuser: true, err: "message" }) @@ -539,7 +551,7 @@ export class Suffix extends React.Component { this.state.createLinkName ]; if (this.state.createUseStartTLS) { - cmd.push("--use-starttls"); + cmd.push("--use-starttls=on"); } log_cmd("createLink", "Create database link", cmd); cockpit @@ -588,6 +600,25 @@ export class Suffix extends React.Component { }, this.checkPasswords); }
+ handleRadioChange(e) { + // Handle the create suffix init option radio button group + let noInit = false; + let addSuffix = false; + let addSample = false; + if (e.target.id == "noSuffixInit") { + noInit = true; + } else if (e.target.id == "createSuffixEntry") { + addSuffix = true; + } else { // createSampleEntries + addSample = true; + } + this.setState({ + noSuffixInit: noInit, + createSuffixEntry: addSuffix, + createSampleEntries: addSample + }); + } + // // Delete suffix // @@ -847,8 +878,12 @@ export class Suffix extends React.Component { showModal={this.state.showSubSuffixModal} closeHandler={this.closeSubSuffixModal} handleChange={this.handleChange} + handleRadioChange={this.handleRadioChange} saveHandler={this.createSubSuffix} suffix={this.props.suffix} + noInit={this.state.noSuffixInit} + addSuffix={this.state.createSuffixEntry} + addSample={this.state.createSampleEntries} error={this.state.errObj} /> <ImportModal diff --git a/src/cockpit/389-console/src/lib/security/ciphers.jsx b/src/cockpit/389-console/src/lib/security/ciphers.jsx index 4714fcb..c07c0dc 100644 --- a/src/cockpit/389-console/src/lib/security/ciphers.jsx +++ b/src/cockpit/389-console/src/lib/security/ciphers.jsx @@ -89,7 +89,7 @@ export class Ciphers extends React.Component { .done(() => { this.props.addNotification( "success", - `Successfully set cipher preferences. You must restart the server for these changes to take effect.` + `Successfully set cipher preferences. You must restart the Directory Server for these changes to take effect.` ); this.setState({ saving: false, diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx index eb0a67c..dc8a701 100644 --- a/src/cockpit/389-console/src/lib/tools.jsx +++ b/src/cockpit/389-console/src/lib/tools.jsx @@ -111,3 +111,14 @@ export function bad_file_name(file_name) { } return false; } + +export function valid_port (val) { + // Validate value is a number and between 1 and 65535 + let result = !isNaN(val); + if (result) { + if (val < 1 || val > 65535) { + result = false; + } + } + return result; +} diff --git a/src/cockpit/389-console/src/replication.js b/src/cockpit/389-console/src/replication.js index e28d175..92566ad 100644 --- a/src/cockpit/389-console/src/replication.js +++ b/src/cockpit/389-console/src/replication.js @@ -271,7 +271,6 @@ function get_and_set_repl_agmts () { * Get the replication agreements for the selected suffix */ var suffix = $("#select-repl-agmt-suffix").val(); - if (suffix) { console.log("Loading replication agreements..."); var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'list', '--suffix=' + suffix ]; @@ -1133,6 +1132,13 @@ $(document).ready( function() { $("#nsds5replicabinddn").css("border-color", "initial"); cmd_args.push('--bind-dn=' + agmt_bind); } + if (agmt_name == "") { + $("#agmt-cn").css("border-color", "red"); + param_err = true; + } else { + $("#agmt-cn").css("border-color", "initial"); + cmd_args.push('"' + agmt_name + '"'); + } if (param_err ){ popup_msg("Error", "Missing required parameters"); return; @@ -1265,13 +1271,6 @@ $(document).ready( function() { if (agmt_init == "online-init") { init_replica = true; } - if ( agmt_name == "") { - $("#agmt-cn").css("border-color", "red"); - param_err = true; - } else { - $("#agmt-cn").css("border-color", "initial"); - cmd_args.push('"' + agmt_name + '"'); - }
// Create agreement in DS if ( editing ) { diff --git a/src/cockpit/389-console/src/security.jsx b/src/cockpit/389-console/src/security.jsx index 77b25f9..fd681e2 100644 --- a/src/cockpit/389-console/src/security.jsx +++ b/src/cockpit/389-console/src/security.jsx @@ -2,7 +2,7 @@ import cockpit from "cockpit"; import React from "react"; import Switch from "react-switch"; import { NotificationController, ConfirmPopup } from "./lib/notifications.jsx"; -import { log_cmd } from "./lib/tools.jsx"; +import { log_cmd, valid_port } from "./lib/tools.jsx"; import { Typeahead } from "react-bootstrap-typeahead"; import { CertificateManagement } from "./lib/security/certificateManagement.jsx"; import { SecurityEnableModal } from "./lib/security/securityModals.jsx"; @@ -18,6 +18,7 @@ import { ControlLabel, Button, Checkbox, + Icon, Spinner } from "patternfly-react"; import PropTypes from "prop-types"; @@ -475,6 +476,26 @@ export class Security extends React.Component { }
saveSecurityConfig () { + // Validate some setting first + let sslMin = this.state._sslVersionMin; + let sslMax = this.state._sslVersionMax; + if (this.state._sslVersionMin != this.state.sslVersionMin) { + sslMin = this.state.sslVersionMin; + } + if (this.state._sslVersionMax != this.state.sslVersionMax) { + sslMax = this.state.sslVersionMax; + } + + if (sslMin > sslMax) { + this.addNotification( + "error", + `The TLS minimum version but be less than or equal to the TLS maximum version` + ); + // Reset page + this.loadSecurityConfig(); + return; + } + let cmd = [ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket', 'security', 'set' @@ -493,6 +514,15 @@ export class Security extends React.Component { cmd.push("--tls-client-auth=" + this.state.clientAuth); } if (this.state._securePort != this.state.securePort) { + if (!valid_port(this.state.securePort)) { + this.addNotification( + "error", + `The Secure Port is invalid, it must be a number between 1 and 65535` + ); + // Reset page + this.loadSecurityConfig(); + return; + } cmd.push("--secure-port=" + this.state.securePort); } if (this.state._secureListenhost != this.state.secureListenhost) { @@ -522,7 +552,7 @@ export class Security extends React.Component {
if (cmd.length > 5) { log_cmd("saveSecurityConfig", "Applying security config change", cmd); - let msg = "Successfully updated security configuration. You must restart the server for these changes to take effect."; + let msg = "Successfully updated security configuration. You must restart the Directory Server for these changes to take effect.";
this.setState({ // Start the spinner @@ -592,7 +622,6 @@ export class Security extends React.Component { render() { let securityPage = ""; let serverCert = [this.state.nssslpersonalityssl]; - if (this.state.loaded && !this.state.saving) { let configPage = ""; if (this.state.securityEnabled) { @@ -603,7 +632,7 @@ export class Security extends React.Component { Server Secure Port </Col> <Col sm={4}> - <input id="securePort" className="ds-input-auto" onChange={this.handleChange} type="text" defaultValue={this.state.securePort} /> + <input id="securePort" className="ds-input-auto" onChange={this.handleChange} type="text" value={this.state.securePort} /> </Col> </Row> <Row className="ds-margin-top" title="This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address). This parameter specifically sets what interface to use for TLS traffic. Requires restart. (nsslapd-securelistenhost)."> @@ -611,7 +640,7 @@ export class Security extends React.Component { Secure Listen Host </Col> <Col sm={4}> - <input id="secureListenhost" className="ds-input-auto" type="text" onChange={this.handleChange} defaultValue={this.state.secureListenhost} /> + <input id="secureListenhost" className="ds-input-auto" type="text" onChange={this.handleChange} value={this.state.secureListenhost} /> </Col> </Row> <Row className="ds-margin-top" title="The name, or nickname, of the server certificate inthe NSS datgabase the server should use (nsSSLPersonalitySSL)."> @@ -635,8 +664,7 @@ export class Security extends React.Component { Minimum TLS Version </Col> <Col sm={4}> - <select id="sslVersionMin" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.sslVersionMin}> - <option /> + <select id="sslVersionMin" className="btn btn-default dropdown ds-select" onChange={this.handleChange} value={this.state.sslVersionMin}> <option>TLS1.3</option> <option>TLS1.2</option> <option>TLS1.1</option> @@ -650,8 +678,7 @@ export class Security extends React.Component { Maximum TLS Version </Col> <Col sm={4}> - <select id="sslVersionMax" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.sslVersionMax}> - <option /> + <select id="sslVersionMax" className="btn btn-default dropdown ds-select" onChange={this.handleChange} value={this.state.sslVersionMax}> <option>TLS1.3</option> <option>TLS1.2</option> <option>TLS1.1</option> @@ -665,7 +692,7 @@ export class Security extends React.Component { Client Authentication </Col> <Col sm={4}> - <select id="clientAuth" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.clientAuth}> + <select id="clientAuth" className="btn btn-default dropdown ds-select" onChange={this.handleChange} value={this.state.clientAuth}> <option>off</option> <option>allowed</option> <option>required</option> @@ -677,7 +704,7 @@ export class Security extends React.Component { Validate Certificate </Col> <Col sm={4}> - <select id="validateCert" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.validateCert}> + <select id="validateCert" className="btn btn-default dropdown ds-select" onChange={this.handleChange} value={this.state.validateCert}> <option>warn</option> <option>on</option> <option>off</option> @@ -761,13 +788,20 @@ export class Security extends React.Component { <Col componentClass={ControlLabel} sm={2}> Security Enabled </Col> - <Col sm={2}> + <Col sm={1}> <Switch + className="ds-switch" onChange={this.handleSwitchChange} checked={this.state.securityEnabled} height={20} /> </Col> + <Col> + <Icon className="ds-left-margin ds-refresh" + type="fa" name="refresh" title="Refresh security settings" + onClick={this.loadSecurityConfig} + /> + </Col> </Row> <hr /> {configPage} diff --git a/src/cockpit/389-console/src/servers.html b/src/cockpit/389-console/src/servers.html index 04678e8..1d17872 100644 --- a/src/cockpit/389-console/src/servers.html +++ b/src/cockpit/389-console/src/servers.html @@ -136,15 +136,15 @@ <form> <div> <label for="nsslapd-rootdn" class="ds-config-label" title="The DN of the unrestricted directory manager (nsslapd-rootdn).">Directory Manager DN</label><input - class="ds-input" type="text" autocomplete="username" id="nsslapd-rootdn" placeholder="cn=directory manager" value="cn=Directory Manager" size="40"/> + class="ds-input" type="text" readonly id="nsslapd-rootdn" value="cn=Directory Manager" size="40"/> </div> <div> <label for="nsslapd-rootpw" class="ds-config-label" title="The Directory Manager password (nsslapd-rootpw).">Directory Manager Password</label><input - class="ds-input" type="password" autocomplete="new-password" id="nsslapd-rootpw" size="40"/> + class="ds-input" type="password" id="nsslapd-rootpw" size="40"/> </div> <div> <label for="nsslapd-rootpw-confirm" class="ds-config-label" title="Confirm directory manager password.">Confirm Password</label><input - class="ds-input" type="password" autocomplete="new-password" id="nsslapd-rootpw-confirm" size="40"/> + class="ds-input" type="password" id="nsslapd-rootpw-confirm" size="40"/> </div> <div> <label for="nsslapd-rootpwstoragescheme" class="ds-config-label" title="Set the Directory Manager password storage scheme (nsslapd-rootpwstoragescheme).">Password Storage Scheme</label><select @@ -525,8 +525,8 @@ <option>day</option> <option>week</option> <option>month</option> - </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-accesslog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0" - title="Minute" id="nsslapd-accesslog-logrotationsyncminute" size="1"/> + </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-accesslog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0" + title="Minute" id="nsslapd-accesslog-logrotationsyncmin" size="1"/> </div> </div> <p></p> @@ -615,7 +615,7 @@ <option>week</option> <option>month</option> </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-auditlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0" - title="Minute" id="nsslapd-auditlog-logrotationsyncminute" size="1"/> + title="Minute" id="nsslapd-auditlog-logrotationsyncmin" size="1"/> </div> </div> <p></p> @@ -674,7 +674,7 @@ <option>week</option> <option>month</option> </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-auditfaillog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0" - title="Minute" id="nsslapd-auditfaillog-logrotationsyncminute" size="1"/> + title="Minute" id="nsslapd-auditfaillog-logrotationsyncmin" size="1"/> </div> </div> <h4 class="ds-sub-header">Deletion Policy</h4> @@ -731,7 +731,7 @@ <option>week</option> <option>month</option> </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-errorlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0" - title="Minute" id="nsslapd-errorlog-logrotationsyncminute" size="1"/> + title="Minute" id="nsslapd-errorlog-logrotationsyncmin" size="1"/> </div> </div>
@@ -950,14 +950,14 @@ <h3 class="ds-config-header">LDAPI & Autobind Settings</h3> <div class="ldapi-attrs ds-inline" hidden> <div> - <label for="nsslapd-ldapifilepath" class="ds-config-label" title="The Unix socket file (nsslapd-ldapifilepath).">LDAPI Socket File Path</label><input + <label for="nsslapd-ldapifilepath" class="ds-config-label" title="The Unix socket file (nsslapd-ldapifilepath). The UI requires this exact path so it is a read-only setting.">LDAPI Socket File Path</label><input class="ds-input" type="text" id="nsslapd-ldapifilepath" size="35" readonly/> </div> <div class="ds-inline"> <div class="autobind-attrs"> <div> - <label for="nsslapd-ldapimaprootdn" class="ds-config-label" title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn).">DN to map "root" To</label><input - class="ds-input" type="text" id="nsslapd-ldapimaprootdn" placeholder="e.g. cn=Directory Manager" size="35"/> + <label for="nsslapd-ldapimaprootdn" class="ds-config-label" title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn). The UI requires this to be set to the current root DN so it is a read-only setting">DN to map "root" To</label><input + class="ds-input" type="text" id="nsslapd-ldapimaprootdn" readonly size="35"/> </div> <div> <p></p> diff --git a/src/cockpit/389-console/src/servers.js b/src/cockpit/389-console/src/servers.js index b2a4b0f..7e34a5a 100644 --- a/src/cockpit/389-console/src/servers.js +++ b/src/cockpit/389-console/src/servers.js @@ -211,7 +211,7 @@ function get_and_set_config () { config_loaded = 1; check_inst_alive(); }).fail(function(data) { - popup_err("Error", "Failed to set config\n" + data.message); + popup_err("Error", "Failed to get config\n" + data.message); check_inst_alive(1); }); } @@ -311,34 +311,56 @@ function get_and_set_sasl () { }
function apply_mods(mods) { - var mod = mods.pop(); + let mod = mods.pop();
- if (!mod){ - popup_success("Successfully updated configuration"); - return; /* all done*/ + if (!mod) { + return 0; /* all done*/ } - var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','config', 'replace']; + let cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'replace']; cmd.push(mod.attr + "=" + mod.val); cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).then(function() { config_values[mod.attr] = mod.val; // Continue with next mods (if any)) apply_mods(mods); - }, function(ex) { - popup_err("Failed to update attribute: " + mod.attr, ex.message); + }, function(ex, data) { + popup_err("Failed to update attribute: " + mod.attr, data); // Reset HTML for remaining values that have not been processed $("#" + mod.attr).val(config_values[mod.attr]); for (remaining in mods) { $("#" + remaining.attr).val(config_values[remaining.attr]); } check_inst_alive(0); - return; // Stop on error + return -1; // Stop on error + }); +} + +function delete_mods(mods) { + let mod = mods.pop(); + + if (!mod) { + return 0; /* all done*/ + } + var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'delete', mod.attr]; + cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).then(function() { + config_values[mod.attr] = ""; + // Continue with next mods (if any)) + delete_mods(mods); + }, function(ex, data) { + popup_err("Failed to delete attribute: " + mod.attr, data); + // Reset HTML for remaining values that have not been processed + $("#" + mod.attr).val(config_values[mod.attr]); + for (remaining in mods) { + $("#" + remaining.attr).val(config_values[remaining.attr]); + } + check_inst_alive(0); + return -1; // Stop on error }); }
function save_config() { // Loop over current config_values check for differences - var mod_list = []; - + let mod_list = []; + let del_list = []; for (var attr in config_values) { var mod = {}; if ( $("#" + attr).is(':checkbox')) { @@ -360,7 +382,6 @@ function save_config() { } else { // Normal input var val = $("#" + attr).val(); - // But first check for rootdn-pw changes and check confirm input matches if (attr == "nsslapd-rootpw") { if (val != config_values[attr] || val != $("#nsslapd-rootpw-confirm").val()) { @@ -379,16 +400,34 @@ function save_config() { }
if (attr == "nsslapd-port") { - if (!valid_num(config_values[attr])) { + if (!valid_port(val)) { popup_msg("Port number is not valid"); $("#nsslapd-port").val(config_values[attr]); } }
- if ( val && val != config_values[attr]) { + if (attr.indexOf("logrotationsynchour") != -1) { + if (!valid_num(val) || val < 0 || val > 23) { + popup_msg("Invalid value", "You must use a number between 0 - 23 for: " + attr); + $("#" + attr).val(config_values[attr]) + return; + } + } + if (attr.indexOf("logrotationsyncmin") != -1) { + if (!valid_num(val) || val < 0 || val > 59){ + popup_msg("Invalid value", "You must use a number between 0 - 59 for: " + attr); + $("#" + attr).val(config_values[attr]) + return; + } + } + + if (val && val != config_values[attr]) { mod['attr'] = attr; mod['val'] = val; mod_list.push(mod); + } else if (val == "" && val != config_values[attr]) { + mod['attr'] = attr; + del_list.push(mod); } } } @@ -434,8 +473,19 @@ function save_config() { }
// Build dsconf commands to apply all the mods - if (mod_list.length) { - apply_mods(mod_list); + if (mod_list.length || del_list.length) { + let err = 0; + if (mod_list.length) { + if (apply_mods(mod_list) == -1) { + return; + } + } + if (del_list.length) { + if (delete_mods(del_list) == -1) { + return; + } + } + popup_success("Successfully updated configuration"); } else { // No changes to save, log msg? popup_msg() } @@ -1500,7 +1550,7 @@ $(document).ready( function() { report_err($("#create-inst-port"), 'You must provide a port number'); $("#create-inst-port").css("border-color", "red"); return; - } else if (!valid_num(server_port)) { + } else if (!valid_port(server_port)) { report_err($("#create-inst-port"), 'Port must be a number between 1 and 65534!'); $("#create-inst-port").css("border-color", "red"); return; @@ -1514,7 +1564,7 @@ $(document).ready( function() { report_err($("#create-inst-secureport"), 'You must provide a secure port number'); $("#create-inst-secureport").css("border-color", "red"); return; - } else if (!valid_num(secure_port)) { + } else if (!valid_port(secure_port)) { report_err($("#create-inst-secureport"), 'Secure port must be a number!'); $("#create-inst-secureport").css("border-color", "red"); return; @@ -1584,8 +1634,8 @@ $(document).ready( function() { } if ( $("#create-sample-entries").is(":checked") ) { setup_inf += '\nsample_entries = yes\n'; - } else { - setup_inf += '\nsample_entries = no\n'; + } else if ( $("#create-suffix-entry").is(":checked") ) { + setup_inf += '\ncreate_suffix_entry = yes\n'; } }
@@ -1599,9 +1649,9 @@ $(document).ready( function() { * [5] Create the instance * [6] Remove setup file */ - cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex) { + cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex, data) { // Failed to get FQDN - popup_err("Failed to get hostname!", ex.message); + popup_err("Failed to get hostname!", data); }).done(function (data){ /* * We have FQDN, so set the hostname in inf file, and create the setup file @@ -1610,38 +1660,38 @@ $(document).ready( function() { var setup_file = "/tmp/389-setup-" + (new Date).getTime() + ".inf"; var rm_cmd = ['rm', setup_file]; var create_file_cmd = ['touch', setup_file]; - cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex) { + cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) { // Failed to create setup file - popup_err("Failed to create installation file!", ex.message); + popup_err("Failed to create installation file!", data); }).done(function (){ /* * We have our new setup file, now set permissions on that setup file before we add sensitive data */ var chmod_cmd = ['chmod', '600', setup_file]; - cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex) { + cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) { // Failed to set permissions on setup file cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password $("#create-inst-spinner").hide(); - popup_err("Failed to set permission on setup file " + setup_file + ": ", ex.message); + popup_err("Failed to set permission on setup file " + setup_file + ": ", data); }).done(function (){ /* * Success we have our setup file and it has the correct permissions. * Now populate the setup file... */ - var cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file]; - cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex) { + let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file]; + cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex, data) { // Failed to populate setup file - popup_err("Failed to populate installation file!", ex.message); + popup_err("Failed to populate installation file!", data); }).done(function (){ /* * Next, create the instance... */ cmd = [DSCREATE, 'from-file', setup_file]; - cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV] }).fail(function(ex) { + cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV] }).fail(function(ex, data) { // Failed to create the new instance! cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password $("#create-inst-spinner").hide(); - popup_err("Failed to create instance!", ex.message); + popup_err("Failed to create instance!", data); }).done(function (){ // Success!!! Now cleanup everything up... cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py index 8e6eb66..0ff4335 100644 --- a/src/lib389/lib389/__init__.py +++ b/src/lib389/lib389/__init__.py @@ -2940,7 +2940,7 @@ class DirSrv(SimpleLDAPObject, object): json_result = {'type': 'list', 'items': []} for backup in dirlist: bak = bakdir + "/" + backup - bak_date = os.path.getctime(bak) + bak_date = os.path.getmtime(bak) bak_date = datetime.fromtimestamp(bak_date).strftime('%Y-%m-%d %H:%M:%S') bak_size = subprocess.check_output(['du', '-sh', bak]).split()[0].decode('utf-8') if use_json: @@ -2980,12 +2980,13 @@ class DirSrv(SimpleLDAPObject, object): json_result = {'type': 'list', 'items': []} for ldif in dirlist: fullpath = ldifdir + "/" + ldif - ldif_date = os.path.getctime(fullpath) + ldif_date = os.path.getmtime(fullpath) ldif_date = datetime.fromtimestamp(ldif_date).strftime('%Y-%m-%d %H:%M:%S') ldif_size = subprocess.check_output(['du', '-sh', fullpath]).split()[0].decode('utf-8') ldif_suffix = self.getLDIFSuffix(fullpath) if ldif_suffix == "": - ldif_suffix = "???" + # This is not a valid LDIF file + ldif_suffix = "Invalid LDIF" if use_json: json_item = [ldif, ldif_date, ldif_size, ldif_suffix] json_result['items'].append(json_item) diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 36ddc2e..010d964 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -282,7 +282,7 @@ class DSLdapObject(DSLogging):
mods = [] for arg in args: - if isinstance(arg[1], list): + if isinstance(arg[1], list) or isinstance(arg[1], tuple): value = ensure_list_bytes(arg[1]) else: value = [ensure_bytes(arg[1])] diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py index a25bbb6..7a0401e 100644 --- a/src/lib389/lib389/chaining.py +++ b/src/lib389/lib389/chaining.py @@ -124,7 +124,7 @@ class ChainingLink(DSLdapObject): pass
# Delete the monitoring entry - monitor = self.get_monitor(rdn) + monitor = self.get_monitor() monitor.delete()
# Delete the link diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py index 36e32ec..0b55ba4 100644 --- a/src/lib389/lib389/cli_conf/backend.py +++ b/src/lib389/lib389/cli_conf/backend.py @@ -8,6 +8,12 @@ # --- END COPYRIGHT BLOCK ---
from lib389.backend import Backend, Backends, DatabaseConfig +from lib389.configurations.sample import ( + create_base_domain, + create_base_org, + create_base_orgunit, + create_base_cn, + ) from lib389.chaining import (ChainingLinks) from lib389.index import Index, VLVIndex, VLVSearches from lib389.monitor import MonitorLDBM @@ -172,6 +178,29 @@ def backend_create(inst, basedn, log, args):
be = Backend(inst) be.create(properties=props) + if args.create_suffix and not args.create_entries: + # Set basic ACIs (taken from instance/setup.py) + o_aci = '(targetattr="o || description || objectClass")(targetfilter="(objectClass=organization)")(version 3.0; acl "Enable anyone o read"; allow (read, search, compare)(userdn="ldap:///anyone");)' + dc_aci = '(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)', + ou_aci = '(targetattr="ou || description || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)' + cn_aci = '(targetattr="cn || description || objectClass")(targetfilter="(objectClass=nscontainer)")(version 3.0; acl "Enable anyone cn read"; allow (read, search, compare)(userdn="ldap:///anyone");)' + suffix_rdn_attr = args.suffix.split('=')[0].lower() + if suffix_rdn_attr == 'dc': + domain = create_base_domain(inst, args.suffix) + domain.add('aci', dc-aci) + elif suffix_rdn_attr == 'o': + org = create_base_org(inst, args.suffix) + org.add('aci', o_aci) + elif suffix_rdn_attr == 'ou': + orgunit = create_base_orgunit(inst, args.suffix) + orgunit.add('aci', ou_aci) + elif suffix_rdn_attr == 'cn': + cn = create_base_cn(inst, args.suffix) + cn.add('aci', cn_aci) + else: + # Unsupported rdn + raise ValueError("Suffix RDN is not supported for creating suffix object. Only 'dc', 'o', 'ou', and 'cn' are supported.") + print("The database was sucessfully created")
@@ -1052,6 +1081,8 @@ def create_parser(subparsers): create_parser.add_argument('--suffix', required=True, help='The database suffix DN, for example "dc=example,dc=com"') create_parser.add_argument('--be-name', required=True, help='The database backend name, for example "userroot"') create_parser.add_argument('--create-entries', action='store_true', help='Create sample entries in the database') + create_parser.add_argument('--create-suffix', action='store_true', + help="Create the suffix object entry in the database. Only suffixes using the attributes 'dc', 'o', 'ou', or 'cn' are supported in this feature")
####################################################### # Delete backend diff --git a/src/lib389/lib389/cli_conf/security.py b/src/lib389/lib389/cli_conf/security.py index 20f2574..0273817 100644 --- a/src/lib389/lib389/cli_conf/security.py +++ b/src/lib389/lib389/cli_conf/security.py @@ -91,7 +91,10 @@ def _security_generic_set(inst, basedn, logs, args, attrs_map): if arg is None: continue dsobj = props.cls(inst) - dsobj.replace(props.attr, arg) + if arg != "": + dsobj.replace(props.attr, arg) + else: + dsobj.remove_all(props.attr)
def _security_generic_get_parser(parent, attrs_map, help): diff --git a/src/lib389/lib389/configurations/sample.py b/src/lib389/lib389/configurations/sample.py index 25a1b32..f30b8d6 100644 --- a/src/lib389/lib389/configurations/sample.py +++ b/src/lib389/lib389/configurations/sample.py @@ -9,6 +9,9 @@ from ldap import dn
from lib389.idm.domain import Domain +from lib389.idm.organization import Organization +from lib389.idm.organizationalunit import OrganizationalUnit +from lib389.idm.nscontainer import nsContainer from lib389.utils import ensure_str
@@ -42,3 +45,53 @@ def create_base_domain(instance, basedn):
return domain
+ +def create_base_org(instance, basedn): + """Create the base organization object""" + + org = Organization(instance, dn=basedn) + # Explode the dn to get the first bit. + avas = dn.str2dn(basedn) + o_ava = avas[0][0][1] + + org.create(properties={ + # I think in python 2 this forces unicode return ... + 'o': o_ava, + 'description': basedn, + }) + + return org + + +def create_base_orgunit(instance, basedn): + """Create the base org unit object for a org unit""" + + orgunit = OrganizationalUnit(instance, dn=basedn) + # Explode the dn to get the first bit. + avas = dn.str2dn(basedn) + ou_ava = avas[0][0][1] + + orgunit.create(properties={ + # I think in python 2 this forces unicode return ... + 'ou': ou_ava, + 'description': basedn, + }) + + return orgunit + + +def create_base_cn(instance, basedn): + """Create the base nsContainer object""" + + cn = nsContainer(instance, dn=basedn) + # Explode the dn to get the first bit. + avas = dn.str2dn(basedn) + cn_ava = avas[0][0][1] + + cn.create(properties={ + # I think in python 2 this forces unicode return ... + 'cn': cn_ava, + 'description': basedn, + }) + + return cn diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py index 21c1ada..48a14cf 100644 --- a/src/lib389/lib389/replica.py +++ b/src/lib389/lib389/replica.py @@ -1264,15 +1264,15 @@ class Replica(DSLdapObject): raise ValueError('Failed to update replica: ' + str(e)) elif replicarole == ReplicaRole.CONSUMER and newrole == ReplicaRole.MASTER: try: - self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)), + self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)), (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)), - (REPL_ID, str(rid))]) + (REPL_ID, str(rid))) except ldap.LDAPError as e: raise ValueError('Failed to update replica: ' + str(e)) elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.MASTER: try: - self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)), - (REPL_ID, str(rid))]) + self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)), + (REPL_ID, str(rid))) except ldap.LDAPError as e: raise ValueError('Failed to update replica: ' + str(e))
389-commits@lists.fedoraproject.org