[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50413 - ds-replcheck - Always display the Result Summary
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new 1b3c075 Ticket 50413 - ds-replcheck - Always display the Result Summary
1b3c075 is described below
commit 1b3c075bbdc6374156330b7b918accaa5d5bf42b
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu May 30 15:38:27 2019 -0400
Ticket 50413 - ds-replcheck - Always display the Result Summary
Description: Previously we only printed a "Result Summary" if there
were no inconsistencies and the entry counts matched.
However, the entry counts do not need to match. So
this made the "Result Summary" checks too strict, and
if things were out of sync there was no Result Summary
printed at all. This fix just always prints a result
summary and it removes the entry count check.
https://pagure.io/389-ds-base/issue/50413
Reviewed by: ?
(cherry picked from commit 423a7ba01ed3bad52c8caa6a20267f2335b3c69f)
---
ldap/admin/src/scripts/ds-replcheck | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/ldap/admin/src/scripts/ds-replcheck b/ldap/admin/src/scripts/ds-replcheck
index a9411cb..aea32fd 100755
--- a/ldap/admin/src/scripts/ds-replcheck
+++ b/ldap/admin/src/scripts/ds-replcheck
@@ -581,14 +581,14 @@ def do_offline_report(opts, output_file=None):
MLDIF = open(opts['mldif'], "r")
except Exception as e:
print('Failed to open Master LDIF: ' + str(e))
- return None
+ return
try:
RLDIF = open(opts['rldif'], "r")
except Exception as e:
print('Failed to open Replica LDIF: ' + str(e))
MLDIF.close()
- return None
+ return
# Verify LDIF Files
try:
@@ -598,7 +598,7 @@ def do_offline_report(opts, output_file=None):
print('Master LDIF file in invalid, aborting...')
MLDIF.close()
RLDIF.close()
- return None
+ return
try:
print("Validating Replica ldif file ({})...".format(opts['rldif']))
LDIFRecordList(RLDIF).parse()
@@ -606,7 +606,7 @@ def do_offline_report(opts, output_file=None):
print('Replica LDIF file is invalid, aborting...')
MLDIF.close()
RLDIF.close()
- return None
+ return
# Get all the dn's, and entry counts
print ("Gathering all the DN's...")
@@ -758,10 +758,13 @@ def do_offline_report(opts, output_file=None):
final_report += ('=====================================================\n\n')
for diff in diff_report:
final_report += ('%s\n' % (diff))
- if missing_report == "" and len(diff_report) == 0 and m_count == r_count:
- final_report += ('\nResult\n')
- final_report += ('=====================================================\n\n')
- final_report += ('No differences between Master and Replica\n')
+
+ final_report += ('\nResult\n')
+ final_report += ('=====================================================\n\n')
+ if missing_report == "" and len(diff_report) == 0:
+ final_report += ('No replication differences between Master and Replica\n')
+ else:
+ final_report += ('There are replication differences between Master and Replica\n')
if output_file:
output_file.write(final_report)
@@ -1009,10 +1012,12 @@ def print_online_report(report, opts, output_file):
for diff in report['diff']:
final_report += ('%s\n' % (diff))
- if not missing and len(report['diff']) == 0 and report['m_count'] == report['r_count']:
- final_report += ('\nResult\n')
- final_report += ('=====================================================\n\n')
- final_report += ('No differences between Master and Replica\n')
+ final_report += ('\nResult\n')
+ final_report += ('=====================================================\n\n')
+ if not missing and len(report['diff']) == 0:
+ final_report += ('No replication differences between Master and Replica\n')
+ else:
+ final_report += ('There are replication differences between Master and Replica\n')
if output_file:
output_file.write(final_report)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50413 - ds-replcheck - Always display the Result Summary
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new ff5b4fd Ticket 50413 - ds-replcheck - Always display the Result Summary
ff5b4fd is described below
commit ff5b4fd648d783b5e3f562054d10b7b86db401d8
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu May 30 15:38:27 2019 -0400
Ticket 50413 - ds-replcheck - Always display the Result Summary
Description: Previously we only printed a "Result Summary" if there
were no inconsistencies and the entry counts matched.
However, the entry counts do not need to match. So
this made the "Result Summary" checks too strict, and
if things were out of sync there was no Result Summary
printed at all. This fix just always prints a result
summary and it removes the entry count check.
https://pagure.io/389-ds-base/issue/50413
Reviewed by: spichugi(Thanks!)
(cherry picked from commit 423a7ba01ed3bad52c8caa6a20267f2335b3c69f)
---
ldap/admin/src/scripts/ds-replcheck | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/ldap/admin/src/scripts/ds-replcheck b/ldap/admin/src/scripts/ds-replcheck
index 5d7295e..4abb417 100755
--- a/ldap/admin/src/scripts/ds-replcheck
+++ b/ldap/admin/src/scripts/ds-replcheck
@@ -661,14 +661,14 @@ def do_offline_report(opts, output_file=None):
MLDIF = open(opts['mldif'], "r")
except Exception as e:
print('Failed to open Master LDIF: ' + str(e))
- return None
+ return
try:
RLDIF = open(opts['rldif'], "r")
except Exception as e:
print('Failed to open Replica LDIF: ' + str(e))
MLDIF.close()
- return None
+ return
# Verify LDIF Files
try:
@@ -679,7 +679,7 @@ def do_offline_report(opts, output_file=None):
print('Master LDIF file in invalid, aborting...')
MLDIF.close()
RLDIF.close()
- return None
+ return
try:
if opts['verbose']:
print("Validating Replica ldif file ({})...".format(opts['rldif']))
@@ -688,7 +688,7 @@ def do_offline_report(opts, output_file=None):
print('Replica LDIF file is invalid, aborting...')
MLDIF.close()
RLDIF.close()
- return None
+ return
# Get all the dn's, and entry counts
if opts['verbose']:
@@ -845,10 +845,13 @@ def do_offline_report(opts, output_file=None):
final_report += ('=====================================================\n\n')
for diff in diff_report:
final_report += ('%s\n' % (diff))
- if missing_report == "" and len(diff_report) == 0 and m_count == r_count:
- final_report += ('\nResult\n')
- final_report += ('=====================================================\n\n')
- final_report += ('No differences between Master and Replica\n')
+
+ final_report += ('\nResult\n')
+ final_report += ('=====================================================\n\n')
+ if missing_report == "" and len(diff_report) == 0:
+ final_report += ('No replication differences between Master and Replica\n')
+ else:
+ final_report += ('There are replication differences between Master and Replica\n')
if output_file:
output_file.write(final_report)
@@ -1130,10 +1133,12 @@ def print_online_report(report, opts, output_file):
for diff in report['diff']:
final_report += ('%s\n' % (diff))
- if not missing and len(report['diff']) == 0 and report['m_count'] == report['r_count']:
- final_report += ('\nResult\n')
- final_report += ('=====================================================\n\n')
- final_report += ('No differences between Master and Replica\n')
+ final_report += ('\nResult\n')
+ final_report += ('=====================================================\n\n')
+ if not missing and len(report['diff']) == 0:
+ final_report += ('No replication differences between Master and Replica\n')
+ else:
+ final_report += ('There are replication differences between Master and Replica\n')
if output_file:
output_file.write(final_report)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50355 - NSS can change the requested SSL min and max versions
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new 1427641 Ticket 50355 - NSS can change the requested SSL min and max versions
1427641 is described below
commit 1427641b0e2d04243be279473c2a97feac22f63f
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Tue May 14 16:58:55 2019 -0400
Ticket 50355 - NSS can change the requested SSL min and max versions
Description: If we try and set a min and max SSL version in the server,
it is actually only a request. After setting the min and
max, you need to retrieve the min and max to see what NSS
did. Then you have to reset the min and max versions one
more time to actually set the valid range. So yes, you do
have to do a set() -> get() -> set().
There also another outstanding issue with NSS where it says
the default max SSL version in FIPS mode is 1.3, but in fact
it is 1.2. So this patch has a hack fix to workaround that
bug. It should be able to be removed soon...
https://pagure.io/389-ds-base/issue/50355
Reviewed by: mhonek(Thanks!)
(cherry picked from commit 3d4c48eb4fc78628ef15e981d5175c68ab9ee4d8)
---
ldap/servers/slapd/ssl.c | 95 +++++++++++++++++++++++++++++-------------------
1 file changed, 57 insertions(+), 38 deletions(-)
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index a7c3ab7..2d7bc2b 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -41,15 +41,15 @@
* Default SSL Version Rule
* Old SSL version attributes:
* nsSSL3: off -- nsSSL3 == SSL_LIBRARY_VERSION_3_0
- * nsTLS1: on -- nsTLS1 == SSL_LIBRARY_VERSION_TLS_1_0 and greater
+ * nsTLS1: on -- nsTLS1 == SSL_LIBRARY_VERSION_TLS_1_2 and greater
* Note: TLS1.0 is defined in RFC2246, which is close to SSL 3.0.
* New SSL version attributes:
- * sslVersionMin: TLS1.0
+ * sslVersionMin: TLS1.2
* sslVersionMax: max ssl version supported by NSS
******************************************************************************/
-#define DEFVERSION "TLS1.0"
-#define CURRENT_DEFAULT_SSL_VERSION SSL_LIBRARY_VERSION_TLS_1_0
+#define DEFVERSION "TLS1.2"
+#define CURRENT_DEFAULT_SSL_VERSION SSL_LIBRARY_VERSION_TLS_1_2
extern char *slapd_SSL3ciphers;
extern symbol_t supported_ciphers[];
@@ -435,8 +435,13 @@ getSSLVersionRange(char **min, char **max)
return -1;
}
if (!slapd_ssl_listener_is_initialized()) {
+ /*
+ * We have not initialized NSS yet, so we will set the default for
+ * now. Then it will get adjusted to NSS's default min and max once
+ * we complete the security initialization in slapd_ssl_init2()
+ */
if (min) {
- *min = slapi_getSSLVersion_str(LDAP_OPT_X_TLS_PROTOCOL_TLS1_0, NULL, 0);
+ *min = slapi_getSSLVersion_str(LDAP_OPT_X_TLS_PROTOCOL_TLS1_2, NULL, 0);
}
if (max) {
*max = slapi_getSSLVersion_str(LDAP_OPT_X_TLS_PROTOCOL_TLS1_2, NULL, 0);
@@ -457,7 +462,7 @@ getSSLVersionRangeOL(int *min, int *max)
{
/* default range values */
if (min) {
- *min = LDAP_OPT_X_TLS_PROTOCOL_TLS1_0;
+ *min = LDAP_OPT_X_TLS_PROTOCOL_TLS1_2;
}
if (max) {
*max = LDAP_OPT_X_TLS_PROTOCOL_TLS1_2;
@@ -2099,43 +2104,57 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
}
}
- if (NSSVersionMin > 0) {
- /* Use new NSS API SSL_VersionRangeSet (NSS3.14 or newer) */
- slapdNSSVersions.min = NSSVersionMin;
- slapdNSSVersions.max = NSSVersionMax;
- restrict_SSLVersionRange();
- (void)slapi_getSSLVersion_str(slapdNSSVersions.min, mymin, sizeof(mymin));
- (void)slapi_getSSLVersion_str(slapdNSSVersions.max, mymax, sizeof(mymax));
- slapi_log_err(SLAPI_LOG_INFO, "Security Initialization",
- "slapd_ssl_init2 - Configured SSL version range: min: %s, max: %s\n",
- mymin, mymax);
+ /* Handle the SSL version range */
+ slapdNSSVersions.min = NSSVersionMin;
+ slapdNSSVersions.max = NSSVersionMax;
+ restrict_SSLVersionRange();
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.min, mymin, sizeof(mymin));
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.max, mymax, sizeof(mymax));
+ slapi_log_err(SLAPI_LOG_INFO, "Security Initialization",
+ "slapd_ssl_init2 - Configured SSL version range: min: %s, max: %s\n",
+ mymin, mymax);
+ sslStatus = SSL_VersionRangeSet(pr_sock, &slapdNSSVersions);
+ if (sslStatus != SECSuccess) {
+ errorCode = PR_GetError();
+ slapd_SSL_error("Security Initialization - "
+ "slapd_ssl_init2 - Failed to set SSL range: min: %s, max: %s - error %d (%s)\n",
+ mymin, mymax, errorCode, slapd_pr_strerror(errorCode));
+ }
+ /*
+ * Get the version range as NSS might have adjusted our requested range. FIPS mode is
+ * pretty picky about this stuff.
+ */
+ sslStatus = SSL_VersionRangeGet(pr_sock, &slapdNSSVersions);
+ if (sslStatus == SECSuccess) {
+ if (slapdNSSVersions.max > LDAP_OPT_X_TLS_PROTOCOL_TLS1_2 && slapd_pk11_isFIPS()) {
+ /*
+ * FIPS & NSS currently only support a max version of TLS1.2
+ * (although NSS advertises 1.3 as a max range in FIPS mode),
+ * hopefully this code block can be removed soon...
+ */
+ slapdNSSVersions.max = LDAP_OPT_X_TLS_PROTOCOL_TLS1_2;
+ }
+ /* Reset request range */
sslStatus = SSL_VersionRangeSet(pr_sock, &slapdNSSVersions);
if (sslStatus == SECSuccess) {
- /* Set the restricted value to the cn=encryption entry */
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.min, mymin, sizeof(mymin));
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.max, mymax, sizeof(mymax));
+ slapi_log_err(SLAPI_LOG_INFO, "Security Initialization",
+ "slapd_ssl_init2 - NSS adjusted SSL version range: min: %s, max: %s\n",
+ mymin, mymax);
} else {
+ errorCode = PR_GetError();
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.min, mymin, sizeof(mymin));
+ (void)slapi_getSSLVersion_str(slapdNSSVersions.max, mymax, sizeof(mymax));
slapd_SSL_error("Security Initialization - "
- "slapd_ssl_init2 - Failed to set SSL range: min: %s, max: %s\n",
- mymin, mymax);
+ "slapd_ssl_init2 - Failed to set SSL range: min: %s, max: %s - error %d (%s)\n",
+ mymin, mymax, errorCode, slapd_pr_strerror(errorCode));
}
} else {
- /* deprecated code */
- sslStatus = SSL_OptionSet(pr_sock, SSL_ENABLE_SSL3, enableSSL3);
- if (sslStatus != SECSuccess) {
- errorCode = PR_GetError();
- slapd_SSL_warn("Failed to %s SSLv3 "
- "on the imported socket (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
- enableSSL3 ? "enable" : "disable",
- errorCode, slapd_pr_strerror(errorCode));
- }
-
- sslStatus = SSL_OptionSet(pr_sock, SSL_ENABLE_TLS, enableTLS1);
- if (sslStatus != SECSuccess) {
- errorCode = PR_GetError();
- slapd_SSL_warn("Failed to %s TLSv1 "
- "on the imported socket (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
- enableTLS1 ? "enable" : "disable",
- errorCode, slapd_pr_strerror(errorCode));
- }
+ errorCode = PR_GetError();
+ slapd_SSL_error("Security Initialization - ",
+ "slapd_ssl_init2 - Failed to get SSL range from socket - error %d (%s)\n",
+ errorCode, slapd_pr_strerror(errorCode));
}
val = NULL;
@@ -2221,7 +2240,7 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
* that matters. */
if (!startTLS)
- _ssl_listener_initialized = 1; /* --ugaston */
+ _ssl_listener_initialized = 1;
return 0;
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] 03/03: Bump version to 1.4.0.23
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit ba68333a787573f2312783aa1e64de953ba83002
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri May 24 15:32:48 2019 -0400
Bump version to 1.4.0.23
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index e0fae64..ff04df7 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=4
-VERSION_MAINT=0.22
+VERSION_MAINT=0.23
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] 02/03: Issue 50041 - Add the rest UI Plugin tabs - Part 2
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit 3e193b9a79c09f7ad0324af3ebb478491fe69692
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Tue Apr 16 01:07:36 2019 +0200
Issue 50041 - Add the rest UI Plugin tabs - Part 2
Description: Add UI plugin tabs for autoMembership, DNA, managedEntries,
passthroughAuthentication, usn.
Add Shared Config Entry to referentialIntegrity plugin.
Add Plugin Precedence field to the basic plugin configuration.
Fix CLI tools according to the UI changes.
https://pagure.io/389-ds-base/issue/50041
Reviewed by: mreynolds (Thanks!)
---
src/cockpit/389-console/.eslintrc.json | 11 +-
.../389-console/src/lib/plugins/accountPolicy.jsx | 610 ++++++-
.../src/lib/plugins/attributeUniqueness.jsx | 647 +++++++-
.../389-console/src/lib/plugins/autoMembership.jsx | 1015 +++++++++++-
src/cockpit/389-console/src/lib/plugins/dna.jsx | 1129 ++++++++++++-
.../src/lib/plugins/linkedAttributes.jsx | 435 ++++-
.../389-console/src/lib/plugins/managedEntries.jsx | 883 +++++++++-
.../389-console/src/lib/plugins/memberOf.jsx | 72 +-
.../src/lib/plugins/passthroughAuthentication.jsx | 1156 +++++++++++++-
.../src/lib/plugins/pluginBasicConfig.jsx | 55 +-
.../389-console/src/lib/plugins/pluginModal.jsx | 21 +-
.../389-console/src/lib/plugins/pluginTables.jsx | 1688 ++++++++++++++++++++
.../src/lib/plugins/referentialIntegrity.jsx | 744 ++++++++-
.../389-console/src/lib/plugins/retroChangelog.jsx | 223 ++-
.../src/lib/plugins/rootDNAccessControl.jsx | 247 ++-
src/cockpit/389-console/src/lib/plugins/usn.jsx | 312 +++-
.../389-console/src/lib/plugins/winsync.jsx | 234 +++
src/cockpit/389-console/src/plugins.jsx | 29 +-
src/lib389/lib389/cli_conf/__init__.py | 2 +-
.../lib389/cli_conf/plugins/accountpolicy.py | 8 +
src/lib389/lib389/cli_conf/plugins/automember.py | 110 +-
src/lib389/lib389/cli_conf/plugins/dna.py | 117 +-
.../lib389/cli_conf/plugins/managedentries.py | 31 +-
.../lib389/cli_conf/plugins/passthroughauth.py | 79 +-
src/lib389/lib389/cli_conf/plugins/referint.py | 68 +-
src/lib389/lib389/plugins.py | 33 +-
26 files changed, 9752 insertions(+), 207 deletions(-)
diff --git a/src/cockpit/389-console/.eslintrc.json b/src/cockpit/389-console/.eslintrc.json
index 11a82c8..03ff3c4 100644
--- a/src/cockpit/389-console/.eslintrc.json
+++ b/src/cockpit/389-console/.eslintrc.json
@@ -14,13 +14,16 @@
},
"plugins": ["flowtype", "react"],
"rules": {
- "indent": ["error", 4,
+ "indent": [
+ "error",
+ 4,
{
"ObjectExpression": "first",
- "CallExpression": {"arguments": "first"},
+ "CallExpression": { "arguments": "first" },
"MemberExpression": 2,
- "ignoredNodes": [ "JSXAttribute" ]
- }],
+ "ignoredNodes": ["JSXAttribute"]
+ }
+ ],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }],
diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
index fae8652..2d769d1 100644
--- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
@@ -5,9 +5,585 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class AccountPolicy extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.getAttributes = this.getAttributes.bind(this);
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+
+ this.state = {
+ attributes: [],
+ configArea: "",
+ configDN: "",
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: [],
+ configEntryModalShow: false,
+ fixupModalShow: false,
+ newEntry: false
+ };
+ }
+
+ openModal() {
+ this.getAttributes();
+ if (!this.state.configArea) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: "",
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: []
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ "show",
+ this.state.configArea
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Account Policy Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configDN: this.state.configArea,
+ altStateAttrName:
+ configEntry["altstateattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["altstateattrname"][0],
+ label: configEntry["altstateattrname"][0]
+ }
+ ],
+ alwaysRecordLogin: !(
+ configEntry["alwaysrecordlogin"] === undefined ||
+ configEntry["alwaysrecordlogin"][0] == "no"
+ ),
+ alwaysRecordLoginAttr:
+ configEntry["alwaysrecordloginattr"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["alwaysrecordloginattr"][0],
+ label: configEntry["alwaysrecordloginattr"][0]
+ }
+ ],
+ limitAttrName:
+ configEntry["limitattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["limitattrname"][0],
+ label: configEntry["limitattrname"][0]
+ }
+ ],
+ specAttrName:
+ configEntry["specattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["specattrname"][0],
+ label: configEntry["specattrname"][0]
+ }
+ ],
+ stateAttrName:
+ configEntry["stateattrname"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["stateattrname"][0],
+ label: configEntry["stateattrname"][0]
+ }
+ ]
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: this.state.configArea,
+ altStateAttrName: [],
+ alwaysRecordLogin: false,
+ alwaysRecordLoginAttr: [],
+ limitAttrName: [],
+ specAttrName: [],
+ stateAttrName: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configDN,
+ altStateAttrName,
+ alwaysRecordLogin,
+ alwaysRecordLoginAttr,
+ limitAttrName,
+ specAttrName,
+ stateAttrName
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ action,
+ configDN,
+ "--always-record-login",
+ alwaysRecordLogin ? "yes" : "no"
+ ];
+
+ cmd = [...cmd, "--alt-state-attr"];
+ if (altStateAttrName.length != 0) {
+ cmd = [...cmd, altStateAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--always-record-login-attr"];
+ if (alwaysRecordLoginAttr.length != 0) {
+ cmd = [...cmd, alwaysRecordLoginAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--limit-attr"];
+ if (limitAttrName.length != 0) {
+ cmd = [...cmd, limitAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--spec-attr"];
+ if (specAttrName.length != 0) {
+ cmd = [...cmd, specAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--state-attr"];
+ if (stateAttrName.length != 0) {
+ cmd = [...cmd, stateAttrName[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "accountPolicyOperation",
+ `Do the ${action} operation on the Account Policy Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("accountPolicyOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configDN} was successfully ${action}ed`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig() {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "account-policy",
+ "config-entry",
+ "delete",
+ this.state.configDN
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Account Policy Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${this.state.configDN} was successfully deleted`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Account Policy Plugin");
+
+ this.setState({
+ configArea:
+ pluginRow["nsslapd_pluginconfigarea"] === undefined
+ ? ""
+ : pluginRow["nsslapd_pluginconfigarea"][0]
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Manage Account Policy Plugin Shared Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configDN">
+ <Col sm={4}>
+ <ControlLabel title="DN of the config entry">
+ Config DN
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={configDN}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="alwaysRecordLoginAttr"
+ controlId="alwaysRecordLoginAttr"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Specifies the attribute to store the time of the last successful login in this attribute in the users directory entry (alwaysRecordLoginAttr)"
+ >
+ Always Record Login Attribute
+ </Col>
+ <Col sm={5}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ alwaysRecordLoginAttr: value
+ });
+ }}
+ selected={alwaysRecordLoginAttr}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="alwaysRecordLogin"
+ checked={alwaysRecordLogin}
+ title="Sets that every entry records its last login time (alwaysRecordLogin)"
+ onChange={this.handleCheckboxChange}
+ >
+ Always Record Login
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="specAttrName"
+ controlId="specAttrName"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Specifies the attribute to identify which entries are account policy configuration entries (specAttrName)"
+ >
+ Specific Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ specAttrName: value
+ });
+ }}
+ selected={specAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="stateAttrName"
+ controlId="stateAttrName"
+ disabled={false}
+ >
+ <Col sm={4}>
+ <ControlLabel title="Specifies the primary time attribute used to evaluate an account policy (stateAttrName)">
+ State Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ stateAttrName: value
+ });
+ }}
+ selected={stateAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="altStateAttrName"
+ controlId="altStateAttrName"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Provides a backup attribute for the server to reference to evaluate the expiration time (altStateAttrName)"
+ >
+ Alternative State Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ altStateAttrName: value
+ });
+ }}
+ selected={altStateAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="limitAttrName" disabled={false}>
+ <Col sm={4}>
+ <ControlLabel title="Specifies the attribute within the policy to use for the account inactivation limit (limitAttrName)">
+ Limit Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ limitAttrName: value
+ });
+ }}
+ selected={limitAttrName}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteConfig}
+ disabled={newEntry}
+ >
+ Delete
+ </Button>
+ <Button bsStyle="primary" onClick={this.editConfig} disabled={newEntry}>
+ Save
+ </Button>
+ <Button bsStyle="primary" onClick={this.addConfig} disabled={!newEntry}>
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +594,39 @@ class AccountPolicy extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={6}>
+ <Form horizontal>
+ <FormGroup key="configArea" controlId="configArea">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="DN of the shared config entry (nsslapd-pluginConfigArea)"
+ >
+ Shared Config Entry
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={configArea}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openModal}
+ >
+ Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
index 0521a89..3d81d58 100644
--- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
@@ -5,9 +5,637 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class AttributeUniqueness extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ attributes: [],
+ objectClasses: [],
+
+ configName: "",
+ configEnabled: false,
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: [],
+
+ newEntry: false,
+ showConfigModal: false,
+ showConfirmDeleteConfig: false
+ };
+
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+ this.getObjectClasses = this.getObjectClasses.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ }
+
+ handleSwitchChange(value) {
+ this.setState({
+ configEnabled: !value
+ });
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ // Get all the attributes and matching rules now
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "attr-uniq",
+ "list"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Attribute Uniqueness Plugin configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ if (err != 0) {
+ let errMsg = JSON.parse(err);
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ this.getObjectClasses();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: []
+ });
+ } else {
+ let configAttrNamesList = [];
+ let configSubtreesList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "attr-uniq",
+ "show",
+ name
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Attribute Uniqueness Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0],
+ configEnabled: !(
+ configEntry["nsslapd-pluginenabled"] === undefined ||
+ configEntry["nsslapd-pluginenabled"][0] == "off"
+ ),
+ acrossAllSubtrees: !(
+ configEntry["uniqueness-across-all-subtrees"] === undefined ||
+ configEntry["uniqueness-across-all-subtrees"][0] == "off"
+ ),
+ topEntryOc:
+ configEntry["uniqueness-top-entry-oc"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["uniqueness-top-entry-oc"][0],
+ label: configEntry["uniqueness-top-entry-oc"][0]
+ }
+ ],
+ subtreeEnriesOc:
+ configEntry["uniqueness-subtree-entries-oc"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["uniqueness-subtree-entries-oc"][0],
+ label: configEntry["uniqueness-subtree-entries-oc"][0]
+ }
+ ]
+ });
+
+ if (configEntry["uniqueness-attribute-name"] === undefined) {
+ this.setState({ attrNames: [] });
+ } else {
+ for (let value of configEntry["uniqueness-attribute-name"]) {
+ configAttrNamesList = [
+ ...configAttrNamesList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({ attrNames: configAttrNamesList });
+ }
+ if (configEntry["uniqueness-subtrees"] === undefined) {
+ this.setState({ subtrees: [] });
+ } else {
+ for (let value of configEntry["uniqueness-subtrees"]) {
+ configSubtreesList = [
+ ...configSubtreesList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({ subtrees: configSubtreesList });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ attrNames: [],
+ subtrees: [],
+ acrossAllSubtrees: false,
+ topEntryOc: [],
+ subtreeEnriesOc: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configName,
+ configEnabled,
+ attrNames,
+ subtrees,
+ acrossAllSubtrees,
+ topEntryOc,
+ subtreeEnriesOc
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "attr-uniq",
+ action,
+ configName,
+ "--enabled",
+ configEnabled ? "on" : "off",
+ "--across-all-subtrees",
+ acrossAllSubtrees ? "on" : "off"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ if (!(action == "add" && attrNames.length == 0)) {
+ cmd = [...cmd, "--attr-name"];
+ if (attrNames.length != 0) {
+ for (let value of attrNames) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ if (!(action == "add" && subtrees.length == 0)) {
+ cmd = [...cmd, "--subtree"];
+ if (subtrees.length != 0) {
+ for (let value of subtrees) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ cmd = [...cmd, "--top-entry-oc"];
+ if (topEntryOc.length != 0) {
+ cmd = [...cmd, topEntryOc[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--subtree-entries-oc"];
+ if (subtreeEnriesOc.length != 0) {
+ cmd = [...cmd, subtreeEnriesOc[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "attrUniqOperation",
+ `Do the ${action} operation on the Attribute Uniqueness Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("attrUniqOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "attr-uniq",
+ "delete",
+ configName
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Attribute Uniqueness Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
+ getObjectClasses() {
+ const oc_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "objectclasses",
+ "list"
+ ];
+ log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);
+ cockpit
+ .spawn(oc_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const ocContent = JSON.parse(content);
+ let ocs = [];
+ for (let content of ocContent["items"]) {
+ ocs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ objectClasses: ocs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get objectClasses - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} Attribute Uniqueness Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configName">
+ <Col sm={3}>
+ <ControlLabel title='Sets the name of the plug-in configuration record. (cn) You can use any string, but "attribute_name Attribute Uniqueness" is recommended.'>
+ Config Name
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="attrNames"
+ controlId="attrNames"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the name of the attribute whose values must be unique. This attribute is multi-valued. (uniqueness-attribute-name)"
+ >
+ Attribute Names
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ attrNames: values
+ });
+ }}
+ selected={attrNames}
+ newSelectionPrefix="Add an attribute: "
+ options={attributes}
+ placeholder="Type an attribute name..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="subtrees"
+ controlId="subtrees"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the DN under which the plug-in checks for uniqueness of the attributes value. This attribute is multi-valued (uniqueness-subtrees)"
+ >
+ Subtrees
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ subtrees: values
+ });
+ }}
+ selected={subtrees}
+ options={[""]}
+ newSelectionPrefix="Add a subtree: "
+ placeholder="Type a subtree DN..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="topEntryOc"
+ controlId="topEntryOc"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Verifies that the value of the attribute set in uniqueness-attribute-name is unique in this subtree (uniqueness-top-entry-oc)"
+ >
+ Top Entry OC
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ topEntryOc: value
+ });
+ }}
+ selected={topEntryOc}
+ options={objectClasses}
+ newSelectionPrefix="Add a top entry objectClass: "
+ placeholder="Type an objectClass..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="subtreeEnriesOc"
+ controlId="subtreeEnriesOc"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Verifies if an attribute is unique, if the entry contains the object class set in this parameter (uniqueness-subtree-entries-oc)"
+ >
+ Subtree Entries OC
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ subtreeEnriesOc: value
+ });
+ }}
+ selected={subtreeEnriesOc}
+ options={objectClasses}
+ newSelectionPrefix="Add a subtree entries objectClass: "
+ placeholder="Type an objectClass..."
+ />
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="acrossAllSubtrees"
+ checked={acrossAllSubtrees}
+ title="If enabled (on), the plug-in checks that the attribute is unique across all subtrees set. If you set the attribute to off, uniqueness is only enforced within the subtree of the updated entry (uniqueness-across-all-subtrees)"
+ onChange={this.handleCheckboxChange}
+ >
+ Across All Subtrees
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configEnabled"
+ controlId="configEnabled"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies whether or not the config is enabled."
+ >
+ Enable config
+ </Col>
+ <Col sm={3}>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="configEnabled"
+ value={configEnabled}
+ onChange={() =>
+ this.handleSwitchChange(configEnabled)
+ }
+ animate={false}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +646,24 @@ class AttributeUniqueness extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <AttrUniqConfigTable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
index e3291fa..b5079ea 100644
--- a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
@@ -1,13 +1,1005 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { AutoMembershipDefinitionTable, AutoMembershipRegexTable } from "./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class AutoMembership extends React.Component {
+ componentWillMount() {
+ this.loadDefinitions();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ definitionRows: [],
+ regexRows: [],
+ regexesToDelete: [],
+ attributes: [],
+
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: "",
+
+ regexName: "",
+ regexExclusive: [],
+ regexInclusive: [],
+ regexTargetGroup: "",
+
+ newDefinitionEntry: false,
+ newRegexEntry: false,
+ definitionEntryModalShow: false,
+ regexEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.loadDefinitions = this.loadDefinitions.bind(this);
+ this.loadRegexes = this.loadRegexes.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditDefinitionModal = this.showEditDefinitionModal.bind(this);
+ this.showAddDefinitionModal = this.showAddDefinitionModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteDefinition = this.deleteDefinition.bind(this);
+ this.addDefinition = this.addDefinition.bind(this);
+ this.editDefinition = this.editDefinition.bind(this);
+
+ this.openRegexModal = this.openRegexModal.bind(this);
+ this.closeRegexModal = this.closeRegexModal.bind(this);
+ this.showEditRegexModal = this.showEditRegexModal.bind(this);
+ this.showAddRegexModal = this.showAddRegexModal.bind(this);
+ this.cmdRegexOperation = this.cmdRegexOperation.bind(this);
+ this.deleteRegex = this.deleteRegex.bind(this);
+ this.addRegex = this.addRegex.bind(this);
+ this.editRegex = this.editRegex.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadDefinitions() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "list",
+ "definitions"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadDefinitions", "Get Auto Membership Plugin definitions", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ definitionRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadDefinitions failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadRegexes(defName) {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "list",
+ "regexes",
+ defName
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadRegexes", "Get Auto Membership Plugin regexes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ regexRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadRegexes failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditDefinitionModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddDefinitionModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: true,
+ regexRows: [],
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: ""
+ });
+ } else {
+ this.loadRegexes(name);
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "definition",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Auto Membership Plugin definition entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let definitionEntry = JSON.parse(content).attrs;
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: false,
+ definitionName:
+ definitionEntry["cn"] === undefined ? "" : definitionEntry["cn"][0],
+ defaultGroup:
+ definitionEntry["automemberdefaultgroup"] === undefined
+ ? ""
+ : definitionEntry["automemberdefaultgroup"][0],
+ filter:
+ definitionEntry["automemberfilter"] === undefined
+ ? ""
+ : definitionEntry["automemberfilter"][0],
+ scope:
+ definitionEntry["automemberscope"] === undefined
+ ? ""
+ : definitionEntry["automemberscope"][0]
+ });
+
+ if (definitionEntry["automembergroupingattr"] === undefined) {
+ this.setState({
+ groupingAttrMember: [],
+ groupingAttrEntry: ""
+ });
+ } else {
+ let groupingAttr = definitionEntry["automembergroupingattr"][0];
+ this.setState({
+ groupingAttrMember: [
+ {
+ id: groupingAttr.split(":")[0],
+ label: groupingAttr.split(":")[0]
+ }
+ ],
+ groupingAttrEntry: groupingAttr.split(":")[1]
+ });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ definitionEntryModalShow: true,
+ newDefinitionEntry: true,
+ regexRows: [],
+ definitionName: "",
+ defaultGroup: "",
+ filter: "",
+ groupingAttrMember: [],
+ groupingAttrEntry: "",
+ scope: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ showEditRegexModal(rowData) {
+ this.openRegexModal(rowData.cn[0]);
+ }
+
+ showAddRegexModal(rowData) {
+ this.openRegexModal();
+ }
+
+ closeModal() {
+ this.setState({ definitionEntryModalShow: false });
+ }
+
+ closeRegexModal() {
+ this.setState({ regexEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ definitionName,
+ defaultGroup,
+ filter,
+ groupingAttrMember,
+ groupingAttrEntry,
+ scope
+ } = this.state;
+
+ if (
+ definitionName === "" ||
+ scope === "" ||
+ filter === "" ||
+ groupingAttrMember.length == 0 ||
+ groupingAttrEntry === ""
+ ) {
+ this.props.addNotification(
+ "warning",
+ "Name, Scope, Filter and Grouping Attribute are required."
+ );
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ action,
+ "--default-group",
+ defaultGroup || action == "add" ? defaultGroup : "delete",
+ "--filter",
+ filter || action == "add" ? filter : "delete",
+ "--scope",
+ scope || action == "add" ? scope : "delete"
+ ];
+
+ cmd = [...cmd, "--grouping-attr"];
+ if (groupingAttrMember.length != 0 && groupingAttrEntry.length != 0) {
+ cmd = [...cmd, `${groupingAttrMember[0].id}:${groupingAttrEntry}`];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "AutoMembershipOperation",
+ `Do the ${action} operation on the Auto Membership Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("AutoMembershipOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${definitionName}" entry`
+ );
+ this.loadDefinitions();
+ this.purgeRegexUpdate();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc.indexOf("nothing to set") === 0) {
+ this.props.addNotification(
+ "error",
+ `Error during the definition entry ${action} operation - ${errMsg.desc}`
+ );
+ } else {
+ this.purgeRegexUpdate();
+ }
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ purgeRegexUpdate() {
+ const { definitionName, regexesToDelete, regexRows } = this.state;
+
+ for (let regexToDelete of regexesToDelete) {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "regex",
+ regexToDelete,
+ "delete"
+ ];
+
+ log_cmd(
+ "AutoMembershipRegexOperation",
+ `Do the delete operation on the Auto Membership Plugin Regex Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info(
+ "AutoMembershipRegexOperation",
+ "Result",
+ `The delete operation was successfully done on "${regexToDelete}" entry`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the regex "${regexToDelete}" entry delete operation - ${
+ errMsg.desc
+ }`
+ );
+ });
+ }
+
+ for (let row of regexRows) {
+ let action = "";
+ let regexName = "";
+ if (row.needsadd !== undefined) {
+ action = "add";
+ regexName = row.cn[0];
+ } else if (row.needsupdate !== undefined) {
+ action = "set";
+ regexName = row.cn[0];
+ } else {
+ continue;
+ }
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "regex",
+ regexName,
+ action
+ ];
+
+ let regexTargetGroup = row.automembertargetgroup[0];
+ let regexExclusive = row.automemberexclusiveregex;
+ let regexInclusive = row.automemberinclusiveregex;
+
+ if (!(action == "add" && regexTargetGroup == 0)) {
+ cmd = [...cmd, "--target-group"];
+ if (regexTargetGroup) {
+ cmd = [...cmd, regexTargetGroup];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ if (!(action == "add" && regexExclusive.length == 0)) {
+ cmd = [...cmd, "--exclusive"];
+ if (regexExclusive.length != 0) {
+ for (let regex of regexExclusive) {
+ cmd = [...cmd, regex];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+ if (!(action == "add" && regexInclusive.length == 0)) {
+ cmd = [...cmd, "--inclusive"];
+ if (regexInclusive.length != 0) {
+ for (let regex of regexInclusive) {
+ cmd = [...cmd, regex];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ log_cmd(
+ "AutoMembershipRegexOperation",
+ `Do the ${action} operation on the Auto Membership Plugin Regex Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info(
+ "AutoMembershipRegexOperation",
+ "Result",
+ `The ${action} operation was successfully done on "${regexName}" entry`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the regex "${regexName}" entry ${action} operation - ${
+ errMsg.desc
+ }`
+ );
+ });
+ }
+ }
+
+ deleteDefinition(rowData) {
+ let definitionName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "automember",
+ "definition",
+ definitionName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteDefinition", "Delete the Auto Membership Plugin definition entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteDefinition", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Definition entry ${definitionName} was successfully deleted`
+ );
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the definition entry removal operation - ${errMsg.desc}`
+ );
+ this.loadDefinitions();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addDefinition() {
+ this.cmdOperation("add");
+ }
+
+ editDefinition() {
+ this.cmdOperation("set");
+ }
+
+ openRegexModal(name) {
+ if (name) {
+ let regexEntry = this.state.regexRows.filter(row => row.cn[0] === name)[0];
+
+ let exclusiveRegexList = [];
+ let inclusiveRegexList = [];
+ // Get all the attributes and matching rules now
+ if (regexEntry["automemberexclusiveregex"] === undefined) {
+ this.setState({ regexExclusive: [] });
+ } else {
+ for (let value of regexEntry["automemberexclusiveregex"]) {
+ exclusiveRegexList = [...exclusiveRegexList, { id: value, label: value }];
+ }
+ this.setState({ regexExclusive: exclusiveRegexList });
+ }
+ if (regexEntry["automemberinclusiveregex"] === undefined) {
+ this.setState({ regexInclusive: [] });
+ } else {
+ for (let value of regexEntry["automemberinclusiveregex"]) {
+ inclusiveRegexList = [...inclusiveRegexList, { id: value, label: value }];
+ }
+ this.setState({ regexInclusive: inclusiveRegexList });
+ }
+ this.setState({
+ regexEntryModalShow: true,
+ newRegexEntry: false,
+ regexName: regexEntry["cn"][0],
+ regexTargetGroup:
+ regexEntry["automembertargetgroup"] === undefined
+ ? ""
+ : regexEntry["automembertargetgroup"][0]
+ });
+ } else {
+ this.setState({
+ regexEntryModalShow: true,
+ newRegexEntry: true,
+ regexName: "",
+ regexExclusive: [],
+ regexInclusive: [],
+ regexTargetGroup: ""
+ });
+ }
+ }
+
+ cmdRegexOperation(action) {
+ const {
+ regexRows,
+ regexName,
+ regexExclusive,
+ regexInclusive,
+ regexTargetGroup
+ } = this.state;
+
+ let regexExists = false;
+ if (regexRows.some(row => row.cn[0] === regexName)) {
+ regexExists = true;
+ }
+
+ if (regexName === "" || regexTargetGroup === "") {
+ this.props.addNotification("warning", "Name and Target Group are required.");
+ } else {
+ if (action == "add") {
+ if (!regexExists) {
+ this.setState(prevState => ({
+ regexRows: [
+ ...prevState.regexRows,
+ {
+ cn: [regexName],
+ automembertargetgroup: [regexTargetGroup],
+ automemberexclusiveregex:
+ regexExclusive.length !== 0
+ ? regexExclusive.map(regex => regex.label)
+ : [],
+ automemberinclusiveregex:
+ regexInclusive.length !== 0
+ ? regexInclusive.map(regex => regex.label)
+ : [],
+ needsadd: true
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification("error", `Regex "${regexName}" already exists`);
+ }
+ } else if (action == "set") {
+ if (regexExists) {
+ this.setState({
+ regexRows: regexRows.filter(row => row.cn[0] !== regexName)
+ });
+
+ this.setState(prevState => ({
+ regexRows: [
+ ...prevState.regexRows,
+ {
+ cn: [regexName],
+ automembertargetgroup: [regexTargetGroup],
+ automemberexclusiveregex:
+ regexExclusive.length !== 0
+ ? regexExclusive.map(regex => regex.label)
+ : [],
+ automemberinclusiveregex:
+ regexInclusive.length !== 0
+ ? regexInclusive.map(regex => regex.label)
+ : [],
+ needsupdate: true
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Regex "${regexName}" does not exist - "${action}" is impossible`
+ );
+ }
+ }
+ this.closeRegexModal();
+ }
+ }
+
+ deleteRegex(rowData) {
+ const { regexRows } = this.state;
+ const regexName = rowData.cn[0];
+
+ if (regexRows.some(row => row.cn[0] === regexName)) {
+ this.setState({
+ regexRows: regexRows.filter(row => row.cn[0] !== regexName)
+ });
+ this.setState(prevState => ({
+ regexesToDelete: [...prevState.regexesToDelete, regexName]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Regex "${regexName}" does not exist - impossible to delete`
+ );
+ }
+ }
+
+ addRegex() {
+ this.cmdRegexOperation("add");
+ }
+
+ editRegex() {
+ this.cmdRegexOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ regexRows,
+ definitionEntryModalShow,
+ definitionName,
+ newDefinitionEntry,
+ regexEntryModalShow,
+ newRegexEntry,
+ attributes,
+ groupingAttrEntry,
+ groupingAttrMember,
+ regexName,
+ regexExclusive,
+ regexInclusive,
+ regexTargetGroup
+ } = this.state;
+
+ const modalDefinitionFields = {
+ defaultGroup: {
+ name: "Default Group",
+ value: this.state.defaultGroup,
+ help: `Sets default or fallback group to add the entry to as a member attribute in group entry (autoMemberDefaultGroup)`
+ },
+ scope: {
+ name: "Scope",
+ value: this.state.scope,
+ help: "Sets the subtree DN to search for entries (autoMemberScope)"
+ },
+ filter: {
+ name: "Filter",
+ value: this.state.filter,
+ help:
+ "Sets a standard LDAP search filter to use to search for matching entries (autoMemberFilter)"
+ }
+ };
+
return (
<div>
+ <Modal show={definitionEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newDefinitionEntry ? "Add" : "Edit"} Auto Membership Plugin
+ Definition Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="definitionName" controlId="definitionName">
+ <Col sm={3}>
+ <ControlLabel>Definition Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={definitionName}
+ onChange={this.handleFieldChange}
+ disabled={!newDefinitionEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalDefinitionFields).map(
+ ([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={3}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+ <FormGroup
+ key="groupingAttrEntry"
+ controlId="groupingAttrEntry"
+ >
+ <Col sm={3}>
+ <ControlLabel
+ title={`Specifies the name of the member attribute in the group entry and the attribute in the object entry that supplies the member attribute value, in the format group_member_attr:entry_attr (autoMemberGroupingAttr)`}
+ >
+ Grouping Attributes
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ groupingAttrMember: value
+ });
+ }}
+ selected={groupingAttrMember}
+ options={attributes}
+ newSelectionPrefix="Set an attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ <Col sm={1}>:</Col>
+ <Col sm={4}>
+ <FormControl
+ required
+ type="text"
+ value={groupingAttrEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <AutoMembershipRegexTable
+ rows={regexRows}
+ editConfig={this.showEditRegexModal}
+ deleteConfig={this.deleteRegex}
+ />
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddRegexModal}
+ >
+ Add Regex
+ </Button>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={
+ newDefinitionEntry ? this.addDefinition : this.editDefinition
+ }
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={regexEntryModalShow} onHide={this.closeRegexModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeRegexModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Manage Auto Membership Plugin Regex Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="regexName" controlId="regexName">
+ <Col sm={3}>
+ <ControlLabel>Regex Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={regexName}
+ onChange={this.handleFieldChange}
+ disabled={!newRegexEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="regexExclusive" controlId="regexExclusive">
+ <Col sm={3}>
+ <ControlLabel
+ title={`Sets a single regular expression to use to identify entries to exclude (autoMemberExclusiveRegex)`}
+ >
+ Exclusive Regex
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ regexExclusive: value
+ });
+ }}
+ selected={regexExclusive}
+ options={[]}
+ newSelectionPrefix="Set an exclusive regex: "
+ placeholder="Type a regex..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="regexInclusive" controlId="regexInclusive">
+ <Col sm={3}>
+ <ControlLabel
+ title={`Sets a single regular expression to use to identify entries to exclude (autoMemberExclusiveRegex)`}
+ >
+ Inclusive Regex
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ regexInclusive: value
+ });
+ }}
+ selected={regexInclusive}
+ options={[]}
+ newSelectionPrefix="Set an inclusive regex: "
+ placeholder="Type a regex..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="regexTargetGroup"
+ controlId="regexTargetGroup"
+ >
+ <Col sm={3}>
+ <ControlLabel
+ title={`Sets which group to add the entry to as a member, if it meets the regular expression conditions (autoMemberTargetGroup)`}
+ >
+ Target Group
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={regexTargetGroup}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeRegexModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newRegexEntry ? this.addRegex : this.editRegex}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +1010,24 @@ class AutoMembership extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <AutoMembershipDefinitionTable
+ rows={this.state.definitionRows}
+ editConfig={this.showEditDefinitionModal}
+ deleteConfig={this.deleteDefinition}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddDefinitionModal}
+ >
+ Add Definition
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx b/src/cockpit/389-console/src/lib/plugins/dna.jsx
index caf62bb..185ca1f 100644
--- a/src/cockpit/389-console/src/lib/plugins/dna.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx
@@ -1,13 +1,1113 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { DNATable, DNASharedTable } from "./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class DNA extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ sharedConfigRows: [],
+ attributes: [],
+
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: "",
+
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: "",
+
+ newEntry: false,
+ newSharedEntry: false,
+ configEntryModalShow: false,
+ sharedConfigListModalShow: false,
+ sharedConfigEntryModalShow: false,
+ showConfirmSharedSave: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.loadSharedConfigs = this.loadSharedConfigs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+
+ this.openSharedListModal = this.openSharedListModal.bind(this);
+ this.closeSharedListModal = this.closeSharedListModal.bind(this);
+
+ this.openSharedModal = this.openSharedModal.bind(this);
+ this.closeSharedModal = this.closeSharedModal.bind(this);
+ this.showEditSharedConfigModal = this.showEditSharedConfigModal.bind(this);
+ this.showAddSharedConfigModal = this.showAddSharedConfigModal.bind(this);
+ this.cmdSharedOperation = this.cmdSharedOperation.bind(this);
+ this.deleteSharedConfig = this.deleteSharedConfig.bind(this);
+ this.addSharedConfig = this.addSharedConfig.bind(this);
+ this.editSharedConfig = this.editSharedConfig.bind(this);
+
+ this.showConfirmSharedSave = this.showConfirmSharedSave.bind(this);
+ this.closeConfirmSharedSave = this.closeConfirmSharedSave.bind(this);
+ }
+
+ showConfirmSharedSave() {
+ if (this.state.sharedConfigEntry != "") {
+ this.setState({
+ showConfirmSharedSave: true
+ });
+ } else {
+ this.props.addNotification(
+ "warning",
+ "Shared Config Entry attribute is required for the 'Manage' operation"
+ );
+ }
+ }
+
+ closeConfirmSharedSave() {
+ this.setState({
+ showConfirmSharedSave: false
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "list",
+ "configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get DNA Plugin configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadSharedConfigs(basedn) {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "list",
+ "shared-configs",
+ basedn
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadSharedConfigs", "Get DNA Plugin shared configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ sharedConfigRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadSharedConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: ""
+ });
+ } else {
+ let dnaTypeList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the DNA Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0],
+ prefix:
+ configEntry["dnaprefix"] === undefined
+ ? ""
+ : configEntry["dnaprefix"][0],
+ nextValue:
+ configEntry["dnanextvalue"] === undefined
+ ? ""
+ : configEntry["dnanextvalue"][0],
+ maxValue:
+ configEntry["dnamaxvalue"] === undefined
+ ? ""
+ : configEntry["dnamaxvalue"][0],
+ interval:
+ configEntry["dnainterval"] === undefined
+ ? ""
+ : configEntry["dnainterval"][0],
+ magicRegen:
+ configEntry["dnamagicregen"] === undefined
+ ? ""
+ : configEntry["dnamagicregen"][0],
+ filter:
+ configEntry["dnafilter"] === undefined
+ ? ""
+ : configEntry["dnafilter"][0],
+ scope:
+ configEntry["dnascope"] === undefined ? "" : configEntry["dnascope"][0],
+ remoteBindDN:
+ configEntry["dnaremotebindDN"] === undefined
+ ? ""
+ : configEntry["dnaremotebindDN"][0],
+ remoteBindCred:
+ configEntry["dnaremotebindcred"] === undefined
+ ? ""
+ : configEntry["dnaremotebindcred"][0],
+ sharedConfigEntry:
+ configEntry["dnasharedcfgdn"] === undefined
+ ? ""
+ : configEntry["dnasharedcfgdn"][0],
+ threshold:
+ configEntry["dnathreshold"] === undefined
+ ? ""
+ : configEntry["dnathreshold"][0],
+ nextRange:
+ configEntry["dnanextrange"] === undefined
+ ? ""
+ : configEntry["dnanextrange"][0],
+ rangeRequesTimeout:
+ configEntry["dnarangerequesttimeout"] === undefined
+ ? ""
+ : configEntry["dnarangerequesttimeout"][0]
+ });
+ if (configEntry["dnatype"] === undefined) {
+ this.setState({ type: [] });
+ } else {
+ for (let value of configEntry["dnatype"]) {
+ dnaTypeList = [...dnaTypeList, { id: value, label: value }];
+ }
+ this.setState({ type: dnaTypeList });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ type: [],
+ prefix: "",
+ nextValue: "",
+ maxValue: "",
+ interval: "",
+ magicRegen: "",
+ filter: "",
+ scope: "",
+ remoteBindDN: "",
+ remoteBindCred: "",
+ sharedConfigEntry: "",
+ threshold: "",
+ nextRange: "",
+ rangeRequesTimeout: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ showEditSharedConfigModal(rowData) {
+ this.openSharedModal(rowData.dnahostname[0], rowData.dnaportnum[0]);
+ }
+
+ showAddSharedConfigModal(rowData) {
+ this.openSharedModal();
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ closeSharedModal() {
+ this.setState({ sharedConfigEntryModalShow: false });
+ }
+
+ cmdOperation(action, muteError) {
+ const {
+ configName,
+ type,
+ prefix,
+ nextValue,
+ maxValue,
+ interval,
+ magicRegen,
+ filter,
+ scope,
+ remoteBindDN,
+ remoteBindCred,
+ sharedConfigEntry,
+ threshold,
+ nextRange,
+ rangeRequesTimeout
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ action,
+ "--prefix",
+ prefix || action == "add" ? prefix : "delete",
+ "--next-value",
+ nextValue || action == "add" ? nextValue : "delete",
+ "--max-value",
+ maxValue || action == "add" ? maxValue : "delete",
+ "--interval",
+ interval || action == "add" ? interval : "delete",
+ "--magic-regen",
+ magicRegen || action == "add" ? magicRegen : "delete",
+ "--filter",
+ filter || action == "add" ? filter : "delete",
+ "--scope",
+ scope || action == "add" ? scope : "delete",
+ "--remote-bind-dn",
+ remoteBindDN || action == "add" ? remoteBindDN : "delete",
+ "--remote-bind-cred",
+ remoteBindCred || action == "add" ? remoteBindCred : "delete",
+ "--shared-config-entry",
+ sharedConfigEntry || action == "add" ? sharedConfigEntry : "delete",
+ "--threshold",
+ threshold || action == "add" ? threshold : "delete",
+ "--next-range",
+ nextRange || action == "add" ? nextRange : "delete",
+ "--range-request-timeout",
+ rangeRequesTimeout || action == "add" ? rangeRequesTimeout : "delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ if (!(action == "add" && type.length == 0)) {
+ cmd = [...cmd, "--type"];
+ if (type.length != 0) {
+ for (let value of type) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd("DNAOperation", `Do the ${action} operation on the DNA Plugin`, cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("DNAOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (muteError !== true) {
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ }
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the DNA Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig(muteError) {
+ this.cmdOperation("add", muteError);
+ }
+
+ editConfig(muteError) {
+ this.cmdOperation("set", muteError);
+ }
+
+ openSharedListModal(sharedConfigEntry) {
+ // Save the config entry that is being edited now
+ if (this.state.newEntry) {
+ this.addConfig(true);
+ } else {
+ this.editConfig(true);
+ }
+ // Get all of the sharedConfig entries located under the base DN
+ this.loadSharedConfigs(sharedConfigEntry);
+ this.setState({ sharedConfigListModalShow: true });
+ }
+
+ closeSharedListModal() {
+ this.setState({ sharedConfigListModalShow: false });
+ }
+
+ openSharedModal(hostname, port) {
+ if (hostname && port) {
+ // Get all the attributes and matching rules now
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ this.state.configName,
+ "shared-config-entry",
+ hostname,
+ port,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openSharedModal", "Fetch the DNA Plugin shared config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: false,
+ sharedHostname:
+ configEntry["dnahostname"] === undefined
+ ? ""
+ : configEntry["dnahostname"][0],
+ sharedPort:
+ configEntry["dnaportnum"] === undefined
+ ? ""
+ : configEntry["dnaportnum"][0],
+ sharedSecurePort:
+ configEntry["dnasecureportnum"] === undefined
+ ? ""
+ : configEntry["dnasecureportnum"][0],
+ sharedRemainingValues:
+ configEntry["dnaremainingvalues"] === undefined
+ ? ""
+ : configEntry["dnaremainingvalues"][0],
+ sharedRemoteBindMethod:
+ configEntry["dnaremotebindmethod"] === undefined
+ ? ""
+ : configEntry["dnaremotebindmethod"][0],
+ sharedRemoteConnProtocol:
+ configEntry["dnaremoteconnprotocol"] === undefined
+ ? ""
+ : configEntry["dnaremoteconnprotocol"][0]
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: true,
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ } else {
+ this.setState({
+ sharedConfigEntryModalShow: true,
+ newSharedEntry: true,
+ sharedBaseDN: "",
+ sharedHostname: "",
+ sharedPort: "",
+ sharedSecurePort: "",
+ sharedRemainingValues: "",
+ sharedRemoteBindMethod: "",
+ sharedRemoteConnProtocol: ""
+ });
+ }
+ }
+
+ cmdSharedOperation(action) {
+ const {
+ configName,
+ sharedHostname,
+ sharedPort,
+ sharedSecurePort,
+ sharedRemainingValues,
+ sharedRemoteBindMethod,
+ sharedRemoteConnProtocol
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ configName,
+ "shared-config-entry",
+ sharedHostname,
+ sharedPort,
+ action,
+ "--secure-port",
+ sharedSecurePort || action == "add" ? sharedSecurePort : "delete",
+ "--remote-bind-method",
+ sharedRemoteBindMethod || action == "add" ? sharedRemoteBindMethod : "delete",
+ "--remote-conn-protocol",
+ sharedRemoteConnProtocol || action == "add" ? sharedRemoteConnProtocol : "delete",
+ "--remaining-values",
+ sharedRemainingValues || action == "add" ? sharedRemainingValues : "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "DNASharedOperation",
+ `Do the ${action} operation on the DNA Plugin Shared Entry`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("DNASharedOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${sharedHostname}:${sharedPort}" entry`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.closeSharedModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.closeSharedModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteSharedConfig(rowData) {
+ let sharedHostname = rowData.dnahostname[0];
+ let sharedPort = rowData.dnaportnum[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "dna",
+ "config",
+ this.state.configName,
+ "shared-config-entry",
+ sharedHostname,
+ sharedPort,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteSharedConfig", "Delete the DNA Plugin Shared config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteSharedConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Shared config entry ${sharedHostname} and ${sharedPort} was successfully deleted`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the shared config entry removal operation - ${errMsg.desc}`
+ );
+ this.loadSharedConfigs(this.state.sharedConfigEntry);
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addSharedConfig() {
+ this.cmdSharedOperation("add");
+ }
+
+ editSharedConfig() {
+ this.cmdSharedOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ sharedConfigRows,
+ configEntryModalShow,
+ configName,
+ newEntry,
+ type,
+ sharedConfigEntry,
+ sharedConfigListModalShow,
+ sharedConfigEntryModalShow,
+ sharedHostname,
+ sharedPort,
+ newSharedEntry,
+ attributes
+ } = this.state;
+
+ const modalConfigFields = {
+ prefix: {
+ name: "Prefix",
+ value: this.state.prefix,
+ help:
+ "Defines a prefix that can be prepended to the generated number values for the attribute (dnaPrefix)"
+ },
+ nextValue: {
+ name: "Next Value",
+ value: this.state.nextValue,
+ help: "Gives the next available number which can be assigned (dnaNextValue)"
+ },
+ maxValue: {
+ name: "Max Value",
+ value: this.state.maxValue,
+ help: "Sets the maximum value that can be assigned for the range (dnaMaxValue)"
+ },
+ interval: {
+ name: "Interval",
+ value: this.state.interval,
+ help:
+ "Sets an interval to use to increment through numbers in a range (dnaInterval)"
+ },
+ magicRegen: {
+ name: "Magic Regen",
+ value: this.state.magicRegen,
+ help:
+ "Sets a user-defined value that instructs the plug-in to assign a new value for the entry (dnaMagicRegen)"
+ },
+ filter: {
+ name: "Filter",
+ value: this.state.filter,
+ help:
+ "Sets an LDAP filter to use to search for and identify the entries to which to apply the distributed numeric assignment range (dnaFilter)"
+ },
+ scope: {
+ name: "Scope",
+ value: this.state.scope,
+ help:
+ "Sets the base DN to search for entries to which to apply the distributed numeric assignment (dnaScope)"
+ },
+ remoteBindDN: {
+ name: "Remote Bind DN",
+ value: this.state.remoteBindDN,
+ help: "Specifies the Replication Manager DN (dnaRemoteBindDN)"
+ },
+ remoteBindCred: {
+ name: "Remote Bind Credentials",
+ value: this.state.remoteBindCred,
+ help: "Specifies the Replication Manager's password (dnaRemoteBindCred)"
+ },
+ threshold: {
+ name: "Threshold",
+ value: this.state.threshold,
+ help:
+ "Sets a threshold of remaining available numbers in the range. When the server hits the threshold, it sends a request for a new range (dnaThreshold)"
+ },
+ nextRange: {
+ name: "Next Range",
+ value: this.state.nextRange,
+ help:
+ "Defines the next range to use when the current range is exhausted (dnaNextRange)"
+ },
+ rangeRequesTimeout: {
+ name: "Range Request Timeout",
+ value: this.state.rangeRequesTimeout,
+ help:
+ "Sets a timeout period, in seconds, for range requests so that the server does not stall waiting on a new range from one server and can request a range from a new server (dnaRangeRequestTimeout)"
+ }
+ };
+
+ const modalSharedConfigFields = {
+ sharedSecurePort: {
+ name: "Secure Port",
+ value: this.state.sharedSecurePort,
+ help:
+ "Gives the secure (TLS) port number to use to connect to the host identified in dnaHostname (dnaSecurePortNum)"
+ },
+ sharedRemainingValues: {
+ name: "Remaining Values",
+ value: this.state.sharedRemainingValues,
+ help: "Specifies the remote bind method (dnaRemoteBindMethod)"
+ },
+ sharedRemoteBindMethod: {
+ name: "Remote Bind Method",
+ value: this.state.sharedRemoteBindMethod,
+ help: "Specifies the remote connection protocol (dnaRemoteConnProtocol)"
+ },
+ sharedRemoteConnProtocol: {
+ name: "Remote Connection Protocol",
+ value: this.state.sharedRemoteConnProtocol,
+ help:
+ "Contains the number of values that are remaining and available to a server to assign to entries (dnaRemainingValues)"
+ }
+ };
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} DNA Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="configName" controlId="configName">
+ <Col sm={4}>
+ <ControlLabel>Config Name</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="sharedConfigEntry"
+ controlId="sharedConfigEntry"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Defines a shared identity that the servers can use to transfer ranges to one another (dnaSharedCfgDN)"
+ >
+ Shared Config Entry
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ type="text"
+ value={sharedConfigEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.showConfirmSharedSave}
+ >
+ Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ <FormGroup key="type" controlId="type">
+ <Col sm={4}>
+ <ControlLabel title="Sets which attributes have unique numbers being generated for them (dnaType)">
+ Type
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ type: value
+ });
+ }}
+ selected={type}
+ options={attributes}
+ newSelectionPrefix="Add a attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalConfigFields).map(([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={4}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={sharedConfigListModalShow} onHide={this.closeSharedListModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeSharedListModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>List DNA Plugin Shared Config Entries</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={9}>
+ <ControlLabel>DNA Config: </ControlLabel> {configName}
+ </Col>
+ <Col sm={9}>
+ <ControlLabel>Shared Config Base DN:</ControlLabel>{" "}
+ {sharedConfigEntry}
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <DNASharedTable
+ rows={sharedConfigRows}
+ editConfig={this.showEditSharedConfigModal}
+ deleteConfig={this.deleteSharedConfig}
+ />
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeSharedListModal}
+ >
+ Close
+ </Button>
+ <Button bsStyle="primary" onClick={this.showAddSharedConfigModal}>
+ Add Config
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={sharedConfigEntryModalShow} onHide={this.closeSharedModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeSharedModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Manage DNA Plugin Shared Config Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="sharedHostname" controlId="sharedHostname">
+ <Col sm={4}>
+ <ControlLabel>Config Hostname</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={sharedHostname}
+ onChange={this.handleFieldChange}
+ disabled={!newSharedEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="sharedPort" controlId="sharedPort">
+ <Col sm={4}>
+ <ControlLabel>Config Port</ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ required
+ type="text"
+ value={sharedPort}
+ onChange={this.handleFieldChange}
+ disabled={!newSharedEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalSharedConfigFields).map(
+ ([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={4}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeSharedModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={
+ newSharedEntry ? this.addSharedConfig : this.editSharedConfig
+ }
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,6 +1118,31 @@ class DNA extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
+ >
+ <Row>
+ <Col sm={9}>
+ <DNATable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
+ <ConfirmPopup
+ showModal={this.state.showConfirmSharedSave}
+ closeHandler={this.closeConfirmSharedSave}
+ actionFunc={this.openSharedListModal}
+ actionParam={this.state.sharedConfigEntry}
+ msg="The current entry state will be saved to the directory. Also, make sure you've set the shared config entry area."
+ msgContent="Are you ready to proceed?"
/>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
index 5216b15..8a7743c 100644
--- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
@@ -5,9 +5,425 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class LinkedAttributes extends React.Component {
+ componentWillMount() {
+ this.loadConfigs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configRows: [],
+ attributes: [],
+
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: "",
+
+ newEntry: false,
+ showConfigModal: false,
+ showConfirmDeleteConfig: false
+ };
+
+ this.getAttributes = this.getAttributes.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ // Get all the attributes and matching rules now
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "linked-attr",
+ "list"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Linked Attributes Plugin configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: ""
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Linked Attributes Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0],
+ linkType:
+ configEntry["linktype"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["linktype"][0],
+ label: configEntry["linktype"][0]
+ }
+ ],
+ managedType:
+ configEntry["managedtype"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["managedtype"][0],
+ label: configEntry["managedtype"][0]
+ }
+ ],
+ linkScope:
+ configEntry["linkscope"] === undefined
+ ? ""
+ : configEntry["linkscope"][0]
+ });
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configName: "",
+ linkType: [],
+ managedType: [],
+ linkScope: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const { configName, linkType, managedType, linkScope } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ configName,
+ action,
+ "--link-scope",
+ linkScope || action == "add" ? linkScope : "delete"
+ ];
+
+ cmd = [...cmd, "--link-type"];
+ if (linkType.length != 0) {
+ cmd = [...cmd, linkType[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--managed-type"];
+ if (managedType.length != 0) {
+ cmd = [...cmd, managedType[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "linkedAttributesOperation",
+ `Do the ${action} operation on the Linked Attributes Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("linkedAttributesOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "linked-attr",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Linked Attributes Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newEntry ? "Add" : "Edit"} Linked Attributes Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configName">
+ <Col sm={3}>
+ <ControlLabel title="The Linked Attributes configuration name">
+ Config Name
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="linkType">
+ <Col sm={3}>
+ <ControlLabel title="Sets the attribute that is managed manually by administrators (linkType)">
+ Link Type
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ linkType: value
+ });
+ }}
+ selected={linkType}
+ options={attributes}
+ newSelectionPrefix="Add a managed attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="managedType">
+ <Col sm={3}>
+ <ControlLabel title="Sets the attribute that is created dynamically by the plugin (managedType)">
+ Managed Type
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ managedType: value
+ });
+ }}
+ selected={managedType}
+ options={attributes}
+ newSelectionPrefix="Add a dynamic attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="linkScope">
+ <Col sm={3}>
+ <ControlLabel title="Sets the base DN that restricts the plugin to a specific part of the directory tree (linkScope)">
+ Link Scope
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={linkScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newEntry ? this.addConfig : this.editConfig}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +434,24 @@ class LinkedAttributes extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <LinkedAttributesTable
+ rows={this.state.configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
index 11771b7..5433535 100644
--- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
@@ -1,24 +1,901 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
-import PropTypes from "prop-types";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { ManagedEntriesTable } from "./pluginTables.jsx";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import PropTypes from "prop-types";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class ManagedEntries extends React.Component {
+ componentWillMount() {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ configArea: "",
+ configRows: [],
+ attributes: [],
+
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: "",
+
+ templateDN: "",
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: [],
+
+ newConfigEntry: false,
+ configEntryModalShow: false,
+ newTemplateEntry: false,
+ templateEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.updateFields = this.updateFields.bind(this);
+ this.loadConfigs = this.loadConfigs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.showEditConfigModal = this.showEditConfigModal.bind(this);
+ this.showAddConfigModal = this.showAddConfigModal.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+
+ this.openTemplateModal = this.openTemplateModal.bind(this);
+ this.closeTemplateModal = this.closeTemplateModal.bind(this);
+ this.cmdTemplateOperation = this.cmdTemplateOperation.bind(this);
+ this.deleteTemplate = this.deleteTemplate.bind(this);
+ this.addTemplate = this.addTemplate.bind(this);
+ this.editTemplate = this.editTemplate.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ loadConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "list",
+ "configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadConfigs", "Get Managed Entries Plugin configs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ configRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditConfigModal(rowData) {
+ this.openModal(rowData.cn[0]);
+ }
+
+ showAddConfigModal(rowData) {
+ this.openModal();
+ }
+
+ openModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: true,
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: ""
+ });
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Managed Entries Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: false,
+ configName: configEntry["cn"] === undefined ? "" : configEntry["cn"][0],
+ originScope:
+ configEntry["originscope"] === undefined
+ ? ""
+ : configEntry["originscope"][0],
+ originFilter:
+ configEntry["originfilter"] === undefined
+ ? ""
+ : configEntry["originfilter"][0],
+ managedBase:
+ configEntry["managedbase"] === undefined
+ ? ""
+ : configEntry["managedbase"][0],
+ managedTemplate:
+ configEntry["managedtemplate"] === undefined
+ ? ""
+ : configEntry["managedtemplate"][0]
+ });
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newConfigEntry: true,
+ configName: "",
+ originScope: "",
+ originFilter: "",
+ managedBase: "",
+ managedTemplate: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ deleteConfig(rowData) {
+ let configName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ configName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Managed Entries Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configName} was successfully deleted`
+ );
+ this.loadConfigs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ cmdOperation(action) {
+ const { configName, originScope, originFilter, managedBase, managedTemplate } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "config",
+ configName,
+ action,
+ "--scope",
+ originScope || action == "add" ? originScope : "delete",
+ "--filter",
+ originFilter || action == "add" ? originFilter : "delete",
+ "--managed-base",
+ managedBase || action == "add" ? managedBase : "delete",
+ "--managed-template",
+ managedTemplate || action == "add" ? managedTemplate : "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("cmdOperation", `Do the ${action} operation on the Managed Entries Plugin`, cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("cmdOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${configName}" entry`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.loadConfigs();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Managed Entries");
+
+ this.setState({
+ configArea:
+ pluginRow["nsslapd-pluginConfigArea"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginConfigArea"][0]
+ });
+ this.loadConfigs();
+ }
+ }
+
+ openTemplateModal() {
+ this.getAttributes();
+ if (!this.state.managedTemplate) {
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: true,
+ templateDN: "",
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: []
+ });
+ } else {
+ let templateMappedAttrObjectList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ this.state.managedTemplate,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openTemplateModal", "Fetch the Managed Entries Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let configEntry = JSON.parse(content).attrs;
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: false,
+ templateDN: this.state.managedTemplate,
+ templateRDNAttr:
+ configEntry["meprdnattr"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["meprdnattr"][0],
+ label: configEntry["meprdnattr"][0]
+ }
+ ],
+ templateStaticAttr:
+ configEntry["mepstaticattr"] === undefined
+ ? []
+ : [
+ {
+ id: configEntry["mepstaticattr"][0],
+ label: configEntry["mepstaticattr"][0]
+ }
+ ]
+ });
+ if (configEntry["mepmappedattr"] === undefined) {
+ this.setState({ templateMappedAttr: [] });
+ } else {
+ for (let value of configEntry["mepmappedattr"]) {
+ templateMappedAttrObjectList = [
+ ...templateMappedAttrObjectList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ templateMappedAttr: templateMappedAttrObjectList
+ });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ templateEntryModalShow: true,
+ newTemplateEntry: true,
+ templateDN: this.state.managedTemplate,
+ templateRDNAttr: [],
+ templateStaticAttr: [],
+ templateMappedAttr: []
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeTemplateModal() {
+ this.setState({ templateEntryModalShow: false });
+ }
+
+ cmdTemplateOperation(action) {
+ const { templateDN, templateRDNAttr, templateStaticAttr, templateMappedAttr } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ templateDN,
+ action
+ ];
+
+ cmd = [...cmd, "--rdn-attr"];
+ if (templateRDNAttr.length != 0) {
+ cmd = [...cmd, templateRDNAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--static-attr"];
+ if (templateStaticAttr.length != 0) {
+ cmd = [...cmd, templateStaticAttr[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ cmd = [...cmd, "--mapped-attr"];
+ if (templateMappedAttr.length != 0) {
+ for (let value of templateMappedAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "managedEntriesOperation",
+ `Do the ${action} operation on the Managed Entries Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("managedEntriesOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${templateDN} was successfully ${action}ed`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteTemplate() {
+ const { templateDN } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "template",
+ templateDN,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Managed Entries Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${templateDN} was successfully deleted`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.closeTemplateModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addTemplate() {
+ this.cmdTemplateOperation("add");
+ }
+
+ editTemplate() {
+ this.cmdTemplateOperation("set");
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ configArea,
+ configRows,
+ attributes,
+ configName,
+ originScope,
+ originFilter,
+ managedBase,
+ managedTemplate,
+ templateDN,
+ templateRDNAttr,
+ templateStaticAttr,
+ templateMappedAttr,
+ newConfigEntry,
+ configEntryModalShow,
+ newTemplateEntry,
+ templateEntryModalShow
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "managed-entries",
+ "set",
+ "--config-area",
+ configArea || "delete"
+ ];
+
+ const modalConfigFields = {
+ originScope: {
+ name: "Scope",
+ value: originScope,
+ help: `Sets the search base DN to use to see which entries the plug-in monitors (originScope)`
+ },
+ originFilter: {
+ name: "Filter",
+ value: originFilter,
+ help: `Sets the search filter to use to search for and identify the entries within the subtree which require a managed entry (originFilter)`
+ },
+ managedBase: {
+ name: "Managed Base",
+ value: managedBase,
+ help: "Sets the subtree under which to create the managed entries (managedBase)"
+ }
+ };
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newConfigEntry ? "Add" : "Edit"} Managed Entries Plugin Config
+ Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="configName" controlId="configName">
+ <Col sm={3}>
+ <ControlLabel>Config Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={configName}
+ onChange={this.handleFieldChange}
+ disabled={!newConfigEntry}
+ />
+ </Col>
+ </FormGroup>
+ {Object.entries(modalConfigFields).map(([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={3}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+ <FormGroup
+ key="managedTemplate"
+ controlId="managedTemplate"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies the template entry to use to create the managed entry (managedTemplate)"
+ >
+ Managed Template
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={managedTemplate}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openTemplateModal}
+ >
+ Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newConfigEntry ? this.addConfig : this.editConfig}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={templateEntryModalShow} onHide={this.closeTemplateModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeTemplateModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Handle Managed Entries Plugin Template Entry</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="templateDN">
+ <Col sm={4}>
+ <ControlLabel title="DN of the template entry">
+ Template DN
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ value={templateDN}
+ onChange={this.handleFieldChange}
+ disabled={!newTemplateEntry}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="templateRDNAttr"
+ controlId="templateRDNAttr"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={4}
+ title="Sets which attribute to use as the naming attribute in the automatically-generated entry (mepRDNAttr)"
+ >
+ RDN Attribute
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ templateRDNAttr: value
+ });
+ }}
+ selected={templateRDNAttr}
+ options={attributes}
+ newSelectionPrefix="Add a RDN attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="templateStaticAttr"
+ controlId="templateStaticAttr"
+ disabled={false}
+ >
+ <Col sm={4}>
+ <ControlLabel title="Sets an attribute with a defined value that must be added to the automatically-generated entry (mepStaticAttr)">
+ Static Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ templateStaticAttr: value
+ });
+ }}
+ selected={templateStaticAttr}
+ options={attributes}
+ newSelectionPrefix="Add a static attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="templateMappedAttr"
+ controlId="templateMappedAttr"
+ disabled={false}
+ >
+ <Col sm={4}>
+ <ControlLabel title="Sets attributes in the Managed Entries template entry which must exist in the generated entry (mepMappedAttr)">
+ Mapped Attributes
+ </ControlLabel>
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ templateMappedAttr: value
+ });
+ }}
+ selected={templateMappedAttr}
+ options={attributes}
+ newSelectionPrefix="Add a mapped attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeTemplateModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteTemplate}
+ disabled={newTemplateEntry}
+ >
+ Delete
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.editTemplate}
+ disabled={newTemplateEntry}
+ >
+ Save
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.addTemplate}
+ disabled={!newTemplateEntry}
+ >
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
cn="Managed Entries"
pluginName="Managed Entries"
cmdName="managed-entries"
+ specificPluginCMD={specificPluginCMD}
savePluginHandler={this.props.savePluginHandler}
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <FormGroup key="configArea" controlId="configArea">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="DN of the shared config entry (nsslapd-pluginConfigArea)"
+ >
+ Shared Config Area
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={configArea}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <Col sm={9}>
+ <ManagedEntriesTable
+ rows={configRows}
+ editConfig={this.showEditConfigModal}
+ deleteConfig={this.deleteConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index d838054..8117da7 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -116,9 +116,10 @@ class MemberOf extends React.Component {
});
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Fixup task for ${this.state.fixupDN} has failed ${err}`
+ `Fixup task for ${this.state.fixupDN} has failed ${errMsg.desc}`
);
this.props.toggleLoadingHandler();
this.setState({
@@ -171,8 +172,13 @@ class MemberOf extends React.Component {
configDN: this.state.memberOfConfigEntry,
configAutoAddOC:
configEntry["memberofautoaddoc"] === undefined
- ? ""
- : configEntry["memberofautoaddoc"][0],
+ ? []
+ : [
+ {
+ id: configEntry["memberofautoaddoc"][0],
+ label: configEntry["memberofautoaddoc"][0]
+ }
+ ],
configAllBackends: !(
configEntry["memberofallbackends"] === undefined ||
configEntry["memberofallbackends"][0] == "off"
@@ -281,6 +287,15 @@ class MemberOf extends React.Component {
configSkipNested ? "on" : "off"
];
+ cmd = [...cmd, "--autoaddoc"];
+ if (configAutoAddOC.length != 0) {
+ cmd = [...cmd, configAutoAddOC[0].label];
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
// Delete attributes if the user set an empty value to the field
cmd = [...cmd, "--attr"];
if (configAttr.length != 0) {
@@ -313,9 +328,10 @@ class MemberOf extends React.Component {
this.props.toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Error during the config entry ${action} operation - ${err}`
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
);
this.props.pluginListHandler();
this.closeModal();
@@ -354,9 +370,10 @@ class MemberOf extends React.Component {
this.props.toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
this.props.addNotification(
"error",
- `Error during the config entry removal operation - ${err}`
+ `Error during the config entry removal operation - ${errMsg.desc}`
);
this.props.pluginListHandler();
this.closeModal();
@@ -442,6 +459,37 @@ class MemberOf extends React.Component {
}
}
+ getObjectClasses() {
+ const oc_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "objectclasses",
+ "list"
+ ];
+ log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);
+ cockpit
+ .spawn(oc_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const ocContent = JSON.parse(content);
+ let ocs = [];
+ for (let content of ocContent["items"]) {
+ ocs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ objectClasses: ocs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get objectClasses - ${errMsg.desc}`);
+ });
+ }
+
render() {
const {
memberOfAttr,
@@ -488,6 +536,13 @@ class MemberOf extends React.Component {
memberOfSkipNested ? "on" : "off"
];
+ specificPluginCMD = [...specificPluginCMD, "--autoaddoc"];
+ if (memberOfAutoAddOC.length != 0) {
+ specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].label];
+ } else {
+ specificPluginCMD = [...specificPluginCMD, "delete"];
+ }
+
// Delete attributes if the user set an empty value to the field
specificPluginCMD = [...specificPluginCMD, "--attr"];
if (memberOfAttr.length != 0) {
@@ -682,6 +737,7 @@ class MemberOf extends React.Component {
id="configAllBackends"
checked={configAllBackends}
onChange={this.handleCheckboxChange}
+ title="Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)"
>
All Backends
</Checkbox>
@@ -707,6 +763,7 @@ class MemberOf extends React.Component {
id="configSkipNested"
checked={configSkipNested}
onChange={this.handleCheckboxChange}
+ title="Specifies wherher to skip nested groups or not (memberOfSkipNested)"
>
Skip Nested
</Checkbox>
@@ -944,7 +1001,10 @@ class MemberOf extends React.Component {
<Col sm={9}>
<Form horizontal>
<FormGroup controlId="memberOfAutoAddOC" disabled={false}>
- <Col sm={3}>
+ <Col
+ sm={3}
+ title="If an entry does not have an object class that allows the memberOf attribute then the memberOf plugin will automatically add the object class listed in the memberOfAutoAddOC parameter"
+ >
<ControlLabel>Auto Add OC</ControlLabel>
</Col>
<Col sm={9}>
diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
index 5b6f76c..5dc1ab6 100644
--- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
@@ -1,14 +1,1129 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ Radio,
+ noop,
+ FormGroup,
+ FormControl,
+ Checkbox,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { PassthroughAuthURLsTable, PassthroughAuthConfigsTable } from "./pluginTables.jsx";
+import PluginBasicPAMConfig from "./pluginBasicConfig.jsx";
import PropTypes from "prop-types";
-import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class PassthroughAuthentication extends React.Component {
+ componentWillMount() {
+ this.loadPAMConfigs();
+ this.loadURLs();
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ pamConfigRows: [],
+ urlRows: [],
+ attributes: [],
+
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: "",
+
+ oldURL: "",
+ urlConnType: "ldap",
+ urlAuthDS: "",
+ urlSubtree: "",
+ urlMaxConns: "3",
+ urlMaxOps: "5",
+ urlTimeout: "300",
+ urlLDVer: "3",
+ urlConnLifeTime: "300",
+ urlStartTLS: false,
+
+ newPAMConfigEntry: false,
+ newURLEntry: false,
+ pamConfigEntryModalShow: false,
+ urlEntryModalShow: false
+ };
+
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+
+ this.loadPAMConfigs = this.loadPAMConfigs.bind(this);
+ this.loadURLs = this.loadURLs.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+
+ this.openPAMModal = this.openPAMModal.bind(this);
+ this.closePAMModal = this.closePAMModal.bind(this);
+ this.showEditPAMConfigModal = this.showEditPAMConfigModal.bind(this);
+ this.showAddPAMConfigModal = this.showAddPAMConfigModal.bind(this);
+ this.cmdPAMOperation = this.cmdPAMOperation.bind(this);
+ this.deletePAMConfig = this.deletePAMConfig.bind(this);
+ this.addPAMConfig = this.addPAMConfig.bind(this);
+ this.editPAMConfig = this.editPAMConfig.bind(this);
+
+ this.openURLModal = this.openURLModal.bind(this);
+ this.closeURLModal = this.closeURLModal.bind(this);
+ this.showEditURLModal = this.showEditURLModal.bind(this);
+ this.showAddURLModal = this.showAddURLModal.bind(this);
+ this.cmdURLOperation = this.cmdURLOperation.bind(this);
+ this.deleteURL = this.deleteURL.bind(this);
+ this.addURL = this.addURL.bind(this);
+ this.editURL = this.editURL.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ loadPAMConfigs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "list",
+ "pam-configs"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadPAMConfigs", "Get PAM Passthough Authentication Plugin pamConfigs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ pamConfigRows: myObject.items.map(item => JSON.parse(item).attrs)
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadPAMConfigs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ loadURLs() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "list",
+ "urls"
+ ];
+ this.props.toggleLoadingHandler();
+ log_cmd("loadURLs", "Get PAM Passthough Authentication Plugin pamConfigs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ this.setState({
+ urlRows: myObject.items
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (err != 0) {
+ console.log("loadURLs failed", errMsg.desc);
+ }
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditPAMConfigModal(rowData) {
+ this.openPAMModal(rowData.cn[0]);
+ }
+
+ showAddPAMConfigModal(rowData) {
+ this.openPAMModal();
+ }
+
+ openPAMModal(name) {
+ this.getAttributes();
+ if (!name) {
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: true,
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: ""
+ });
+ } else {
+ let pamExcludeSuffixList = [];
+ let pamIncludeSuffixList = [];
+ let pamIDAttrList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ name,
+ "show"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "openModal",
+ "Fetch the PAM Passthough Authentication Plugin pamConfig entry",
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let pamConfigEntry = JSON.parse(content).attrs;
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: false,
+ pamConfigName:
+ pamConfigEntry["cn"] === undefined ? "" : pamConfigEntry["cn"][0],
+ pamMissingSuffix:
+ pamConfigEntry["pammissingsuffix"] === undefined
+ ? ""
+ : pamConfigEntry["pammissingsuffix"][0],
+ pamFilter:
+ pamConfigEntry["pamfilter"] === undefined
+ ? ""
+ : pamConfigEntry["pamfilter"][0],
+ pamIDMapMethod:
+ pamConfigEntry["pamidmapmethod"] === undefined
+ ? ""
+ : pamConfigEntry["pamidmapmethod"][0],
+ pamFallback: !(
+ pamConfigEntry["pamfallback"] === undefined ||
+ pamConfigEntry["pamfallback"][0] == "FALSE"
+ ),
+ pamSecure: !(
+ pamConfigEntry["pamsecure"] === undefined ||
+ pamConfigEntry["pamsecure"][0] == "FALSE"
+ ),
+ pamService:
+ pamConfigEntry["pamservice"] === undefined
+ ? ""
+ : pamConfigEntry["pamservice"][0]
+ });
+ if (pamConfigEntry["pamexcludesuffix"] === undefined) {
+ this.setState({ pamExcludeSuffix: [] });
+ } else {
+ for (let value of pamConfigEntry["pamexcludesuffix"]) {
+ pamExcludeSuffixList = [
+ ...pamExcludeSuffixList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ pamExcludeSuffix: pamExcludeSuffixList
+ });
+ }
+
+ if (pamConfigEntry["pamincludesuffix"] === undefined) {
+ this.setState({ pamIncludeSuffix: [] });
+ } else {
+ for (let value of pamConfigEntry["pamincludesuffix"]) {
+ pamIncludeSuffixList = [
+ ...pamIncludeSuffixList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ pamIncludeSuffix: pamIncludeSuffixList
+ });
+ }
+
+ if (pamConfigEntry["pamidattr"] === undefined) {
+ this.setState({ pamIDAttr: [] });
+ } else {
+ for (let value of pamConfigEntry["pamidattr"]) {
+ pamIDAttrList = [...pamIDAttrList, { id: value, label: value }];
+ }
+ this.setState({ pamIDAttr: pamIDAttrList });
+ }
+
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ pamConfigEntryModalShow: true,
+ newPAMConfigEntry: true,
+ pamConfigName: "",
+ pamExcludeSuffix: [],
+ pamIncludeSuffix: [],
+ pamMissingSuffix: "",
+ pamFilter: "",
+ pamIDAttr: [],
+ pamIDMapMethod: "",
+ pamFallback: false,
+ pamSecure: false,
+ pamService: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closePAMModal() {
+ this.setState({ pamConfigEntryModalShow: false });
+ }
+
+ deletePAMConfig(rowData) {
+ let pamConfigName = rowData.cn[0];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ pamConfigName,
+ "delete"
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "deletePAMConfig",
+ "Delete the PAM Passthough Authentication Plugin pamConfig entry",
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deletePAMConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `PAMConfig entry ${pamConfigName} was successfully deleted`
+ );
+ this.loadPAMConfigs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the pamConfig entry removal operation - ${errMsg.desc}`
+ );
+ this.loadPAMConfigs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addPAMConfig() {
+ this.cmdPAMOperation("add");
+ }
+
+ editPAMConfig() {
+ this.cmdPAMOperation("set");
+ }
+
+ cmdPAMOperation(action) {
+ // Save table here too
+ const {
+ pamConfigName,
+ pamExcludeSuffix,
+ pamIncludeSuffix,
+ pamMissingSuffix,
+ pamFilter,
+ pamIDAttr,
+ pamIDMapMethod,
+ pamFallback,
+ pamSecure,
+ pamService
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "pam-config",
+ pamConfigName,
+ action,
+ "--missing-suffix",
+ pamMissingSuffix || action == "add" ? pamMissingSuffix : "delete",
+ "--filter",
+ pamFilter || action == "add" ? pamFilter : "delete",
+ "--id_map_method",
+ pamIDMapMethod || action == "add" ? pamIDMapMethod : "delete",
+ "--fallback",
+ pamFallback ? "TRUE" : "FALSE",
+ "--secure",
+ pamSecure ? "TRUE" : "FALSE",
+ "--service",
+ pamService || action == "add" ? pamService : "delete"
+ ];
+
+ cmd = [...cmd, "--exclude-suffix"];
+ if (pamExcludeSuffix.length != 0) {
+ for (let value of pamExcludeSuffix) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ cmd = [...cmd, "--include-suffix"];
+ if (pamIncludeSuffix.length != 0) {
+ for (let value of pamIncludeSuffix) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ cmd = [...cmd, "--id-attr"];
+ if (pamIDAttr.length != 0) {
+ for (let value of pamIDAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "pamPassthroughAuthOperation",
+ `Do the ${action} operation on the PAM Passthough Authentication Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("pamPassthroughAuthOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${pamConfigName}" entry`
+ );
+ this.loadPAMConfigs();
+ this.closePAMModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the pamConfig entry ${action} operation - ${errMsg.desc}`
+ );
+ this.loadPAMConfigs();
+ this.closePAMModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ showEditURLModal(rowData) {
+ this.openURLModal(rowData.url);
+ }
+
+ showAddURLModal(rowData) {
+ this.openURLModal();
+ }
+
+ openURLModal(url) {
+ if (!url) {
+ this.setState({
+ urlEntryModalShow: true,
+ newURLEntry: true,
+ oldURL: "",
+ urlConnType: "ldap",
+ urlAuthDS: "",
+ urlSubtree: "",
+ urlMaxConns: "3",
+ urlMaxOps: "5",
+ urlTimeout: "300",
+ urlLDVer: "3",
+ urlConnLifeTime: "300",
+ urlStartTLS: false
+ });
+ } else {
+ let link = url.split(" ")[0];
+ let params = url.split(" ")[1];
+ this.setState({
+ urlEntryModalShow: true,
+ oldURL: url,
+ newURLEntry: false,
+ urlConnType: link.split(":")[0],
+ urlAuthDS: link.split("/")[2],
+ urlSubtree: link.split("/")[3],
+ urlMaxConns: params.split(",")[0],
+ urlMaxOps: params.split(",")[1],
+ urlTimeout: params.split(",")[2],
+ urlLDVer: params.split(",")[3],
+ urlConnLifeTime: params.split(",")[4],
+ urlStartTLS: !(params.split(",")[5] == "0")
+ });
+ }
+ }
+
+ closeURLModal() {
+ this.setState({ urlEntryModalShow: false });
+ }
+
+ deleteURL(rowData) {
+ let url = rowData.url;
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "url",
+ "delete",
+ url
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteURL", "Delete the Passthough Authentication Plugin URL entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteURL", "Result", content);
+ this.props.addNotification("success", `URL ${url} was successfully deleted`);
+ this.loadURLs();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the URL removal operation - ${errMsg.desc}`
+ );
+ this.loadURLs();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addURL() {
+ this.cmdURLOperation("add");
+ }
+
+ editURL() {
+ this.cmdURLOperation("modify");
+ }
+
+ cmdURLOperation(action) {
+ const {
+ oldURL,
+ urlConnType,
+ urlAuthDS,
+ urlSubtree,
+ urlMaxConns,
+ urlMaxOps,
+ urlTimeout,
+ urlLDVer,
+ urlConnLifeTime,
+ urlStartTLS
+ } = this.state;
+
+ if (!urlAuthDS || !urlSubtree) {
+ this.props.addNotification(
+ "warning",
+ "Authentication Hostname and Subtree fields are required."
+ );
+ } else {
+ const constructedURL = `${urlConnType}://${urlAuthDS}/${urlSubtree} ${urlMaxConns},${urlMaxOps},${urlTimeout},${urlLDVer},${urlConnLifeTime},${
+ urlStartTLS ? "1" : "0"
+ }`;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "pass-through-auth",
+ "url",
+ action
+ ];
+ if (oldURL != "" && action == "modify") {
+ cmd = [...cmd, oldURL, constructedURL];
+ } else {
+ cmd = [...cmd, constructedURL];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "PassthroughAuthOperation",
+ `Do the ${action} operation on the Passthough Authentication Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("PassthroughAuthOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `The ${action} operation was successfully done on "${constructedURL}" entry`
+ );
+ this.loadURLs();
+ this.closeURLModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the URL ${action} operation - ${errMsg.desc}`
+ );
+ this.loadURLs();
+ this.closeURLModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ urlRows,
+ pamConfigRows,
+ attributes,
+ pamConfigName,
+ pamMissingSuffix,
+ pamExcludeSuffix,
+ pamIncludeSuffix,
+ pamFilter,
+ pamIDAttr,
+ pamIDMapMethod,
+ pamFallback,
+ pamSecure,
+ pamService,
+ urlConnType,
+ urlLDVer,
+ urlAuthDS,
+ urlSubtree,
+ urlMaxConns,
+ urlMaxOps,
+ urlTimeout,
+ urlConnLifeTime,
+ urlStartTLS,
+ newPAMConfigEntry,
+ newURLEntry,
+ pamConfigEntryModalShow,
+ urlEntryModalShow
+ } = this.state;
+
+ const modalPAMConfigFields = {
+ pamFilter: {
+ name: "Filter",
+ value: pamFilter,
+ help: `Sets an LDAP filter to use to identify specific entries within the included suffixes for which to use PAM pass-through authentication (pamFilter)`
+ },
+ pamIDMapMethod: {
+ name: "Map Method",
+ value: pamIDMapMethod,
+ help: `Gives the method to use to map the LDAP bind DN to a PAM identity (pamIDMapMethod)`
+ },
+ pamService: {
+ name: "Service",
+ value: pamService,
+ help: `Contains the service name to pass to PAM (pamService)`
+ }
+ };
+
+ const modalURLFields = {
+ urlAuthDS: {
+ name: "Authentication Hostname",
+ value: urlAuthDS,
+ help: `The authenticating directory host name. The port number of the Directory Server can be given by adding a colon and then the port number. For example, dirserver.example.com:389. If the port number is not specified, the PTA server attempts to connect using either of the standard ports: Port 389 if ldap:// is specified in the URL. Port 636 if ldaps:// is specified in the URL.`
+ },
+ urlSubtree: {
+ name: "Subtree",
+ value: urlSubtree,
+ help: `The pass-through subtree. The PTA Directory Server passes through bind requests to the authenticating Directory Server from all clients whose DN is in this subtree.`
+ },
+ urlMaxConns: {
+ name: "Maximum Number of Connections",
+ value: urlMaxConns,
+ help: `The maximum number of connections the PTA directory can simultaneously open to the authenticating directory.`
+ },
+ urlMaxOps: {
+ name: "Maximum Number of Simultaneous Operations",
+ value: urlMaxOps,
+ help: `The maximum number of simultaneous operations (usually bind requests) the PTA directory can send to the authenticating directory within a single connection.`
+ },
+ urlTimeout: {
+ name: "Timeout",
+ value: urlTimeout,
+ help: `The time limit, in seconds, that the PTA directory waits for a response from the authenticating Directory Server. If this timeout is exceeded, the server returns an error to the client. The default is 300 seconds (five minutes). Specify zero (0) to indicate no time limit should be enforced.`
+ },
+ urlConnLifeTime: {
+ name: "Connection Life Time",
+ value: urlConnLifeTime,
+ help: `The time limit, in seconds, within which a connection may be used.`
+ }
+ };
return (
<div>
- <PluginBasicConfig
+ <Modal show={pamConfigEntryModalShow} onHide={this.closePAMModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closePAMModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newPAMConfigEntry ? "Add" : "Edit"} PAM Passthough Authentication
+ Plugin Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="pamConfigName" controlId="pamConfigName">
+ <Col sm={3}>
+ <ControlLabel>Config Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ required
+ type="text"
+ value={pamConfigName}
+ onChange={this.handleFieldChange}
+ disabled={!newPAMConfigEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamExcludeSuffix"
+ controlId="pamExcludeSuffix"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies a suffix to exclude from PAM authentication (pamExcludeSuffix)"
+ >
+ Exclude Suffix
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ pamExcludeSuffix: values
+ });
+ }}
+ selected={pamExcludeSuffix}
+ options={[""]}
+ newSelectionPrefix="Add a suffix: "
+ placeholder="Type a suffix DN..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamIncludeSuffix"
+ controlId="pamIncludeSuffix"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets a suffix to include for PAM authentication (pamIncludeSuffix)"
+ >
+ Include Suffix
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={values => {
+ this.setState({
+ pamIncludeSuffix: values
+ });
+ }}
+ selected={pamIncludeSuffix}
+ options={[""]}
+ newSelectionPrefix="Add a suffix: "
+ placeholder="Type a suffix DN..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamIDAttr"
+ controlId="pamIDAttr"
+ disabled={false}
+ >
+ <Col sm={3}>
+ <ControlLabel title="Contains the attribute name which is used to hold the PAM user ID (pamIDAttr)">
+ ID Attribute
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ pamIDAttr: value
+ });
+ }}
+ selected={pamIDAttr}
+ options={attributes}
+ newSelectionPrefix="Add an attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="pamMissingSuffix"
+ controlId="pamMissingSuffix"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Identifies how to handle missing include or exclude suffixes (pamMissingSuffix)"
+ >
+ Missing Suffix
+ </Col>
+ <Col sm={9}>
+ <div>
+ <Radio
+ id="pamMissingSuffix"
+ value="ERROR"
+ name="ERROR"
+ inline
+ checked={pamMissingSuffix === "ERROR"}
+ onChange={this.handleFieldChange}
+ >
+ ERROR
+ </Radio>
+ <Radio
+ id="pamMissingSuffix"
+ value="ALLOW"
+ name="ALLOW"
+ inline
+ checked={pamMissingSuffix === "ALLOW"}
+ onChange={this.handleFieldChange}
+ >
+ ALLOW
+ </Radio>
+ <Radio
+ id="pamMissingSuffix"
+ value="IGNORE"
+ name="IGNORE"
+ inline
+ checked={pamMissingSuffix === "IGNORE"}
+ onChange={this.handleFieldChange}
+ >
+ IGNORE
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ {Object.entries(modalPAMConfigFields).map(
+ ([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={3}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ )
+ )}
+
+ <FormGroup key="pamCheckboxes" controlId="pamCheckboxes">
+ <Col sm={4}>
+ <Checkbox
+ id="pamFallback"
+ checked={pamFallback}
+ onChange={this.handleCheckboxChange}
+ title={`Sets whether to fallback to regular LDAP authentication if PAM authentication fails (pamFallback)`}
+ >
+ Fallback
+ </Checkbox>
+ </Col>
+ <Col sm={4}>
+ <Checkbox
+ id="pamSecure"
+ checked={pamSecure}
+ onChange={this.handleCheckboxChange}
+ title="Requires secure TLS connection for PAM authentication (pamSecure)"
+ >
+ Secure
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closePAMModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newPAMConfigEntry ? this.addPAMConfig : this.editPAMConfig}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <Modal show={urlEntryModalShow} onHide={this.closeURLModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeURLModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {newPAMConfigEntry ? "Add" : "Edit"}
+ Passthough Authentication Plugin URL
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup
+ key="urlConnType"
+ controlId="urlConnType"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={5}
+ title="Defines whether TLS is used for communication between the two Directory Servers."
+ >
+ Connection Type
+ </Col>
+ <Col sm={7}>
+ <div>
+ <Radio
+ id="urlConnType"
+ value="ldap"
+ name="ldap"
+ inline
+ checked={urlConnType === "ldap"}
+ onChange={this.handleFieldChange}
+ >
+ ldap
+ </Radio>
+ <Radio
+ id="urlConnType"
+ value="ldaps"
+ name="ldaps"
+ inline
+ checked={urlConnType === "ldaps"}
+ onChange={this.handleFieldChange}
+ >
+ ldaps
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ {Object.entries(modalURLFields).map(([id, content]) => (
+ <FormGroup key={id} controlId={id}>
+ <Col sm={5}>
+ <ControlLabel title={content.help}>
+ {content.name}
+ </ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={content.value}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ ))}
+
+ <FormGroup
+ key="urlLDVer"
+ controlId="urlLDVer"
+ disabled={false}
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={5}
+ title={`The version of the LDAP protocol used to connect to the authenticating directory. Directory Server supports LDAP version 2 and 3. The default is version 3, and Red Hat strongly recommends against using LDAPv2, which is old and will be deprecated.`}
+ >
+ Version
+ </Col>
+ <Col sm={7}>
+ <div>
+ <Radio
+ id="urlLDVer"
+ value="2"
+ name="LDAPv2"
+ inline
+ checked={urlLDVer === "2"}
+ onChange={this.handleFieldChange}
+ >
+ LDAPv2
+ </Radio>
+ <Radio
+ id="urlLDVer"
+ value="3"
+ name="LDAPv3"
+ inline
+ checked={urlLDVer === "3"}
+ onChange={this.handleFieldChange}
+ >
+ LDAPv3
+ </Radio>
+ </div>
+ </Col>
+ </FormGroup>
+ <FormGroup key="urlStartTLS" controlId="urlStartTLS">
+ <Col sm={4}>
+ <Checkbox
+ id="urlStartTLS"
+ checked={urlStartTLS}
+ onChange={this.handleCheckboxChange}
+ title={`A flag of whether to use Start TLS for the connection to the authenticating directory. Start TLS establishes a secure connection over the standard port, so it is useful for connecting using LDAP instead of LDAPS. The TLS server and CA certificates need to be available on both of the servers. To use Start TLS, the LDAP URL must use ldap:, not ldaps:.`}
+ >
+ Enable StartTLS
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup key="resultURL" controlId="resultURL">
+ <Col sm={5}>
+ <ControlLabel title="The URL that will be added or modified after you click 'Save'">
+ Result URL
+ </ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <ControlLabel>
+ {urlConnType}://{urlAuthDS}/{urlSubtree}{" "}
+ {urlMaxConns},{urlMaxOps},{urlTimeout},
+ {urlLDVer},{urlConnLifeTime},
+ {urlStartTLS ? "1" : "0"}
+ </ControlLabel>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeURLModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={newURLEntry ? this.addURL : this.editURL}
+ >
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ <PluginBasicPAMConfig
rows={this.props.rows}
serverId={this.props.serverId}
cn="Pass Through Authentication"
@@ -18,7 +1133,40 @@ class PassthroughAuthentication extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <PassthroughAuthURLsTable
+ rows={urlRows}
+ editConfig={this.showEditURLModal}
+ deleteConfig={this.deleteURL}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddURLModal}
+ >
+ Add URL
+ </Button>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ <PassthroughAuthConfigsTable
+ rows={pamConfigRows}
+ editConfig={this.showEditPAMConfigModal}
+ deleteConfig={this.deletePAMConfig}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.showAddPAMConfigModal}
+ >
+ Add Config
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicPAMConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
index ae37334..e2cb6a7 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
@@ -45,7 +45,8 @@ class PluginBasicConfig extends React.Component {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
};
}
@@ -90,9 +91,10 @@ class PluginBasicConfig extends React.Component {
toggleLoadingHandler();
})
.fail(err => {
+ let errMsg = JSON.parse(err);
addNotification(
"error",
- `Error during ${pluginName} plugin modification - ${err}`
+ `Error during ${pluginName} plugin modification - ${errMsg.desc}`
);
toggleLoadingHandler();
this.setState({ disableSwitch: false });
@@ -118,7 +120,11 @@ class PluginBasicConfig extends React.Component {
currentPluginDependsOnNamed:
pluginRow["nsslapd-plugin-depends-on-named"] === undefined
? ""
- : pluginRow["nsslapd-plugin-depends-on-named"][0]
+ : pluginRow["nsslapd-plugin-depends-on-named"][0],
+ currentPluginPrecedence:
+ pluginRow["nsslapd-pluginprecedence"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginprecedence"][0]
});
}
this.updateSwitch();
@@ -160,6 +166,7 @@ class PluginBasicConfig extends React.Component {
currentPluginDescription,
currentPluginDependsOnType,
currentPluginDependsOnNamed,
+ currentPluginPrecedence,
disableSwitch
} = this.state;
@@ -172,7 +179,8 @@ class PluginBasicConfig extends React.Component {
currentPluginVendor: this.state.currentPluginVendor,
currentPluginVersion: this.state.currentPluginVersion,
currentPluginDescription: this.state.currentPluginDescription,
- currentPluginId: this.state.currentPluginId
+ currentPluginId: this.state.currentPluginId,
+ currentPluginPrecedence: this.state.currentPluginPrecedence
};
return (
<div>
@@ -185,24 +193,26 @@ class PluginBasicConfig extends React.Component {
</ControlLabel>
</h3>
</Col>
- <Col smOffset={1} sm={3}>
- <FormGroup key="switchPluginStatus" controlId="switchPluginStatus">
- <ControlLabel
- className="toolbar-pf-find ds-float-left ds-right-indent"
- >
- Status
- </ControlLabel>
- <Switch
- bsSize="normal"
- title="normal"
- id="bsSize-example"
- value={currentPluginEnabled}
- onChange={() => this.handleSwitchChange(currentPluginEnabled)}
- animate={false}
- disabled={disableSwitch}
- />
- </FormGroup>
- </Col>
+ {this.props.removeSwitch || (
+ <Col smOffset={1} sm={3}>
+ <FormGroup key="switchPluginStatus" controlId="switchPluginStatus">
+ <ControlLabel className="toolbar-pf-find ds-float-left ds-right-indent">
+ Status
+ </ControlLabel>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="bsSize-example"
+ value={currentPluginEnabled}
+ onChange={() =>
+ this.handleSwitchChange(currentPluginEnabled)
+ }
+ animate={false}
+ disabled={disableSwitch}
+ />
+ </FormGroup>
+ </Col>
+ )}
</Row>
</Form>
{this.props.children}
@@ -296,6 +306,7 @@ class PluginBasicConfig extends React.Component {
description: currentPluginDescription,
dependsOnType: currentPluginDependsOnType,
dependsOnNamed: currentPluginDependsOnNamed,
+ precedence: currentPluginPrecedence,
specificPluginCMD: this.props.specificPluginCMD
})
}
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
index 7b21201..9ef5025 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
@@ -34,7 +34,8 @@ class PluginEditModal extends React.Component {
currentPluginVersion,
currentPluginDescription,
currentPluginDependsOnType,
- currentPluginDependsOnNamed
+ currentPluginDependsOnNamed,
+ currentPluginPrecedence
} = this.props.pluginData;
const modalFields = {
currentPluginType: currentPluginType,
@@ -43,7 +44,8 @@ class PluginEditModal extends React.Component {
currentPluginId: currentPluginId,
currentPluginVendor: currentPluginVendor,
currentPluginVersion: currentPluginVersion,
- currentPluginDescription: currentPluginDescription
+ currentPluginDescription: currentPluginDescription,
+ currentPluginPrecedence: currentPluginPrecedence
};
return (
@@ -100,7 +102,7 @@ class PluginEditModal extends React.Component {
controlId="currentPluginDependsOnType"
disabled={false}
>
- <Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={5}>
Plugin Depends On Type
</Col>
<Col sm={6}>
@@ -116,8 +118,8 @@ class PluginEditModal extends React.Component {
controlId="currentPluginDependsOnNamed"
disabled={false}
>
- <Col componentClass={ControlLabel} sm={6}>
- Plugin Depends On Type
+ <Col componentClass={ControlLabel} sm={5}>
+ Plugin Depends On Named
</Col>
<Col sm={6}>
<FormControl
@@ -147,7 +149,8 @@ class PluginEditModal extends React.Component {
version: currentPluginVersion,
description: currentPluginDescription,
dependsOnType: currentPluginDependsOnType,
- dependsOnNamed: currentPluginDependsOnNamed
+ dependsOnNamed: currentPluginDependsOnNamed,
+ precedence: currentPluginPrecedence
})
}
>
@@ -174,7 +177,8 @@ PluginEditModal.propTypes = {
currentPluginVersion: PropTypes.string,
currentPluginDescription: PropTypes.string,
currentPluginDependsOnType: PropTypes.string,
- currentPluginDependsOnNamed: PropTypes.string
+ currentPluginDependsOnNamed: PropTypes.string,
+ currentPluginPrecedence: PropTypes.string
}),
closeHandler: PropTypes.func,
savePluginHandler: PropTypes.func,
@@ -195,7 +199,8 @@ PluginEditModal.defaultProps = {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
},
closeHandler: noop,
savePluginHandler: noop,
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
new file mode 100644
index 0000000..49abfa1
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
@@ -0,0 +1,1688 @@
+import React from "react";
+import {
+ Button,
+ DropdownButton,
+ MenuItem,
+ actionHeaderCellFormatter,
+ sortableHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import { DSTable } from "../dsTable.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+class PluginTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ searchFilterValue: "",
+ fieldsToSearch: ["cn", "nsslapd-pluginType"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Plugin Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginType",
+ header: {
+ label: "Plugin Type",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginEnabled",
+ header: {
+ label: "Enabled",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "Actions",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <Button
+ onClick={() => {
+ this.props.loadModalHandler(rowData);
+ }}
+ >
+ Edit Plugin
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div>
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ rowKey="cn"
+ rows={this.props.rows}
+ toolBarSearchField="Plugins"
+ toolBarDisableLoadingSpinner
+ />
+ </div>
+ );
+ }
+}
+
+PluginTable.propTypes = {
+ rows: PropTypes.array,
+ loadModalHandler: PropTypes.func
+};
+
+PluginTable.defaultProps = {
+ rows: [],
+ loadModalHandler: noop
+};
+
+class AttrUniqConfigTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "uniqueness-attribute-name"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "uniqueness-attribute-name",
+ header: {
+ label: "Attribute",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nsslapd-pluginenabled",
+ header: {
+ label: "Enabled",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AttrUniqConfigTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AttrUniqConfigTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class LinkedAttributesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "linkType", "managedType", "linkScope"],
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "linktype",
+ header: {
+ label: "Link Type",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "managedtype",
+ header: {
+ label: "Managed Type",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "linkscope",
+ header: {
+ label: "Link Scope",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+LinkedAttributesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+LinkedAttributesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class DNATable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "dnanextvalue", "dnafilter", "dnascope"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnascope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnafilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnanextvalue",
+ header: {
+ label: "Next Value",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+DNATable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+DNATable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class DNASharedTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["dnahostname", "dnaportnum", "dnaremainingvalues"],
+
+ columns: [
+ {
+ property: "dnahostname",
+ header: {
+ label: "Hostname",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnaportnum",
+ header: {
+ label: "Port",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "dnaremainingvalues",
+ header: {
+ label: "Remaining Values",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.entrydn[0]}>
+ <DropdownButton
+ id={rowData.entrydn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="entrydn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+DNASharedTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+DNASharedTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class AutoMembershipDefinitionTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Definitions",
+ fieldsToSearch: [
+ "cn",
+ "automemberdefaultgroup",
+ "automemberfilter",
+ "automembergroupingattr",
+ "automemberscope"
+ ],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Definition Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberdefaultgroup",
+ header: {
+ label: "Default Group",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberscope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Definition
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Definition
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AutoMembershipDefinitionTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AutoMembershipDefinitionTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class AutoMembershipRegexTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: [
+ "cn",
+ "automemberexclusiveregex",
+ "automemberinclusiveregex",
+ "automembertargetgroup"
+ ],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberexclusiveregex",
+ header: {
+ label: "Exclusive Regex",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automemberinclusiveregex",
+ header: {
+ label: "Inclusive Regex",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "automembertargetgroup",
+ header: {
+ label: "Target Group",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Regex
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Regex
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+AutoMembershipRegexTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+AutoMembershipRegexTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class ManagedEntriesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "originscope", "originfilter", "managedbase"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "originscope",
+ header: {
+ label: "Scope",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "originfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "managedbase",
+ header: {
+ label: "Managed Base",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+ManagedEntriesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ManagedEntriesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class PassthroughAuthURLsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "URLs",
+ fieldsToSearch: ["url"],
+
+ columns: [
+ {
+ property: "url",
+ header: {
+ label: "URL",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.id[0]}>
+ <DropdownButton
+ id={rowData.id[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit URL
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete URL
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="id"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+PassthroughAuthURLsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+PassthroughAuthURLsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+class PassthroughAuthConfigsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+
+ this.state = {
+ searchField: "Configs",
+ fieldsToSearch: ["cn", "pamfilter", "pamidattr", "pamidmapmethod"],
+
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Config Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamidattr",
+ header: {
+ label: "Attribute",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamidmapmethod",
+ header: {
+ label: "Map Method",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pamfilter",
+ header: {
+ label: "Filter",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton
+ id={rowData.cn[0]}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Config
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Config
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey="cn"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+ );
+ }
+}
+
+PassthroughAuthConfigsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+PassthroughAuthConfigsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+export {
+ PluginTable,
+ AttrUniqConfigTable,
+ LinkedAttributesTable,
+ DNATable,
+ DNASharedTable,
+ AutoMembershipDefinitionTable,
+ AutoMembershipRegexTable,
+ ManagedEntriesTable,
+ PassthroughAuthURLsTable,
+ PassthroughAuthConfigsTable
+};
diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
index 96e8464..bf3deee 100644
--- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
@@ -1,13 +1,612 @@
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ noop,
+ FormGroup,
+ FormControl,
+ Modal,
+ Icon,
+ Row,
+ Col,
+ Form,
+ Button,
+ ControlLabel
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class ReferentialIntegrity extends React.Component {
+ componentWillMount(prevProps) {
+ this.getAttributes();
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ updateDelay: "",
+ membershipAttr: [],
+ entryScope: "",
+ excludeEntryScope: "",
+ containerScope: "",
+ logFile: "",
+ referintConfigEntry: "",
+ configEntryModalShow: false,
+
+ configDN: "",
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: "",
+ newEntry: true,
+
+ attributes: []
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
+ this.openModal = this.openModal.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.addConfig = this.addConfig.bind(this);
+ this.editConfig = this.editConfig.bind(this);
+ this.deleteConfig = this.deleteConfig.bind(this);
+ this.cmdOperation = this.cmdOperation.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ openModal() {
+ this.getAttributes();
+ if (!this.state.referintConfigEntry) {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: "",
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: ""
+ });
+ } else {
+ let membershipAttrList = [];
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ "show",
+ this.state.referintConfigEntry
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("openModal", "Fetch the Referential Integrity Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ let pluginRow = JSON.parse(content).attrs;
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: false,
+ configDN: this.state.referintConfigEntry,
+ configUpdateDelay:
+ pluginRow["referint-update-delay"] === undefined
+ ? ""
+ : pluginRow["referint-update-delay"][0],
+ configEntryScope:
+ pluginRow["nsslapd-pluginentryscope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginentryscope"][0],
+ configExcludeEntryScope:
+ pluginRow["nsslapd-pluginexcludeentryscope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginexcludeentryscope"][0],
+ configContainerScope:
+ pluginRow["nsslapd-plugincontainerscope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-plugincontainerscope"][0],
+ configLogFile:
+ pluginRow["referint-logfile"] === undefined
+ ? ""
+ : pluginRow["referint-logfile"][0]
+ });
+
+ if (pluginRow["referint-membership-attr"] === undefined) {
+ this.setState({ configMembershipAttr: [] });
+ } else {
+ for (let value of pluginRow["referint-membership-attr"]) {
+ membershipAttrList = [
+ ...membershipAttrList,
+ { id: value, label: value }
+ ];
+ }
+ this.setState({
+ configMembershipAttr: membershipAttrList
+ });
+ }
+ this.props.toggleLoadingHandler();
+ })
+ .fail(_ => {
+ this.setState({
+ configEntryModalShow: true,
+ newEntry: true,
+ configDN: this.state.referintConfigEntry,
+ configUpdateDelay: "",
+ configMembershipAttr: [],
+ configEntryScope: "",
+ configExcludeEntryScope: "",
+ configContainerScope: "",
+ configLogFile: ""
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+ }
+
+ closeModal() {
+ this.setState({ configEntryModalShow: false });
+ }
+
+ cmdOperation(action) {
+ const {
+ configDN,
+ configUpdateDelay,
+ configMembershipAttr,
+ configEntryScope,
+ configExcludeEntryScope,
+ configContainerScope,
+ configLogFile
+ } = this.state;
+
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ action,
+ configDN,
+ "--update-delay",
+ configUpdateDelay || action == "add" ? configUpdateDelay : "delete",
+ "--entry-scope",
+ configEntryScope || action == "add" ? configEntryScope : "delete",
+ "--exclude-entry-scope",
+ configExcludeEntryScope || action == "add" ? configExcludeEntryScope : "delete",
+ "--container-scope",
+ configContainerScope || action == "add" ? configContainerScope : "delete",
+ "--log-file",
+ configLogFile || action == "add" ? configLogFile : "delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ cmd = [...cmd, "--membership-attr"];
+ if (configMembershipAttr.length != 0) {
+ for (let value of configMembershipAttr) {
+ cmd = [...cmd, value.label];
+ }
+ } else if (action == "add") {
+ cmd = [...cmd, ""];
+ } else {
+ cmd = [...cmd, "delete"];
+ }
+ this.props.toggleLoadingHandler();
+ log_cmd(
+ "referintOperation",
+ `Do the ${action} operation on the Referential Integrity Plugin`,
+ cmd
+ );
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("referintOperation", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${configDN} was successfully ${action}ed`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry ${action} operation - ${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ deleteConfig() {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "referential-integrity",
+ "config-entry",
+ "delete",
+ this.state.configDN
+ ];
+
+ this.props.toggleLoadingHandler();
+ log_cmd("deleteConfig", "Delete the Referential Integrity Plugin config entry", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ console.info("deleteConfig", "Result", content);
+ this.props.addNotification(
+ "success",
+ `Config entry ${this.state.configDN} was successfully deleted`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error during the config entry removal operation - ${errMsg.desc}`
+ );
+ this.props.pluginListHandler();
+ this.closeModal();
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ addConfig() {
+ this.cmdOperation("add");
+ }
+
+ editConfig() {
+ this.cmdOperation("set");
+ }
+
+ updateFields() {
+ let membershipAttrList = [];
+
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(
+ row => row.cn[0] === "referential integrity postoperation"
+ );
+
+ this.setState({
+ updateDelay:
+ pluginRow["referint-update-delay"] === undefined
+ ? ""
+ : pluginRow["referint-update-delay"][0],
+ entryScope:
+ pluginRow["nsslapd-pluginEntryScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginEntryScope"][0],
+ excludeEntryScope:
+ pluginRow["nsslapd-pluginExcludeEntryScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginExcludeEntryScope"][0],
+ containerScope:
+ pluginRow["nsslapd-pluginContainerScope"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginContainerScope"][0],
+ referintConfigEntry:
+ pluginRow["nsslapd-pluginConfigArea"] === undefined
+ ? ""
+ : pluginRow["nsslapd-pluginConfigArea"][0],
+ logFile:
+ pluginRow["referint-logfile"] === undefined
+ ? ""
+ : pluginRow["referint-logfile"][0]
+ });
+
+ if (pluginRow["referint-membership-attr"] === undefined) {
+ this.setState({ membershipAttr: [] });
+ } else {
+ for (let value of pluginRow["referint-membership-attr"]) {
+ membershipAttrList = [...membershipAttrList, { id: value, label: value }];
+ }
+ this.setState({ membershipAttr: membershipAttrList });
+ }
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ updateDelay,
+ membershipAttr,
+ entryScope,
+ excludeEntryScope,
+ containerScope,
+ logFile,
+ referintConfigEntry,
+ attributes,
+ configDN,
+ configUpdateDelay,
+ configMembershipAttr,
+ configEntryScope,
+ configExcludeEntryScope,
+ configContainerScope,
+ configEntryModalShow,
+ configLogFile,
+ newEntry
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "referential-integrity",
+ "set",
+ "--update-delay",
+ updateDelay || "delete",
+ "--entry-scope",
+ entryScope || "delete",
+ "--exclude-entry-scope",
+ excludeEntryScope || "delete",
+ "--container-scope",
+ containerScope || "delete",
+ "--config-entry",
+ referintConfigEntry || "delete",
+ "--log-file",
+ logFile || "delete"
+ ];
+
+ // Delete attributes if the user set an empty value to the field
+ specificPluginCMD = [...specificPluginCMD, "--membership-attr"];
+ if (membershipAttr.length != 0) {
+ for (let value of membershipAttr) {
+ specificPluginCMD = [...specificPluginCMD, value.label];
+ }
+ } else {
+ specificPluginCMD = [...specificPluginCMD, "delete"];
+ }
+
return (
<div>
+ <Modal show={configEntryModalShow} onHide={this.closeModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.closeModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Manage Referential Integrity Plugin Shared Config Entry
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="configDN">
+ <Col sm={3}>
+ <ControlLabel title="The config entry full DN">
+ Config DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configDN}
+ onChange={this.handleFieldChange}
+ disabled={!newEntry}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configUpdateDelay"
+ controlId="configUpdateDelay"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets the update interval. Special values: 0 - The check is performed immediately, -1 - No check is performed (referint-update-delay)"
+ >
+ Update Delay
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configUpdateDelay}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configMembershipAttr"
+ controlId="configMembershipAttr"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies attributes to check for and update (referint-membership-attr)"
+ >
+ Membership Attribute
+ </Col>
+ <Col sm={9}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ configMembershipAttr: value
+ });
+ }}
+ selected={configMembershipAttr}
+ options={attributes}
+ newSelectionPrefix="Add a membership attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configEntryScope"
+ controlId="configEntryScope"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines the subtree in which the plug-in looks for the delete or rename operations of a user entry (nsslapd-pluginEntryScope)"
+ >
+ Entry Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configExcludeEntryScope"
+ controlId="configExcludeEntryScope"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines the subtree in which the plug-in ignores any operations for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)"
+ >
+ Exclude Entry Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configExcludeEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="configContainerScope"
+ controlId="configContainerScope"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Specifies which branch the plug-in searches for the groups to which the user belongs. It only updates groups that are under the specified container branch, and leaves all other groups not updated (nsslapd-pluginContainerScope)"
+ >
+ Container Scope
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configContainerScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="configLogFile" controlId="configLogFile">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title={`Specifies a path to the Referential integrity logfile. For example: /var/log/dirsrv/slapd-${
+ this.props.serverId
+ }/referint`}
+ >
+ Logfile
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={configLogFile}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.closeModal}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.deleteConfig}
+ disabled={newEntry}
+ >
+ Delete
+ </Button>
+ <Button bsStyle="primary" onClick={this.editConfig} disabled={newEntry}>
+ Save
+ </Button>
+ <Button bsStyle="primary" onClick={this.addConfig} disabled={!newEntry}>
+ Add
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +617,148 @@ class ReferentialIntegrity extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="updateDelay" controlId="updateDelay">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets the update interval. Special values: 0 - The check is performed immediately, -1 - No check is performed (referint-update-delay)"
+ >
+ Update Delay
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={updateDelay}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="membershipAttr" controlId="membershipAttr">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies attributes to check for and update (referint-membership-attr)"
+ >
+ Membership Attribute
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ membershipAttr: value
+ });
+ }}
+ selected={membershipAttr}
+ options={attributes}
+ newSelectionPrefix="Add a membership attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="entryScope" controlId="entryScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Defines the subtree in which the plug-in looks for the delete or rename operations of a user entry (nsslapd-pluginEntryScope)"
+ >
+ Entry Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={entryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="excludeEntryScope" controlId="excludeEntryScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Defines the subtree in which the plug-in ignores any operations for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)"
+ >
+ Exclude Entry Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={excludeEntryScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="containerScope" controlId="containerScope">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies which branch the plug-in searches for the groups to which the user belongs. It only updates groups that are under the specified container branch, and leaves all other groups not updated (nsslapd-pluginContainerScope)"
+ >
+ Container Scope
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={containerScope}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="logFile" controlId="logFile">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title={`Specifies a path to the Referential integrity logfile. For example: /var/log/dirsrv/slapd-${
+ this.props.serverId
+ }/referint`}
+ >
+ Logfile
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={logFile}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="referintConfigEntry"
+ controlId="referintConfigEntry"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="The value to set as nsslapd-pluginConfigArea"
+ >
+ Shared Config Entry
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={referintConfigEntry}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={3}>
+ <Button
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.openModal}
+ >
+ Manage
+ </Button>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
index 4e3490b..41eb859 100644
--- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
@@ -5,7 +5,140 @@ import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class RetroChangelog extends React.Component {
+ componentWillMount(prevProps) {
+ this.getAttributes();
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isReplicated: false,
+ attribute: [],
+ directory: "",
+ maxAge: "",
+ excludeSuffix: "",
+ attributes: []
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Retro Changelog Plugin");
+
+ this.setState({
+ isReplicated: !(
+ pluginRow["isReplicated"] === undefined ||
+ pluginRow["isReplicated"][0] == "FALSE"
+ ),
+ attribute:
+ pluginRow["nsslapd-attribute"] === undefined
+ ? []
+ : [
+ {
+ id: pluginRow["nsslapd-attribute"][0],
+ label: pluginRow["nsslapd-attribute"][0]
+ }
+ ],
+ directory:
+ pluginRow["nsslapd-changelogdir"] === undefined
+ ? ""
+ : pluginRow["nsslapd-changelogdir"][0],
+ maxAge:
+ pluginRow["nsslapd-changelogmaxage"] === undefined
+ ? ""
+ : pluginRow["nsslapd-changelogmaxage"][0],
+ excludeSuffix:
+ pluginRow["nsslapd-exclude-suffix"] === undefined
+ ? ""
+ : pluginRow["nsslapd-exclude-suffix"][0]
+ });
+ }
+ }
+
+ getAttributes() {
+ const attr_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
+ const {
+ isReplicated,
+ attribute,
+ directory,
+ maxAge,
+ excludeSuffix,
+ attributes
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "retro-changelog",
+ "set",
+ "--is-replicated",
+ isReplicated ? "TRUE" : "FALSE",
+ "--attribute",
+ attribute.length != 0 ? attribute[0].label : "delete",
+ "--directory",
+ directory || "delete",
+ "--max-age",
+ maxAge || "delete",
+ "--exclude-suffix",
+ excludeSuffix || "delete"
+ ];
+
return (
<div>
<PluginBasicConfig
@@ -18,7 +151,95 @@ class RetroChangelog extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="attribute" controlId="attribute">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies another Directory Server attribute which must be included in the retro changelog entries (nsslapd-attribute)"
+ >
+ Attribute
+ </Col>
+ <Col sm={7}>
+ <Typeahead
+ allowNew
+ onChange={value => {
+ this.setState({
+ attribute: value
+ });
+ }}
+ selected={attribute}
+ options={attributes}
+ newSelectionPrefix="Add an attribute: "
+ placeholder="Type an attribute..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="directory" controlId="directory">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Specifies the name of the directory in which the changelog database is created the first time the plug-in is run"
+ >
+ Directory
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={directory}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="maxAge" controlId="maxAge">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="This attribute specifies the maximum age of any entry in the changelog (nsslapd-changelogmaxage)"
+ >
+ Max Age
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ value={maxAge}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="excludeSuffix" controlId="excludeSuffix">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="This attribute specifies the suffix which will be excluded from the scope of the plugin (nsslapd-exclude-suffix)"
+ >
+ Exclude Suffix
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ type="text"
+ value={excludeSuffix}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="isReplicated"
+ checked={isReplicated}
+ onChange={this.handleCheckboxChange}
+ title="Sets a flag to indicate on a change in the changelog whether the change is newly made on that server or whether it was replicated over from another server (isReplicated)"
+ >
+ Is Replicated
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
index 3e4d820..d1bac89 100644
--- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
@@ -1,10 +1,102 @@
import React from "react";
-import { noop } from "patternfly-react";
+import { noop, FormGroup, FormControl, Row, Col, Form, ControlLabel } from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
import "../../css/ds.css";
class RootDNAccessControl extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ allowHost: [],
+ denyHost: [],
+ allowIP: [],
+ denyIP: [],
+ openTime: "",
+ closeTime: "",
+ daysAllowed: ""
+ };
+
+ this.updateFields = this.updateFields.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ updateFields() {
+ let allowHostList = [];
+ let denyHostList = [];
+ let allowIPList = [];
+ let denyIPList = [];
+
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "RootDN Access Control");
+ this.setState({
+ openTime:
+ pluginRow["rootdn-open-time"] === undefined
+ ? ""
+ : pluginRow["rootdn-open-time"][0],
+ closeTime:
+ pluginRow["rootdn-close-time"] === undefined
+ ? ""
+ : pluginRow["rootdn-close-time"][0],
+ daysAllowed:
+ pluginRow["rootdn-days-allowed"] === undefined
+ ? ""
+ : pluginRow["rootdn-days-allowed"][0]
+ });
+
+ if (pluginRow["rootdn-allow-host"] === undefined) {
+ this.setState({ allowHost: [] });
+ } else {
+ for (let value of pluginRow["rootdn-allow-host"]) {
+ allowHostList = [...allowHostList, { id: value, label: value }];
+ }
+ this.setState({ allowHost: allowHostList });
+ }
+ if (pluginRow["rootdn-deny-host"] === undefined) {
+ this.setState({ denyHost: [] });
+ } else {
+ for (let value of pluginRow["rootdn-deny-host"]) {
+ denyHostList = [...denyHostList, { id: value, label: value }];
+ }
+ this.setState({ denyHost: denyHostList });
+ }
+ if (pluginRow["rootdn-allow-ip"] === undefined) {
+ this.setState({ allowIP: [] });
+ } else {
+ for (let value of pluginRow["rootdn-allow-ip"]) {
+ allowIPList = [...allowIPList, { id: value, label: value }];
+ }
+ this.setState({ allowIP: allowIPList });
+ }
+ if (pluginRow["rootdn-deny-ip"] === undefined) {
+ this.setState({ denyIP: [] });
+ } else {
+ for (let value of pluginRow["rootdn-deny-ip"]) {
+ denyIPList = [...denyIPList, { id: value, label: value }];
+ }
+ this.setState({ denyIP: denyIPList });
+ }
+ }
+ }
+
render() {
return (
<div>
@@ -18,7 +110,158 @@ class RootDNAccessControl extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup key="allowHost" controlId="allowHost">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what hosts, by fully-qualified domain name, the root user is allowed to use to access the Directory Server. Any hosts not listed are implicitly denied (rootdn-allow-host)"
+ >
+ Allow Host
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ allowHost: value
+ });
+ }}
+ selected={allowHost}
+ options={[]}
+ newSelectionPrefix="Add a host to allow: "
+ placeholder="Type a hostname (wild cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="denyHost" controlId="denyHost">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what hosts, by fully-qualified domain name, the root user is not allowed to use to access the Directory Server Any hosts not listed are implicitly allowed (rootdn-deny-host). If an host address is listed in both the rootdn-allow-host and rootdn-deny-host attributes, it is denied access."
+ >
+ Deny Host
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ denyHost: value
+ });
+ }}
+ selected={denyHost}
+ options={[]}
+ newSelectionPrefix="Add a host to deny: "
+ placeholder="Type a hostname (wild cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="allowIP" controlId="allowIP">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what IP addresses, either IPv4 or IPv6, for machines the root user is allowed to use to access the Directory Server Any IP addresses not listed are implicitly denied (rootdn-allow-ip)"
+ >
+ Allow IP address
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ allowIP: value
+ });
+ }}
+ selected={allowIP}
+ options={[]}
+ newSelectionPrefix="Add an IP address to allow: "
+ placeholder="Type an IP address (wild cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="denyIP" controlId="denyIP">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets what IP addresses, either IPv4 or IPv6, for machines the root user is not allowed to use to access the Directory Server. Any IP addresses not listed are implicitly allowed (rootdn-deny-ip) If an IP address is listed in both the rootdn-allow-ip and rootdn-deny-ip attributes, it is denied access."
+ >
+ Deny IP address
+ </Col>
+ <Col sm={6}>
+ <Typeahead
+ allowNew
+ multiple
+ onChange={value => {
+ this.setState({
+ denyIP: value
+ });
+ }}
+ selected={denyIP}
+ options={[]}
+ newSelectionPrefix="Add an IP address to deny: "
+ placeholder="Type an IP address (wild cards are allowed)..."
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="openTime" controlId="openTime">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets part of a time period or range when the root user is allowed to access the Directory Server. This sets when the time-based access begins (rootdn-open-time)"
+ >
+ Open Time
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={openTime}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="closeTime" controlId="closeTime">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Sets part of a time period or range when the root user is allowed to access the Directory Server. This sets when the time-based access ends (rootdn-close-time)"
+ >
+ Close Time
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={closeTime}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup key="daysAllowed" controlId="daysAllowed">
+ <Col
+ componentClass={ControlLabel}
+ sm={2}
+ title="Gives a comma-separated list of what days the root user is allowed to use to access the Directory Server. Any days listed are implicitly denied (rootdn-days-allowed)"
+ >
+ Days Allowed
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ value={daysAllowed}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx b/src/cockpit/389-console/src/lib/plugins/usn.jsx
index bb4d321..08d7beb 100644
--- a/src/cockpit/389-console/src/lib/plugins/usn.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx
@@ -1,13 +1,284 @@
+import cockpit from "cockpit";
import React from "react";
-import { noop } from "patternfly-react";
+import {
+ Icon,
+ Modal,
+ Button,
+ Row,
+ Col,
+ Form,
+ Switch,
+ noop,
+ FormGroup,
+ FormControl,
+ ControlLabel
+} from "patternfly-react";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class USN extends React.Component {
+ componentWillMount() {
+ this.updateSwitch();
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.runCleanup = this.runCleanup.bind(this);
+ this.toggleCleanupModal = this.toggleCleanupModal.bind(this);
+ this.updateSwitch = this.updateSwitch.bind(this);
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
+
+ this.state = {
+ globalMode: false,
+ disableSwitch: false,
+ cleanupModalShow: false,
+ cleanupSuffix: "",
+ cleanupBackend: "",
+ cleanupMaxUSN: ""
+ };
+ }
+
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
+ handleSwitchChange(value) {
+ const { serverId, addNotification, toggleLoadingHandler } = this.props;
+ const new_status = this.state.globalMode ? "off" : "on";
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + serverId + ".socket",
+ "plugin",
+ "usn",
+ "global",
+ new_status
+ ];
+
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: true });
+ log_cmd("handleSwitchChange", "Switch global USN mode from the USN plugin tab", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ console.info("savePlugin", "Result", content);
+ this.updateSwitch();
+ addNotification(
+ "success",
+ `Global USN mode was successfully set to ${new_status}.`
+ );
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: false });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ addNotification(
+ "error",
+ `Error during global USN mode modification - ${errMsg.desc}`
+ );
+ toggleLoadingHandler();
+ this.setState({ disableSwitch: false });
+ });
+ }
+
+ updateSwitch() {
+ const cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config",
+ "get",
+ "nsslapd-entryusn-global"
+ ];
+
+ this.setState({
+ disableSwitch: true
+ });
+ this.props.toggleLoadingHandler();
+ log_cmd("updateSwitch", "Get global USN status", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let myObject = JSON.parse(content);
+ let usnGlobalAttr = myObject.attrs["nsslapd-entryusn-global"][0];
+ this.setState({
+ globalMode: !(usnGlobalAttr == "off")
+ });
+ this.setState({
+ disableSwitch: false
+ });
+ this.props.toggleLoadingHandler();
+ })
+ .fail(err => {
+ if (err != 0) {
+ let errMsg = JSON.parse(err);
+ console.log("Get global USN failed", errMsg.desc);
+ }
+ this.setState({
+ disableSwitch: false
+ });
+ this.props.toggleLoadingHandler();
+ });
+ }
+
+ toggleCleanupModal() {
+ this.setState(prevState => ({
+ cleanupModalShow: !prevState.cleanupModalShow,
+ cleanupSuffix: "",
+ cleanupBackend: "",
+ cleanupMaxUSN: ""
+ }));
+ }
+
+ runCleanup() {
+ if (!this.state.cleanupSuffix && !this.state.cleanupBackend) {
+ this.props.addNotification("warning", "Suffix or backend name is required.");
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "usn",
+ "cleanup"
+ ];
+
+ if (this.state.cleanupSuffix) {
+ cmd = [...cmd, "--suffix", this.state.cleanupSuffix];
+ }
+ if (this.state.cleanupBackend) {
+ cmd = [...cmd, "--backend", this.state.cleanupBackend];
+ }
+ if (this.state.cleanupMaxUSN) {
+ cmd = [...cmd, "--max-usn", this.state.cleanupMaxUSN];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd("runCleanup", "Run cleanup USN tombstones", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Cleanup USN Tombstones task was successfull`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ cleanupModalShow: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Cleanup USN Tombstones task has failed ${errMsg.desc}`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ cleanupModalShow: false
+ });
+ });
+ }
+ }
+
render() {
+ const {
+ globalMode,
+ disableSwitch,
+ cleanupModalShow,
+ cleanupSuffix,
+ cleanupBackend,
+ cleanupMaxUSN
+ } = this.state;
+
return (
<div>
+ <Modal show={cleanupModalShow} onHide={this.toggleCleanupModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.toggleCleanupModal}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Fixup MemberOf Task</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="cleanupSuffix" key="cleanupSuffix">
+ <Col sm={3}>
+ <ControlLabel title="Gives the suffix or subtree in the Directory Server to run the cleanup operation against">
+ Cleanup Suffix
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupSuffix}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="cleanupBackend" key="cleanupBackend">
+ <Col sm={3}>
+ <ControlLabel title="Gives the Directory Server instance back end, or database, to run the cleanup operation against">
+ Cleanup Backend
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupBackend}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="cleanupMaxUSN" key="cleanupMaxUSN">
+ <Col sm={3}>
+ <ControlLabel title="Gives the highest USN value to delete when removing tombstone entries. All tombstone entries up to and including that number are deleted. Tombstone entries with higher USN values (that means more recent entries) are not deleted">
+ Cleanup Max USN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={cleanupMaxUSN}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.toggleCleanupModal}
+ >
+ Cancel
+ </Button>
+ <Button bsStyle="primary" onClick={this.runCleanup}>
+ Run
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -18,7 +289,44 @@ class USN extends React.Component {
pluginListHandler={this.props.pluginListHandler}
addNotification={this.props.addNotification}
toggleLoadingHandler={this.props.toggleLoadingHandler}
- />
+ >
+ <Row>
+ <Col sm={9}>
+ <FormGroup key="globalMode" controlId="globalMode">
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Defines if the USN plug-in assigns unique update sequence numbers (USN) across all back end databases or to each database individually"
+ >
+ USN Global
+ </Col>
+ <Col sm={3}>
+ <Switch
+ bsSize="normal"
+ title="normal"
+ id="bsSize-example"
+ value={globalMode}
+ onChange={() => this.handleSwitchChange(globalMode)}
+ animate={false}
+ disabled={disableSwitch}
+ />
+ </Col>
+ </FormGroup>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ <Button
+ className="ds-margin-top"
+ bsSize="large"
+ bsStyle="primary"
+ onClick={this.toggleCleanupModal}
+ >
+ Run Fixup Task
+ </Button>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/plugins/winsync.jsx b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
new file mode 100644
index 0000000..39be7a9
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
@@ -0,0 +1,234 @@
+import React from "react";
+import { Row, Col, Form, noop, FormGroup, Checkbox, ControlLabel } from "patternfly-react";
+import PropTypes from "prop-types";
+import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import "../../css/ds.css";
+
+class WinSync extends React.Component {
+ componentWillMount(prevProps) {
+ this.updateFields();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.rows !== prevProps.rows) {
+ this.updateFields();
+ }
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.updateFields = this.updateFields.bind(this);
+
+ this.state = {
+ posixWinsyncCreateMemberOfTask: false,
+ posixWinsyncLowerCaseUID: false,
+ posixWinsyncMapMemberUID: false,
+ posixWinsyncMapNestedGrouping: false,
+ posixWinsyncMsSFUSchema: false
+ };
+ }
+
+ handleCheckboxChange(e) {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+
+ updateFields() {
+ if (this.props.rows.length > 0) {
+ const pluginRow = this.props.rows.find(row => row.cn[0] === "Posix Winsync API");
+
+ this.setState({
+ posixWinsyncCreateMemberOfTask: !(
+ pluginRow["posixwinsynccreatememberoftask"] === undefined ||
+ pluginRow["posixwinsynccreatememberoftask"][0] == "false"
+ ),
+ posixWinsyncLowerCaseUID: !(
+ pluginRow["posixwinsynclowercaseuid"] === undefined ||
+ pluginRow["posixwinsynclowercaseuid"][0] == "false"
+ ),
+ posixWinsyncMapMemberUID: !(
+ pluginRow["posixwinsyncmapmemberuid"] === undefined ||
+ pluginRow["posixwinsyncmapmemberuid"][0] == "false"
+ ),
+ posixWinsyncMapNestedGrouping: !(
+ pluginRow["posixwinsyncmapnestedgrouping"] === undefined ||
+ pluginRow["posixwinsyncmapnestedgrouping"][0] == "false"
+ ),
+ posixWinsyncMsSFUSchema: !(
+ pluginRow["posixwinsyncmssfuschema"] === undefined ||
+ pluginRow["posixwinsyncmssfuschema"][0] == "false"
+ )
+ });
+ }
+ }
+
+ render() {
+ const {
+ posixWinsyncCreateMemberOfTask,
+ posixWinsyncLowerCaseUID,
+ posixWinsyncMapMemberUID,
+ posixWinsyncMapNestedGrouping,
+ posixWinsyncMsSFUSchema
+ } = this.state;
+
+ let specificPluginCMD = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "plugin",
+ "posix-winsync",
+ "set",
+ "--create-memberof-task",
+ posixWinsyncCreateMemberOfTask ? "true" : "false",
+ "--lower-case-uid",
+ posixWinsyncLowerCaseUID ? "true" : "false",
+ "--map-member-uid",
+ posixWinsyncMapMemberUID ? "true" : "false",
+ "--map-nested-grouping",
+ posixWinsyncMapNestedGrouping ? "true" : "false",
+ "--ms-sfu-schema",
+ posixWinsyncMsSFUSchema ? "true" : "false"
+ ];
+ return (
+ <div>
+ <PluginBasicConfig
+ rows={this.props.rows}
+ serverId={this.props.serverId}
+ cn="Posix Winsync API"
+ pluginName="Posix Winsync API"
+ cmdName="posix-winsync"
+ specificPluginCMD={specificPluginCMD}
+ savePluginHandler={this.props.savePluginHandler}
+ pluginListHandler={this.props.pluginListHandler}
+ addNotification={this.props.addNotification}
+ toggleLoadingHandler={this.props.toggleLoadingHandler}
+ >
+ <Row>
+ <Col sm={9}>
+ <Form horizontal>
+ <FormGroup
+ key="posixWinsyncCreateMemberOfTask"
+ controlId="posixWinsyncCreateMemberOfTask"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to run the memberOf fix-up task immediately after a sync run in order to update group memberships for synced users"
+ >
+ Create MemberOf Task
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncCreateMemberOfTask"
+ checked={posixWinsyncCreateMemberOfTask}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncLowerCaseUID"
+ controlId="posixWinsyncLowerCaseUID"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to store (and, if necessary, convert) the UID value in the memberUID attribute in lower case"
+ >
+ Lower Case UID
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncLowerCaseUID"
+ checked={posixWinsyncLowerCaseUID}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMapMemberUID"
+ controlId="posixWinsyncMapMemberUID"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to map the memberUID attribute in an Active Directory group to the uniqueMember attribute in a Directory Server group"
+ >
+ Map Member UID
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMapMemberUID"
+ checked={posixWinsyncMapMemberUID}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMapNestedGrouping"
+ controlId="posixWinsyncMapNestedGrouping"
+ >
+ <Col
+ title="Manages if nested groups are updated when memberUID attributes in an Active Directory POSIX group change"
+ componentClass={ControlLabel}
+ sm={3}
+ >
+ Map Nested Grouping
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMapNestedGrouping"
+ checked={posixWinsyncMapNestedGrouping}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ key="posixWinsyncMsSFUSchema"
+ controlId="posixWinsyncMsSFUSchema"
+ >
+ <Col
+ componentClass={ControlLabel}
+ sm={3}
+ title="Sets whether to the older Microsoft System Services for Unix 3.0 (msSFU30) schema when syncing Posix attributes from Active Directory"
+ >
+ Microsoft System Services for Unix 3.0 (msSFU30) schema
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="posixWinsyncMsSFUSchema"
+ checked={posixWinsyncMsSFUSchema}
+ onChange={this.handleCheckboxChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </PluginBasicConfig>
+ </div>
+ );
+ }
+}
+
+WinSync.propTypes = {
+ rows: PropTypes.array,
+ serverId: PropTypes.string,
+ savePluginHandler: PropTypes.func,
+ pluginListHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ toggleLoadingHandler: PropTypes.func
+};
+
+WinSync.defaultProps = {
+ rows: [],
+ serverId: "",
+ savePluginHandler: noop,
+ pluginListHandler: noop,
+ addNotification: noop,
+ toggleLoadingHandler: noop
+};
+
+export default WinSync;
diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx
index ac9f8b7..1dc267c 100644
--- a/src/cockpit/389-console/src/plugins.jsx
+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -71,7 +71,8 @@ export class Plugins extends React.Component {
currentPluginVersion: "",
currentPluginDescription: "",
currentPluginDependsOnType: "",
- currentPluginDependsOnNamed: ""
+ currentPluginDependsOnNamed: "",
+ currentPluginPrecedence: ""
};
}
@@ -157,6 +158,10 @@ export class Plugins extends React.Component {
rowData["nsslapd-plugin-depends-on-named"] === undefined
? ""
: rowData["nsslapd-plugin-depends-on-named"][0],
+ currentPluginPrecedence:
+ rowData["nsslapd-pluginprecedence"] === undefined
+ ? ""
+ : rowData["nsslapd-pluginprecedence"][0],
showPluginModal: true
});
}
@@ -216,7 +221,9 @@ export class Plugins extends React.Component {
"--depends-on-type",
data.dependsOnType || "delete",
"--depends-on-named",
- data.dependsOnNamed || "delete"
+ data.dependsOnNamed || "delete",
+ "--precedence",
+ data.precedence || "delete"
];
if ("enabled" in data) {
@@ -231,10 +238,8 @@ export class Plugins extends React.Component {
.done(content => {
console.info("savePlugin", "Result", content);
basicPluginSuccess = true;
- this.addNotification(
- "success",
- `Plugin ${data.name} was successfully modified`
- );
+ this.addNotification("success", `Plugin ${data.name} was successfully modified`);
+ this.pluginList();
this.closePluginModal();
this.toggleLoading();
})
@@ -252,7 +257,7 @@ export class Plugins extends React.Component {
this.toggleLoading();
})
.always(() => {
- if ("specificPluginCMD" in data) {
+ if ("specificPluginCMD" in data && data.specificPluginCMD.length != 0) {
this.toggleLoading();
log_cmd(
"savePlugin",
@@ -279,11 +284,8 @@ export class Plugins extends React.Component {
.fail(err => {
let errMsg = JSON.parse(err);
if (
- (errMsg.desc.indexOf(
- "nothing to set"
- ) >= 0 &&
- nothingToSetErr) ||
- errMsg.desc.indexOf("nothing to set") < 0
+ (errMsg.desc.indexOf("nothing to set") >= 0 && nothingToSetErr) ||
+ errMsg.desc.indexOf("nothing to set") < 0
) {
if (basicPluginSuccess) {
this.addNotification(
@@ -528,7 +530,8 @@ export class Plugins extends React.Component {
currentPluginVersion: this.state.currentPluginVersion,
currentPluginDescription: this.state.currentPluginDescription,
currentPluginDependsOnType: this.state.currentPluginDependsOnType,
- currentPluginDependsOnNamed: this.state.currentPluginDependsOnNamed
+ currentPluginDependsOnNamed: this.state.currentPluginDependsOnNamed,
+ currentPluginPrecedence: this.state.currentPluginPrecedence
}}
closeHandler={this.closePluginModal}
showModal={this.state.showPluginModal}
diff --git a/src/lib389/lib389/cli_conf/__init__.py b/src/lib389/lib389/cli_conf/__init__.py
index 2567f1e..2e8f2a6 100644
--- a/src/lib389/lib389/cli_conf/__init__.py
+++ b/src/lib389/lib389/cli_conf/__init__.py
@@ -35,7 +35,7 @@ def generic_object_add(dsldap_objects_class, inst, log, args, arg_to_attr, dn=No
rdn = None
# Gather the attributes
attrs = _args_to_attrs(args, arg_to_attr)
- props.update({attr: value for (attr, value) in attrs.items() if value != ""})
+ props.update({attr: value for (attr, value) in attrs.items() if value != "" and value != [""]})
# Get RDN attribute and Base DN from the DN if Base DN is not specified
if basedn is None:
diff --git a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
index e585d2a..60134c7 100644
--- a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
+++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py
@@ -32,6 +32,8 @@ def accountpolicy_edit(inst, basedn, log, args):
def accountpolicy_add_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_add_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = generic_object_add(AccountPolicyConfig, inst, log, args, arg_to_attr_config, dn=targetdn)
plugin = AccountPolicyPlugin(inst)
plugin.replace('nsslapd_pluginConfigArea', config.dn)
@@ -42,6 +44,8 @@ def accountpolicy_add_config(inst, basedn, log, args):
def accountpolicy_edit_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_edit_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
generic_object_edit(config, log, args, arg_to_attr_config)
@@ -49,6 +53,8 @@ def accountpolicy_edit_config(inst, basedn, log, args):
def accountpolicy_show_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_show_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
if not config.exists():
@@ -64,6 +70,8 @@ def accountpolicy_show_config(inst, basedn, log, args):
def accountpolicy_del_config(inst, basedn, log, args):
log = log.getChild('accountpolicy_del_config')
targetdn = args.DN
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
config = AccountPolicyConfig(inst, targetdn)
config.delete()
log.info("Successfully deleted the %s", targetdn)
diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py b/src/lib389/lib389/cli_conf/plugins/automember.py
index fbd270a..bc5e55e 100644
--- a/src/lib389/lib389/cli_conf/plugins/automember.py
+++ b/src/lib389/lib389/cli_conf/plugins/automember.py
@@ -29,43 +29,44 @@ arg_to_attr_regex = {
def definition_list(inst, basedn, log, args):
automembers = AutoMembershipDefinitions(inst)
- all_definitions = automembers.list()
+ result = []
+ result_json = []
+ for definition in automembers.list():
+ if args.json:
+ result_json.append(definition.get_all_attrs_json())
+ else:
+ result.append(definition.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(all_definitions) > 0:
- for definition in all_definitions:
- if args.json:
- result['items'].append(definition)
- else:
- log.info(definition.rdn)
+ log.info(json.dumps({"type": "list", "items": result_json}))
else:
- log.info("No automember definitions were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No Automember definitions were found")
def definition_add(inst, basedn, log, args):
log = log.getChild('definition_add')
plugin = AutoMembershipPlugin(inst)
- props = {'cn': args.DEF_NAME}
+ props = {'cn': args.DEFNAME}
generic_object_add(AutoMembershipDefinition, inst, log, args, arg_to_attr_definition, basedn=plugin.dn, props=props)
def definition_edit(inst, basedn, log, args):
log = log.getChild('definition_edit')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
generic_object_edit(definition, log, args, arg_to_attr_definition)
def definition_show(inst, basedn, log, args):
log = log.getChild('definition_show')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
if not definition.exists():
- raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.DEFNAME)
if args and args.json:
o_str = definition.get_all_attrs_json()
log.info(o_str)
@@ -76,57 +77,58 @@ def definition_show(inst, basedn, log, args):
def definition_del(inst, basedn, log, args):
log = log.getChild('definition_del')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
definition.delete()
- log.info("Successfully deleted the %s definition", args.name)
+ log.info("Successfully deleted the %s definition", args.DEFNAME)
def regex_list(inst, basedn, log, args):
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- all_regexes = regexes.list()
+ result = []
+ result_json = []
+ for regex in regexes.list():
+ if args.json:
+ result_json.append(regex.get_all_attrs_json())
+ else:
+ result.append(regex.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(all_regexes) > 0:
- for regex in all_regexes:
- if args.json:
- result['items'].append(regex)
- else:
- log.info(regex.rdn)
+ log.info(json.dumps({"type": "list", "items": result_json}))
else:
- log.info("No automember regexes were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No Automember regexes were found")
def regex_add(inst, basedn, log, args):
log = log.getChild('regex_add')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
- props = {'cn': args.REGEX_NAME}
+ definition = definitions.get(args.DEFNAME)
+ props = {'cn': args.REGEXNAME}
generic_object_add(AutoMembershipRegexRule, inst, log, args, arg_to_attr_regex, basedn=definition.dn, props=props)
def regex_edit(inst, basedn, log, args):
log = log.getChild('regex_edit')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
generic_object_edit(regex, log, args, arg_to_attr_regex)
def regex_show(inst, basedn, log, args):
log = log.getChild('regex_show')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
if not regex.exists():
- raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.REGEXNAME)
if args and args.json:
o_str = regex.get_all_attrs_json()
log.info(o_str)
@@ -137,11 +139,11 @@ def regex_show(inst, basedn, log, args):
def regex_del(inst, basedn, log, args):
log = log.getChild('regex_del')
definitions = AutoMembershipDefinitions(inst)
- definition = definitions.get(args.DEF_NAME)
+ definition = definitions.get(args.DEFNAME)
regexes = AutoMembershipRegexRules(inst, definition.dn)
- regex = regexes.get(args.REGEX_NAME)
+ regex = regexes.get(args.REGEXNAME)
regex.delete()
- log.info("Successfully deleted the %s regex", regex.dn)
+ log.info("Successfully deleted the %s regex", args.REGEXNAME)
def fixup(inst, basedn, log, args):
@@ -159,25 +161,25 @@ def fixup(inst, basedn, log, args):
def _add_parser_args_definition(parser):
- parser.add_argument('--grouping-attr',
+ parser.add_argument('--grouping-attr', required=True,
help='Specifies the name of the member attribute in the group entry and '
'the attribute in the object entry that supplies the member attribute value, '
'in the format group_member_attr:entry_attr (autoMemberGroupingAttr)')
- parser.add_argument('--default-group', required=True,
+ parser.add_argument('--default-group',
help='Sets default or fallback group to add the entry to as a member '
- 'member attribute in group entry (autoMemberDefaultGroup)')
+ 'attribute in group entry (autoMemberDefaultGroup)')
parser.add_argument('--scope', required=True,
help='Sets the subtree DN to search for entries (autoMemberScope)')
- parser.add_argument('--filter',
+ parser.add_argument('--filter', required=True,
help='Sets a standard LDAP search filter to use to search for '
'matching entries (autoMemberFilter)')
def _add_parser_args_regex(parser):
- parser.add_argument("--exclusive",
+ parser.add_argument("--exclusive", nargs='+',
help='Sets a single regular expression to use to identify '
'entries to exclude (autoMemberExclusiveRegex)')
- parser.add_argument('--inclusive', required=True,
+ parser.add_argument('--inclusive', nargs='+',
help='Sets a single regular expression to use to identify '
'entries to include (autoMemberInclusiveRegex)')
parser.add_argument('--target-group', required=True,
@@ -195,11 +197,11 @@ def create_parser(subparsers):
list_definitions = subcommands_list.add_parser('definitions', help='List Automembership definitions.')
list_definitions.set_defaults(func=definition_list)
list_regexes = subcommands_list.add_parser('regexes', help='List Automembership regex rules.')
- list_regexes.add_argument('DEF-NAME', help='The definition entry CN.')
+ list_regexes.add_argument('DEFNAME', help='The definition entry CN.')
list_regexes.set_defaults(func=regex_list)
definition = subcommands.add_parser('definition', help='Manage Automembership definition.')
- definition.add_argument('DEF-NAME', help='The definition entry CN.')
+ definition.add_argument('DEFNAME', help='The definition entry CN.')
subcommands_definition = definition.add_subparsers(help='action')
add_def = subcommands_definition.add_parser('add', help='Create Automembership definition.')
@@ -210,19 +212,23 @@ def create_parser(subparsers):
_add_parser_args_definition(edit_def)
delete_def = subcommands_definition.add_parser('delete', help='Remove Automembership definition.')
delete_def.set_defaults(func=definition_del)
+ show_def = subcommands_definition.add_parser('show', help='Display Automembership definition.')
+ show_def.set_defaults(func=definition_show)
regex = subcommands_definition.add_parser('regex', help='Manage Automembership regex rules.')
- regex.add_argument('REGEX-NAME', help='The regex entry CN.')
+ regex.add_argument('REGEXNAME', help='The regex entry CN.')
subcommands_regex = regex.add_subparsers(help='action')
add_regex = subcommands_regex.add_parser('add', help='Create Automembership regex.')
add_regex.set_defaults(func=regex_add)
- _add_parser_args_definition(add_regex)
+ _add_parser_args_regex(add_regex)
edit_regex = subcommands_regex.add_parser('set', help='Edit Automembership regex.')
edit_regex.set_defaults(func=regex_edit)
- _add_parser_args_definition(edit_regex)
+ _add_parser_args_regex(edit_regex)
delete_regex = subcommands_regex.add_parser('delete', help='Remove Automembership regex.')
delete_regex.set_defaults(func=regex_del)
+ show_regex = subcommands_regex.add_parser('show', help='Display Automembership regex.')
+ show_regex.set_defaults(func=regex_show)
fixup = subcommands.add_parser('fixup', help='Run a rebuild membership task.')
fixup.set_defaults(func=fixup)
diff --git a/src/lib389/lib389/cli_conf/plugins/dna.py b/src/lib389/lib389/cli_conf/plugins/dna.py
index 6033c17..a86034d 100644
--- a/src/lib389/lib389/cli_conf/plugins/dna.py
+++ b/src/lib389/lib389/cli_conf/plugins/dna.py
@@ -29,8 +29,8 @@ arg_to_attr = {
}
arg_to_attr_config = {
- 'hostname': 'dnaHostname',
- 'port': 'dnaPortNum',
+ 'HOSTNAME': 'dnaHostname',
+ 'PORT': 'dnaPortNum',
'secure_port': 'dnaSecurePortNum',
'remaining_values': 'dnaRemainingValues',
'remote_bind_method': 'dnaRemoteBindMethod',
@@ -38,23 +38,37 @@ arg_to_attr_config = {
}
+def _get_shared_config_dn(inst, args):
+ configs = DNAPluginConfigs(inst)
+ config = configs.get(args.NAME)
+ if config.present('dnaSharedCfgDN'):
+ basedn = config.get_attr_val_utf8_l('dnaSharedCfgDN')
+ else:
+ raise ValueError('dnaSharedCfgDN should be set at the "%s" config entry' % args.NAME)
+
+ decomposed_dn = [[('dnaHostname', args.HOSTNAME, 1),
+ ('dnaPortNum', args.PORT, 1)]] + ldap.dn.str2dn(basedn)
+ return ldap.dn.dn2str(decomposed_dn)
+
+
def dna_list(inst, basedn, log, args):
log = log.getChild('dna_list')
configs = DNAPluginConfigs(inst)
- config_list = configs.list()
+ result = []
+ result_json = []
+ for config in configs.list():
+ if args.json:
+ result_json.append(config.get_all_attrs_json())
+ else:
+ result.append(config.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(config_list) > 0:
- for config in config_list:
- if args.json:
- result['items'].append(config)
- else:
- log.info(config.rdn)
+ log.info(json.dumps({"type": "list", "items": result_json}))
else:
- log.info("No DNA configurations were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ else:
+ log.info("No DNA configurations were found")
def dna_add(inst, basedn, log, args):
@@ -96,25 +110,42 @@ def dna_del(inst, basedn, log, args):
def dna_config_list(inst, basedn, log, args):
log = log.getChild('dna_list')
configs = DNAPluginSharedConfigs(inst, args.BASEDN)
- config_list = configs.list()
+ result = []
+ result_json = []
+ parent_config_entries = []
+
+ parent_configs = DNAPluginConfigs(inst)
+ for config in parent_configs.list():
+ if config.present("dnaSharedCfgDN") and config.get_attr_val_utf8("dnaSharedCfgDN") == args.BASEDN:
+ parent_config_entries.append(config.rdn)
+
+ for config in configs.list():
+ if args.json:
+ result_json.append(config.get_all_attrs_json())
+ else:
+ result.append(config.rdn)
if args.json:
- result = {'type': 'list', 'items': []}
- if len(config_list) > 0:
- for config in config_list:
- if args.json:
- result['items'].append(config.get_all_attrs_json())
- else:
- log.info(config.dn)
+ log.info(json.dumps({"type": "list", "items": result_json}))
else:
- log.info("No DNA shared configurations were found")
-
- if args.json:
- log.info(json.dumps(result))
+ if len(result) > 0:
+ for i in result:
+ log.info(i)
+ if parent_config_entries:
+ log.info("DNA plugin configs which have the shared config entry set as a dnaSharedCfgDN attribute: " + " ".join(parent_config_entries))
+ else:
+ log.info("No DNA plugin configs have the shared config entry set as a dnaSharedCfgDN attribute.")
+ else:
+ log.info("No DNA shared configurations were found")
def dna_config_add(inst, basedn, log, args):
log = log.getChild('dna_config_add')
- targetdn = args.BASEDN
+ configs = DNAPluginConfigs(inst)
+ config = configs.get(args.NAME)
+ if config.present('dnaSharedCfgDN'):
+ targetdn = config.get_attr_val_utf8_l('dnaSharedCfgDN')
+ else:
+ raise ValueError('dnaSharedCfgDN should be set at the "%s" config entry' % args.NAME)
shared_configs = DNAPluginSharedConfigs(inst, targetdn)
attrs = _args_to_attrs(args, arg_to_attr_config)
@@ -123,23 +154,19 @@ def dna_config_add(inst, basedn, log, args):
shared_config = shared_configs.create(properties=props)
log.info("Successfully created the %s" % shared_config.dn)
- configs = DNAPluginConfigs(inst)
- config = configs.get(args.NAME)
- config.replace('dnaSharedCfgDN', config.dn)
- log.info('DNA attribute dnaSharedCfgDN (shared-config-entry) '
- 'was set in the %s plugin config' % config.rdn)
-
def dna_config_edit(inst, basedn, log, args):
log = log.getChild('dna_config_edit')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
+
shared_config = DNAPluginSharedConfig(inst, targetdn)
generic_object_edit(shared_config, log, args, arg_to_attr_config)
def dna_config_show(inst, basedn, log, args):
log = log.getChild('dna_config_show')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
+
shared_config = DNAPluginSharedConfig(inst, targetdn)
if not shared_config.exists():
@@ -153,7 +180,7 @@ def dna_config_show(inst, basedn, log, args):
def dna_config_del(inst, basedn, log, args):
log = log.getChild('dna_config_del')
- targetdn = args.DN
+ targetdn = _get_shared_config_dn(inst, args)
shared_config = DNAPluginSharedConfig(inst, targetdn)
shared_config.delete()
log.info("Successfully deleted the %s", targetdn)
@@ -188,11 +215,6 @@ def _add_parser_args(parser):
def _add_parser_args_config(parser):
- parser.add_argument('--hostname',
- help='Identifies the host name of a server in a shared range, as part of the DNA '
- 'range configuration for that specific host in multi-master replication (dnaHostname)')
- parser.add_argument('--port', help='Gives the standard port number to use to connect to '
- 'the host identified in dnaHostname (dnaPortNum)')
parser.add_argument('--secure-port', help='Gives the secure (TLS) port number to use to connect '
'to the host identified in dnaHostname (dnaSecurePortNum)')
parser.add_argument('--remote-bind-method', help='Specifies the remote bind method (dnaRemoteBindMethod)')
@@ -227,21 +249,22 @@ def create_parser(subparsers):
show.set_defaults(func=dna_show)
delete = config_subcommands.add_parser('delete', help='Delete the config entry')
delete.set_defaults(func=dna_del)
+
shared_config = config_subcommands.add_parser('shared-config-entry', help='Manage the shared config entry')
+ shared_config.add_argument('HOSTNAME',
+ help='Identifies the host name of a server in a shared range, as part of the DNA range '
+ 'configuration for that specific host in multi-master replication (dnaHostname)')
+ shared_config.add_argument('PORT', help='Gives the standard port number to use to connect to '
+ 'the host identified in dnaHostname (dnaPortNum)')
shared_config_subcommands = shared_config.add_subparsers(help='action')
add_config = shared_config_subcommands.add_parser('add', help='Add the shared config entry')
- add_config.add_argument('BASEDN', help='The shared config entry BASE DN. The new DN will be constructed with '
- 'dnaHostname and dnaPortNum')
add_config.set_defaults(func=dna_config_add)
_add_parser_args_config(add_config)
- edit_config = shared_config_subcommands.add_parser('edit', help='Edit the shared config entry')
- edit_config.add_argument('DN', help='The shared config entry DN')
+ edit_config = shared_config_subcommands.add_parser('set', help='Edit the shared config entry')
edit_config.set_defaults(func=dna_config_edit)
_add_parser_args_config(edit_config)
show_config_parser = shared_config_subcommands.add_parser('show', help='Display the shared config entry')
- show_config_parser.add_argument('DN', help='The shared config entry DN')
show_config_parser.set_defaults(func=dna_config_show)
del_config_parser = shared_config_subcommands.add_parser('delete', help='Delete the shared config entry')
- del_config_parser.add_argument('DN', help='The shared config entry DN')
del_config_parser.set_defaults(func=dna_config_del)
diff --git a/src/lib389/lib389/cli_conf/plugins/managedentries.py b/src/lib389/lib389/cli_conf/plugins/managedentries.py
index 071df7b..99ffc94 100644
--- a/src/lib389/lib389/cli_conf/plugins/managedentries.py
+++ b/src/lib389/lib389/cli_conf/plugins/managedentries.py
@@ -12,7 +12,7 @@ from lib389.plugins import ManagedEntriesPlugin, MEPConfig, MEPConfigs, MEPTempl
from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add
arg_to_attr = {
- 'config_area': 'nsslapd-pluginConfigArea'
+ 'config_area': 'nsslapd-pluginconfigarea'
}
arg_to_attr_config = {
@@ -23,9 +23,9 @@ arg_to_attr_config = {
}
arg_to_attr_template = {
- 'rdn_attr': 'mepRDNAttr',
- 'static_attr': 'mepStaticAttr',
- 'mapped_attr': 'mepMappedAttr'
+ 'rdn_attr': 'meprdnattr',
+ 'static_attr': 'mepstaticattr',
+ 'mapped_attr': 'mepmappedattr'
}
@@ -125,7 +125,9 @@ def mep_template_list(inst, basedn, log, args):
def mep_template_add(inst, basedn, log, args):
log = log.getChild('mep_template_add')
targetdn = args.DN
- generic_object_add(MEPTemplate, inst, log, args, arg_to_attr_config, dn=targetdn)
+ if not targetdn or not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ generic_object_add(MEPTemplate, inst, log, args, arg_to_attr_template, dn=targetdn)
log.info('Don\'t forget to assign the template to Managed Entry Plugin config '
'attribute - managedTemplate')
@@ -133,16 +135,18 @@ def mep_template_add(inst, basedn, log, args):
def mep_template_edit(inst, basedn, log, args):
log = log.getChild('mep_template_edit')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
- generic_object_edit(template, log, args, arg_to_attr_config)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
+ generic_object_edit(template, log, args, arg_to_attr_template)
def mep_template_show(inst, basedn, log, args):
log = log.getChild('mep_template_show')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
if not template.exists():
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)
@@ -156,8 +160,9 @@ def mep_template_show(inst, basedn, log, args):
def mep_template_del(inst, basedn, log, args):
log = log.getChild('mep_template_del')
targetdn = args.DN
- templates = MEPTemplates(inst)
- template = templates.get(targetdn)
+ if not ldap.dn.is_dn(targetdn):
+ raise ValueError("Specified DN is not a valid DN")
+ template = MEPTemplate(inst, targetdn)
template.delete()
log.info("Successfully deleted the %s", targetdn)
@@ -179,7 +184,7 @@ def _add_parser_args_template(parser):
parser.add_argument('--static-attr', help='Sets an attribute with a defined value that must be added '
'to the automatically-generated entry (mepStaticAttr)')
parser.add_argument('--mapped-attr', nargs='+',
- help='Sets an attribute in the Managed Entries template entry which must exist '
+ help='Sets attributes in the Managed Entries template entry which must exist '
'in the generated entry (mepMappedAttr)')
diff --git a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
index c5d0646..dc115e0 100644
--- a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
+++ b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py
@@ -26,48 +26,99 @@ arg_to_attr_pam = {
}
+def _get_url_next_num(url_attrs):
+ existing_nums = list(map(lambda url: int(url.split('nsslapd-pluginarg')[1]),
+ [i for i, _ in url_attrs.items()]))
+ if len(existing_nums) > 0:
+ existing_nums.sort()
+ full_num_list = list(range(existing_nums[-1]+2))
+ if not full_num_list:
+ next_num_list = ["0"]
+ else:
+ next_num_list = list(filter(lambda x: x not in existing_nums, full_num_list))
+ else:
+ next_num_list = ["0"]
+
+ return next_num_list[0]
+
+
+def _validate_url(url):
+ failed = False
+ if len(url.split(" ")) == 2:
+ link = url.split(" ")[0]
+ params = url.split(" ")[1]
+ else:
+ link = url
+ params = ""
+
+ if (":" not in link) or ("//" not in link) or ("/" not in link) or (params and "," not in params):
+ failed = False
+
+ if ldap.dn.is_dn(link.split("/")[-1]):
+ raise ValueError("Subtree is an invalid DN")
+
+ if params and len(params.split(",")) != 6 and not all(map(str.isdigit, params.split(","))):
+ failed = False
+
+ if failed:
+ raise ValueError("URL should be in one of the next formats (all parameters after a space should be digits): "
+ "'ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS' or "
+ "'ldap|ldaps://authDS/subtree'")
+ return url
+
+
def pta_list(inst, basedn, log, args):
log = log.getChild('pta_list')
plugin = PassThroughAuthenticationPlugin(inst)
- result = []
urls = plugin.get_urls()
if args.json:
- log.info(json.dumps({"type": "list", "items": urls}))
+ log.info(json.dumps({"type": "list",
+ "items": [{"id": id, "url": value} for id, value in urls.items()]}))
else:
if len(urls) > 0:
- for i in result:
- log.info(i)
+ for _, value in urls.items():
+ log.info(value)
else:
log.info("No Pass Through Auth URLs were found")
def pta_add(inst, basedn, log, args):
log = log.getChild('pta_add')
+ new_url_l = _validate_url(args.URL.lower())
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
- if args.URL.lower() in urls:
+ url_attrs = plugin.get_urls()
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in url_attrs.items()]))
+ next_num = _get_url_next_num(url_attrs)
+ if new_url_l in urls:
raise ldap.ALREADY_EXISTS("Entry %s already exists" % args.URL)
- plugin.add("nsslapd-pluginarg%s" % len(urls), args.URL)
+ plugin.add("nsslapd-pluginarg%s" % next_num, args.URL)
def pta_edit(inst, basedn, log, args):
log = log.getChild('pta_edit')
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
+ url_attrs = plugin.get_urls()
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in url_attrs.items()]))
+ next_num = _get_url_next_num(url_attrs)
+ _validate_url(args.NEW_URL.lower())
old_url_l = args.OLD_URL.lower()
+ import pdb; pdb.set_trace()
if old_url_l not in urls:
log.info("Entry %s doesn't exists. Adding a new value." % args.OLD_URL)
- url_num = len(urls)
else:
- url_num = urls.index(old_url_l)
- plugin.remove("nsslapd-pluginarg%s" % url_num, old_url_l)
- plugin.add("nsslapd-pluginarg%s" % url_num, args.NEW_URL)
+ for attr, value in url_attrs:
+ if value.lower() == old_url_l:
+ plugin.remove(attr, old_url_l)
+ plugin.add("nsslapd-pluginarg%s" % next_num, args.NEW_URL)
def pta_del(inst, basedn, log, args):
log = log.getChild('pta_del')
plugin = PassThroughAuthenticationPlugin(inst)
- urls = list(map(lambda url: url.lower(), plugin.get_urls()))
+ urls = list(map(lambda url: url.lower(),
+ [i for _, i in plugin.get_urls().items()]))
old_url_l = args.URL.lower()
if old_url_l not in urls:
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.URL)
@@ -137,7 +188,7 @@ def _add_parser_args_pam(parser):
help='Specifies a suffix to exclude from PAM authentication (pamExcludeSuffix)')
parser.add_argument('--include-suffix', nargs='+',
help='Sets a suffix to include for PAM authentication (pamIncludeSuffix)')
- parser.add_argument('--missing-suffix', choices=['ERROR', 'ALLOW', 'IGNORE'],
+ parser.add_argument('--missing-suffix', choices=['ERROR', 'ALLOW', 'IGNORE', 'delete', ''],
help='Identifies how to handle missing include or exclude suffixes (pamMissingSuffix)')
parser.add_argument('--filter',
help='Sets an LDAP filter to use to identify specific entries within '
diff --git a/src/lib389/lib389/cli_conf/plugins/referint.py b/src/lib389/lib389/cli_conf/plugins/referint.py
index 9482a14..01bd4f0 100644
--- a/src/lib389/lib389/cli_conf/plugins/referint.py
+++ b/src/lib389/lib389/cli_conf/plugins/referint.py
@@ -6,8 +6,9 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
-from lib389.plugins import ReferentialIntegrityPlugin
-from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit
+import ldap
+from lib389.plugins import ReferentialIntegrityPlugin, ReferentialIntegrityConfig
+from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add
arg_to_attr = {
'update_delay': 'referint-update-delay',
@@ -15,6 +16,8 @@ arg_to_attr = {
'entry_scope': 'nsslapd-pluginEntryScope',
'exclude_entry_scope': 'nsslapd-pluginExcludeEntryScope',
'container_scope': 'nsslapd-pluginContainerScope',
+ 'config_entry': 'nsslapd-pluginConfigArea',
+ 'log_file': 'referint-logfile'
}
@@ -24,6 +27,46 @@ def referint_edit(inst, basedn, log, args):
generic_object_edit(plugin, log, args, arg_to_attr)
+def referint_add_config(inst, basedn, log, args):
+ log = log.getChild('referint_add_config')
+ targetdn = args.DN
+ config = generic_object_add(ReferentialIntegrityConfig, inst, log, args, arg_to_attr, dn=targetdn)
+ plugin = ReferentialIntegrityPlugin(inst)
+ plugin.replace('nsslapd-pluginConfigArea', config.dn)
+ import pdb; pdb.set_trace()
+ log.info('ReferentialIntegrity attribute nsslapd-pluginConfigArea (config-entry) '
+ 'was set in the main plugin config')
+
+
+def referint_edit_config(inst, basedn, log, args):
+ log = log.getChild('referint_edit_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+ generic_object_edit(config, log, args, arg_to_attr)
+
+
+def referint_show_config(inst, basedn, log, args):
+ log = log.getChild('referint_show_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+
+ if not config.exists():
+ raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)
+ if args and args.json:
+ o_str = config.get_all_attrs_json()
+ log.info(o_str)
+ else:
+ log.info(config.display())
+
+
+def referint_del_config(inst, basedn, log, args):
+ log = log.getChild('referint_del_config')
+ targetdn = args.DN
+ config = ReferentialIntegrityConfig(inst, targetdn)
+ config.delete()
+ log.info("Successfully deleted the %s", targetdn)
+
+
def _add_parser_args(parser):
parser.add_argument('--update-delay',
help='Sets the update interval. Special values: 0 - The check is performed immediately, '
@@ -40,6 +83,9 @@ def _add_parser_args(parser):
help='Specifies which branch the plug-in searches for the groups to which the user belongs. '
'It only updates groups that are under the specified container branch, '
'and leaves all other groups not updated (nsslapd-pluginContainerScope)')
+ parser.add_argument('--log-file',
+ help='Specifies a path to the Referential integrity logfile.'
+ 'For example: /var/log/dirsrv/slapd-YOUR_INSTANCE/referint')
def create_parser(subparsers):
@@ -53,5 +99,21 @@ def create_parser(subparsers):
edit = subcommands.add_parser('set', help='Edit the plugin')
edit.set_defaults(func=referint_edit)
_add_parser_args(edit)
+ edit.add_argument('--config-entry', help='The value to set as nsslapd-pluginConfigArea')
-
+ config = subcommands.add_parser('config-entry', help='Manage the config entry')
+ config_subcommands = config.add_subparsers(help='action')
+ add_config = config_subcommands.add_parser('add', help='Add the config entry')
+ add_config.set_defaults(func=referint_add_config)
+ add_config.add_argument('DN', help='The config entry full DN')
+ _add_parser_args(add_config)
+ edit_config = config_subcommands.add_parser('set', help='Edit the config entry')
+ edit_config.set_defaults(func=referint_edit_config)
+ edit_config.add_argument('DN', help='The config entry full DN')
+ _add_parser_args(edit_config)
+ show_config = config_subcommands.add_parser('show', help='Display the config entry')
+ show_config.set_defaults(func=referint_show_config)
+ show_config.add_argument('DN', help='The config entry full DN')
+ del_config_ = config_subcommands.add_parser('delete', help='Delete the config entry')
+ del_config_.set_defaults(func=referint_del_config)
+ del_config_.add_argument('DN', help='The config entry full DN')
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index 53e3448..0691e4e 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -319,6 +319,35 @@ class MEPConfigs(DSLdapObjects):
basedn = "cn=managed entries,cn=plugins,cn=config"
self._basedn = basedn
+ def list(self):
+ """Get a list of children entries (DSLdapObject, Replica, etc.) using a base DN
+ and objectClasses of our object (DSLdapObjects, Replicas, etc.)
+
+ :returns: A list of children entries
+ """
+
+ # Filter based on the objectclasses and the basedn
+ insts = None
+ # This will yield and & filter for objectClass with as many terms as needed.
+ filterstr = self._get_objectclass_filter()
+ self._log.debug('list filter = %s' % filterstr)
+ try:
+ results = self._instance.search_ext_s(
+ base=self._basedn,
+ scope=self._scope,
+ filterstr=filterstr,
+ attrlist=self._list_attrlist,
+ serverctrls=self._server_controls, clientctrls=self._client_controls,
+ escapehatch='i am sure'
+ )
+ # def __init__(self, instance, dn=None):
+ insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results]
+ except ldap.NO_SUCH_OBJECT:
+ # There are no objects to select from, se we return an empty array
+ insts = []
+ return insts
+
+
class MEPTemplate(DSLdapObject):
"""A single instance of MEP template entry
@@ -1276,10 +1305,10 @@ class PassThroughAuthenticationPlugin(Plugin):
"""
attr_dict = collections.OrderedDict(sorted(self.get_all_attrs().items()))
- result = []
+ result = {}
for attr, value in attr_dict.items():
if attr.startswith("nsslapd-pluginarg"):
- result.append(ensure_str(value[0]))
+ result[attr] = ensure_str(value[0])
return result
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] 01/03: Ticket 50340 - 2nd try - structs for diabled plugins will not be freed
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit 41a8e4c600ef06060ea15220b86455a21dec256f
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
AuthorDate: Thu May 23 11:11:25 2019 +0000
Ticket 50340 - 2nd try - structs for diabled plugins will not be freed
Bug: when plugins are loaded from dse.ldif enabled plugins will be added to
the list of the plugin type and freed when plugins are stopped.
But the memory allocated for disabled plugins will remain allocated and
and be reported.
Fix: The previous fix did free not enabled plugins in plugin_setup, but
that caused a lot of issues.
This patch frees not enabled plugins in plugin_dependency_freeall
Reviewed by: ?
Signed-off-by: Mark Reynolds <mreynolds(a)redhat.com>
---
ldap/servers/slapd/plugin.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 7dfab84..0b10678 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -1914,13 +1914,18 @@ void
plugin_dependency_freeall()
{
entry_and_plugin_t *iterp, *nextp;
+ char *value;
/* free the plugin dependency entry list */
iterp = dep_plugin_entries;
while (iterp) {
nextp = iterp->next;
+ if ((value = slapi_entry_attr_get_charptr(iterp->e, ATTR_PLUGIN_ENABLED)) &&
+ !strcasecmp(value, "off")) {
+ plugin_free(iterp->plugin);
+ }
+ slapi_ch_free_string(&value);
slapi_entry_free(iterp->e);
- /* plugin_free(iterp->plugin); */
slapi_ch_free((void **)&iterp);
iterp = nextp;
}
@@ -3031,7 +3036,7 @@ plugin_setup(Slapi_Entry *plugin_entry, struct slapi_componentid *group, slapi_p
add_plugin_entry_dn(dn_copy);
}
- if (add_entry && enabled) {
+ if (add_entry) {
/* make a copy of the plugin entry for our own use because it will
be freed later by the caller */
Slapi_Entry *e_copy = slapi_entry_dup(plugin_entry);
@@ -3040,7 +3045,7 @@ plugin_setup(Slapi_Entry *plugin_entry, struct slapi_componentid *group, slapi_p
}
PLUGIN_CLEANUP:
- if (status || !enabled) {
+ if (status) {
plugin_free(plugin);
}
slapi_ch_free((void **)&configdir);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch master updated: Bump version to 1.4.1.3
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch master
in repository 389-ds-base.
The following commit(s) were added to refs/heads/master by this push:
new 1f1119d Bump version to 1.4.1.3
1f1119d is described below
commit 1f1119d4ba00ca7daa4d5df3ad4b6c77e4d59671
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri May 24 14:37:38 2019 -0400
Bump version to 1.4.1.3
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index a0b867d..be82b6b 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=4
-VERSION_MAINT=1.2
+VERSION_MAINT=1.3
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch master updated: Ticket 50340 - 2nd try - structs for diabled plugins will not be freed
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch master
in repository 389-ds-base.
The following commit(s) were added to refs/heads/master by this push:
new 2ca86fe Ticket 50340 - 2nd try - structs for diabled plugins will not be freed
2ca86fe is described below
commit 2ca86fe13e0efa224fbe4f374be692a8ddabdeb7
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
AuthorDate: Thu May 23 11:11:25 2019 +0000
Ticket 50340 - 2nd try - structs for diabled plugins will not be freed
Bug: when plugins are loaded from dse.ldif enabled plugins will be added to
the list of the plugin type and freed when plugins are stopped.
But the memory allocated for disabled plugins will remain allocated and
and be reported.
Fix: The previous fix did free not enabled plugins in plugin_setup, but
that caused a lot of issues.
This patch frees not enabled plugins in plugin_dependency_freeall
Reviewed by: ?
Signed-off-by: Mark Reynolds <mreynolds(a)redhat.com>
---
ldap/servers/slapd/plugin.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 7dfab84..0b10678 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -1914,13 +1914,18 @@ void
plugin_dependency_freeall()
{
entry_and_plugin_t *iterp, *nextp;
+ char *value;
/* free the plugin dependency entry list */
iterp = dep_plugin_entries;
while (iterp) {
nextp = iterp->next;
+ if ((value = slapi_entry_attr_get_charptr(iterp->e, ATTR_PLUGIN_ENABLED)) &&
+ !strcasecmp(value, "off")) {
+ plugin_free(iterp->plugin);
+ }
+ slapi_ch_free_string(&value);
slapi_entry_free(iterp->e);
- /* plugin_free(iterp->plugin); */
slapi_ch_free((void **)&iterp);
iterp = nextp;
}
@@ -3031,7 +3036,7 @@ plugin_setup(Slapi_Entry *plugin_entry, struct slapi_componentid *group, slapi_p
add_plugin_entry_dn(dn_copy);
}
- if (add_entry && enabled) {
+ if (add_entry) {
/* make a copy of the plugin entry for our own use because it will
be freed later by the caller */
Slapi_Entry *e_copy = slapi_entry_dup(plugin_entry);
@@ -3040,7 +3045,7 @@ plugin_setup(Slapi_Entry *plugin_entry, struct slapi_componentid *group, slapi_p
}
PLUGIN_CLEANUP:
- if (status || !enabled) {
+ if (status) {
plugin_free(plugin);
}
slapi_ch_free((void **)&configdir);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50393 - maxlogsperdir accepting negative values
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new a50a865 Ticket 50393 - maxlogsperdir accepting negative values
a50a865 is described below
commit a50a8654776d8bfcb5852c05341029bde7fc3e58
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon May 20 11:38:05 2019 -0400
Ticket 50393 - maxlogsperdir accepting negative values
Description: Improve the log "digit" config setting validation
for all settings.
https://pagure.io/389-ds-base/issue/50393
Reviewed by: tbordaz, firstyear, mhonek, and spichugi (Thanks!!!!)
(cherry picked from commit ca70d06fbb7a2c06c62f0ba5b192dba36f24b8e3)
---
dirsrvtests/tests/suites/logging/__init__.py | 3 +
.../tests/suites/logging/logging_config_test.py | 86 +++++++++++++
ldap/servers/slapd/log.c | 143 +++++++++++++++------
3 files changed, 192 insertions(+), 40 deletions(-)
diff --git a/dirsrvtests/tests/suites/logging/__init__.py b/dirsrvtests/tests/suites/logging/__init__.py
new file mode 100644
index 0000000..7f812e3
--- /dev/null
+++ b/dirsrvtests/tests/suites/logging/__init__.py
@@ -0,0 +1,3 @@
+"""
+ :Requirement: 389-ds-base: Directory Server Logging Configurations
+"""
diff --git a/dirsrvtests/tests/suites/logging/logging_config_test.py b/dirsrvtests/tests/suites/logging/logging_config_test.py
new file mode 100644
index 0000000..4d8d68a
--- /dev/null
+++ b/dirsrvtests/tests/suites/logging/logging_config_test.py
@@ -0,0 +1,86 @@
+import logging
+import pytest
+import os
+import ldap
+from lib389._constants import *
+from lib389.topologies import topology_st as topo
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+if DEBUGGING:
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
+else:
+ logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+big_value = "1111111111111111111111111111111111111111111"
+
+
+(a)pytest.mark.parametrize("attr, invalid_vals, valid_vals",
+ [
+ ("logexpirationtime", ["-2", "0"], ["1", "-1"]),
+ ("maxlogsize", ["-2", "0"], ["100", "-1"]),
+ ("logmaxdiskspace", ["-2", "0"], ["100", "-1"]),
+ ("logminfreediskspace", ["-2", "0"], ["100", "-1"]),
+ ("mode", ["888", "778", "77", "7777"], ["777", "000", "600"]),
+ ("maxlogsperdir", ["-1", "0"], ["1", "20"]),
+ ("logrotationsynchour", ["-1", "24"], ["0", "23"]),
+ ("logrotationsyncmin", ["-1", "60"], ["0", "59"]),
+ ("logrotationtime", ["-2", "0"], ["100", "-1"])
+ ])
+def test_logging_digit_config(topo, attr, invalid_vals, valid_vals):
+ """Validate logging config settings
+
+ :id: a0ef30e5-538b-46fa-9762-01a4435a15e9
+ :setup: Standalone Instance
+ :steps:
+ 1. Test log expiration time
+ 2. Test log max size
+ 3. Test log max disk space
+ 4. Test log min disk space
+ 5. Test log mode
+ 6. Test log max number of logs
+ 7. Test log rotation hour
+ 8. Test log rotation minute
+ 9. Test log rotation time
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Success
+ 7. Success
+ 8. Success
+ 9. Success
+ """
+
+ accesslog_attr = "nsslapd-accesslog-{}".format(attr)
+ auditlog_attr = "nsslapd-auditlog-{}".format(attr)
+ auditfaillog_attr = "nsslapd-auditfaillog-{}".format(attr)
+ errorlog_attr = "nsslapd-errorlog-{}".format(attr)
+
+ # Test each log
+ for attr in [accesslog_attr, auditlog_attr, auditfaillog_attr, errorlog_attr]:
+ # Invalid values
+ for invalid_val in invalid_vals:
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, invalid_val)
+
+ # Invalid high value
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, big_value)
+
+ # Non digits
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, "abc")
+
+ # Valid values
+ for valid_val in valid_vals:
+ topo.standalone.config.set(attr, valid_val)
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 7dd7154..2456abf 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -817,8 +817,9 @@ log_update_auditfaillogdir(char *pathname, int apply)
int
log_set_mode(const char *attrname, char *value, int logtype, char *errorbuf, int apply)
{
- int v = 0;
+ int64_t v = 0;
int retval = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (NULL == value) {
@@ -833,7 +834,18 @@ log_set_mode(const char *attrname, char *value, int logtype, char *errorbuf, int
return LDAP_SUCCESS;
}
- v = strtol(value, NULL, 8);
+ errno = 0;
+ v = strtol(value, &endp, 8);
+ if (*endp != '\0' || errno == ERANGE ||
+ strlen(value) != 3 ||
+ v > 0777 /* octet of 777 511 */ ||
+ v < 0)
+ {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s) (%ld), value must be three digits between 000 and 777",
+ value, attrname, v);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -895,9 +907,9 @@ int
log_set_numlogsperdir(const char *attrname, char *numlogs_str, int logtype, char *returntext, int apply)
{
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
-
+ char *endp = NULL;
int rv = LDAP_SUCCESS;
- int numlogs;
+ int64_t numlogs;
if (logtype != SLAPD_ACCESS_LOG &&
logtype != SLAPD_ERROR_LOG &&
@@ -911,7 +923,14 @@ log_set_numlogsperdir(const char *attrname, char *numlogs_str, int logtype, char
return rv;
}
- numlogs = atoi(numlogs_str);
+ errno = 0;
+ numlogs = strtol(numlogs_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || numlogs < 1) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be between 1 and 2147483647",
+ numlogs_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
if (numlogs >= 1) {
switch (logtype) {
@@ -960,21 +979,25 @@ int
log_set_logsize(const char *attrname, char *logsize_str, int logtype, char *returntext, int apply)
{
int rv = LDAP_SUCCESS;
- PRInt64 max_logsize; /* in bytes */
- int logsize; /* in megabytes */
+ int64_t max_logsize; /* in bytes */
+ int64_t logsize; /* in megabytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (!apply || !logsize_str || !*logsize_str)
return rv;
- logsize = atoi(logsize_str);
+ errno = 0;
+ logsize = strtol(logsize_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || logsize < -1 || logsize == 0) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ logsize_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* convert it to bytes */
- max_logsize = (PRInt64)logsize * LOG_MB_IN_BYTES;
-
- if (max_logsize <= 0) {
- max_logsize = -1;
- }
+ max_logsize = logsize * LOG_MB_IN_BYTES;
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1101,8 +1124,9 @@ log_set_rotationsync_enabled(const char *attrname, char *value, int logtype, cha
int
log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, char *returntext, int apply)
{
- int rhour = -1;
+ int64_t rhour = -1;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1115,12 +1139,19 @@ log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, cha
}
/* return if we aren't doing this for real */
- if (!apply) {
+ if (!apply || !rhour_str || !*rhour_str) {
return rv;
}
- if (rhour_str && *rhour_str != '\0')
- rhour = atol(rhour_str);
+ errno = 0;
+ rhour = strtol(rhour_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rhour < 0 || rhour > 23) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"0\" thru \"23\"",
+ rhour_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
if (rhour > 23)
rhour = rhour % 24;
@@ -1161,8 +1192,9 @@ log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, cha
int
log_set_rotationsyncmin(const char *attrname, char *rmin_str, int logtype, char *returntext, int apply)
{
- int rmin = -1;
+ int64_t rmin = -1;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1175,14 +1207,18 @@ log_set_rotationsyncmin(const char *attrname, char *rmin_str, int logtype, char
}
/* return if we aren't doing this for real */
- if (!apply) {
+ if (!apply || !rmin_str || !*rmin_str) {
return rv;
}
- if (rmin_str && *rmin_str != '\0')
- rmin = atol(rmin_str);
- if (rmin > 59)
- rmin = rmin % 60;
+ errno = 0;
+ rmin = strtol(rmin_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rmin < 0 || rmin > 59) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be between \"0\" and \"59\"",
+ rmin_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1229,8 +1265,9 @@ log_set_rotationtime(const char *attrname, char *rtime_str, int logtype, char *r
{
int runit = 0;
- int value, rtime;
+ int64_t value, rtime;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1247,7 +1284,14 @@ log_set_rotationtime(const char *attrname, char *rtime_str, int logtype, char *r
return rv;
}
- rtime = atoi(rtime_str);
+ errno = 0;
+ rtime = strtol(rtime_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rtime < -1 || rtime == 0) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than \"0\"",
+ rtime_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
if (0 == rtime) {
rtime = -1; /* Value Range: -1 | 1 to PR_INT32_MAX */
@@ -1332,7 +1376,6 @@ log_set_rotationtimeunit(const char *attrname, char *runit, int logtype, char *e
int origvalue = 0, value = 0;
int runitType;
int rv = 0;
-
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1448,10 +1491,10 @@ int
log_set_maxdiskspace(const char *attrname, char *maxdiskspace_str, int logtype, char *errorbuf, int apply)
{
int rv = 0;
- PRInt64 mlogsize = 0; /* in bytes */
- PRInt64 maxdiskspace; /* in bytes */
- int s_maxdiskspace; /* in megabytes */
-
+ int64_t mlogsize = 0; /* in bytes */
+ int64_t maxdiskspace; /* in bytes */
+ int64_t s_maxdiskspace; /* in megabytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1465,7 +1508,14 @@ log_set_maxdiskspace(const char *attrname, char *maxdiskspace_str, int logtype,
if (!apply || !maxdiskspace_str || !*maxdiskspace_str)
return rv;
- s_maxdiskspace = atoi(maxdiskspace_str);
+ errno = 0;
+ s_maxdiskspace = strtol(maxdiskspace_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || s_maxdiskspace < -1 || s_maxdiskspace == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ maxdiskspace_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* Disk space are in MB but store in bytes */
switch (logtype) {
@@ -1538,9 +1588,9 @@ int
log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype, char *errorbuf, int apply)
{
int rv = LDAP_SUCCESS;
- int minfreespace; /* in megabytes */
- PRInt64 minfreespaceB; /* in bytes */
-
+ int64_t minfreespace; /* in megabytes */
+ int64_t minfreespaceB; /* in bytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1556,11 +1606,18 @@ log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype,
return rv;
}
- minfreespace = atoi(minfreespace_str);
+ errno = 0;
+ minfreespace = strtol(minfreespace_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || minfreespace < -1 || minfreespace == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ minfreespace_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* Disk space are in MB but store in bytes */
if (minfreespace >= 1) {
- minfreespaceB = (PRInt64)minfreespace * LOG_MB_IN_BYTES;
+ minfreespaceB = minfreespace * LOG_MB_IN_BYTES;
switch (logtype) {
case SLAPD_ACCESS_LOG:
LOG_ACCESS_LOCK_WRITE();
@@ -1602,10 +1659,10 @@ log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype,
int
log_set_expirationtime(const char *attrname, char *exptime_str, int logtype, char *errorbuf, int apply)
{
-
- int eunit, value, exptime;
+ int64_t eunit, value, exptime;
int rsec = 0;
int rv = 0;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1621,7 +1678,14 @@ log_set_expirationtime(const char *attrname, char *exptime_str, int logtype, cha
return rv;
}
- exptime = atoi(exptime_str); /* <= 0: no exptime */
+ errno = 0;
+ exptime = strtol(exptime_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || exptime < -1 || exptime == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ exptime_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1734,7 +1798,6 @@ log_set_expirationtimeunit(const char *attrname, char *expunit, int logtype, cha
} else {
slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "%s: invalid time unit \"%s\"", attrname, expunit);
rv = LDAP_OPERATIONS_ERROR;
- ;
}
/* return if we aren't doing this for real */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50393 - maxlogsperdir accepting negative values
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new 2e749e7 Ticket 50393 - maxlogsperdir accepting negative values
2e749e7 is described below
commit 2e749e72202d3dafefa27fc359dfe26e33f10303
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon May 20 11:38:05 2019 -0400
Ticket 50393 - maxlogsperdir accepting negative values
Description: Improve the log "digit" config setting validation
for all settings.
https://pagure.io/389-ds-base/issue/50393
Reviewed by: tbordaz, firstyear, mhonek, and spichugi (Thanks!!!!)
(cherry picked from commit ca70d06fbb7a2c06c62f0ba5b192dba36f24b8e3)
---
dirsrvtests/tests/suites/logging/__init__.py | 3 +
.../tests/suites/logging/logging_config_test.py | 86 +++++++++++++
ldap/servers/slapd/log.c | 143 +++++++++++++++------
3 files changed, 192 insertions(+), 40 deletions(-)
diff --git a/dirsrvtests/tests/suites/logging/__init__.py b/dirsrvtests/tests/suites/logging/__init__.py
new file mode 100644
index 0000000..7f812e3
--- /dev/null
+++ b/dirsrvtests/tests/suites/logging/__init__.py
@@ -0,0 +1,3 @@
+"""
+ :Requirement: 389-ds-base: Directory Server Logging Configurations
+"""
diff --git a/dirsrvtests/tests/suites/logging/logging_config_test.py b/dirsrvtests/tests/suites/logging/logging_config_test.py
new file mode 100644
index 0000000..4d8d68a
--- /dev/null
+++ b/dirsrvtests/tests/suites/logging/logging_config_test.py
@@ -0,0 +1,86 @@
+import logging
+import pytest
+import os
+import ldap
+from lib389._constants import *
+from lib389.topologies import topology_st as topo
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+if DEBUGGING:
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
+else:
+ logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+big_value = "1111111111111111111111111111111111111111111"
+
+
+(a)pytest.mark.parametrize("attr, invalid_vals, valid_vals",
+ [
+ ("logexpirationtime", ["-2", "0"], ["1", "-1"]),
+ ("maxlogsize", ["-2", "0"], ["100", "-1"]),
+ ("logmaxdiskspace", ["-2", "0"], ["100", "-1"]),
+ ("logminfreediskspace", ["-2", "0"], ["100", "-1"]),
+ ("mode", ["888", "778", "77", "7777"], ["777", "000", "600"]),
+ ("maxlogsperdir", ["-1", "0"], ["1", "20"]),
+ ("logrotationsynchour", ["-1", "24"], ["0", "23"]),
+ ("logrotationsyncmin", ["-1", "60"], ["0", "59"]),
+ ("logrotationtime", ["-2", "0"], ["100", "-1"])
+ ])
+def test_logging_digit_config(topo, attr, invalid_vals, valid_vals):
+ """Validate logging config settings
+
+ :id: a0ef30e5-538b-46fa-9762-01a4435a15e9
+ :setup: Standalone Instance
+ :steps:
+ 1. Test log expiration time
+ 2. Test log max size
+ 3. Test log max disk space
+ 4. Test log min disk space
+ 5. Test log mode
+ 6. Test log max number of logs
+ 7. Test log rotation hour
+ 8. Test log rotation minute
+ 9. Test log rotation time
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Success
+ 7. Success
+ 8. Success
+ 9. Success
+ """
+
+ accesslog_attr = "nsslapd-accesslog-{}".format(attr)
+ auditlog_attr = "nsslapd-auditlog-{}".format(attr)
+ auditfaillog_attr = "nsslapd-auditfaillog-{}".format(attr)
+ errorlog_attr = "nsslapd-errorlog-{}".format(attr)
+
+ # Test each log
+ for attr in [accesslog_attr, auditlog_attr, auditfaillog_attr, errorlog_attr]:
+ # Invalid values
+ for invalid_val in invalid_vals:
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, invalid_val)
+
+ # Invalid high value
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, big_value)
+
+ # Non digits
+ with pytest.raises(ldap.LDAPError):
+ topo.standalone.config.set(attr, "abc")
+
+ # Valid values
+ for valid_val in valid_vals:
+ topo.standalone.config.set(attr, valid_val)
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 7dd7154..2456abf 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -817,8 +817,9 @@ log_update_auditfaillogdir(char *pathname, int apply)
int
log_set_mode(const char *attrname, char *value, int logtype, char *errorbuf, int apply)
{
- int v = 0;
+ int64_t v = 0;
int retval = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (NULL == value) {
@@ -833,7 +834,18 @@ log_set_mode(const char *attrname, char *value, int logtype, char *errorbuf, int
return LDAP_SUCCESS;
}
- v = strtol(value, NULL, 8);
+ errno = 0;
+ v = strtol(value, &endp, 8);
+ if (*endp != '\0' || errno == ERANGE ||
+ strlen(value) != 3 ||
+ v > 0777 /* octet of 777 511 */ ||
+ v < 0)
+ {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s) (%ld), value must be three digits between 000 and 777",
+ value, attrname, v);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -895,9 +907,9 @@ int
log_set_numlogsperdir(const char *attrname, char *numlogs_str, int logtype, char *returntext, int apply)
{
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
-
+ char *endp = NULL;
int rv = LDAP_SUCCESS;
- int numlogs;
+ int64_t numlogs;
if (logtype != SLAPD_ACCESS_LOG &&
logtype != SLAPD_ERROR_LOG &&
@@ -911,7 +923,14 @@ log_set_numlogsperdir(const char *attrname, char *numlogs_str, int logtype, char
return rv;
}
- numlogs = atoi(numlogs_str);
+ errno = 0;
+ numlogs = strtol(numlogs_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || numlogs < 1) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be between 1 and 2147483647",
+ numlogs_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
if (numlogs >= 1) {
switch (logtype) {
@@ -960,21 +979,25 @@ int
log_set_logsize(const char *attrname, char *logsize_str, int logtype, char *returntext, int apply)
{
int rv = LDAP_SUCCESS;
- PRInt64 max_logsize; /* in bytes */
- int logsize; /* in megabytes */
+ int64_t max_logsize; /* in bytes */
+ int64_t logsize; /* in megabytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (!apply || !logsize_str || !*logsize_str)
return rv;
- logsize = atoi(logsize_str);
+ errno = 0;
+ logsize = strtol(logsize_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || logsize < -1 || logsize == 0) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ logsize_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* convert it to bytes */
- max_logsize = (PRInt64)logsize * LOG_MB_IN_BYTES;
-
- if (max_logsize <= 0) {
- max_logsize = -1;
- }
+ max_logsize = logsize * LOG_MB_IN_BYTES;
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1101,8 +1124,9 @@ log_set_rotationsync_enabled(const char *attrname, char *value, int logtype, cha
int
log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, char *returntext, int apply)
{
- int rhour = -1;
+ int64_t rhour = -1;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1115,12 +1139,19 @@ log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, cha
}
/* return if we aren't doing this for real */
- if (!apply) {
+ if (!apply || !rhour_str || !*rhour_str) {
return rv;
}
- if (rhour_str && *rhour_str != '\0')
- rhour = atol(rhour_str);
+ errno = 0;
+ rhour = strtol(rhour_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rhour < 0 || rhour > 23) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"0\" thru \"23\"",
+ rhour_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
if (rhour > 23)
rhour = rhour % 24;
@@ -1161,8 +1192,9 @@ log_set_rotationsynchour(const char *attrname, char *rhour_str, int logtype, cha
int
log_set_rotationsyncmin(const char *attrname, char *rmin_str, int logtype, char *returntext, int apply)
{
- int rmin = -1;
+ int64_t rmin = -1;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1175,14 +1207,18 @@ log_set_rotationsyncmin(const char *attrname, char *rmin_str, int logtype, char
}
/* return if we aren't doing this for real */
- if (!apply) {
+ if (!apply || !rmin_str || !*rmin_str) {
return rv;
}
- if (rmin_str && *rmin_str != '\0')
- rmin = atol(rmin_str);
- if (rmin > 59)
- rmin = rmin % 60;
+ errno = 0;
+ rmin = strtol(rmin_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rmin < 0 || rmin > 59) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be between \"0\" and \"59\"",
+ rmin_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1229,8 +1265,9 @@ log_set_rotationtime(const char *attrname, char *rtime_str, int logtype, char *r
{
int runit = 0;
- int value, rtime;
+ int64_t value, rtime;
int rv = LDAP_SUCCESS;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1247,7 +1284,14 @@ log_set_rotationtime(const char *attrname, char *rtime_str, int logtype, char *r
return rv;
}
- rtime = atoi(rtime_str);
+ errno = 0;
+ rtime = strtol(rtime_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || rtime < -1 || rtime == 0) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than \"0\"",
+ rtime_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
if (0 == rtime) {
rtime = -1; /* Value Range: -1 | 1 to PR_INT32_MAX */
@@ -1332,7 +1376,6 @@ log_set_rotationtimeunit(const char *attrname, char *runit, int logtype, char *e
int origvalue = 0, value = 0;
int runitType;
int rv = 0;
-
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1448,10 +1491,10 @@ int
log_set_maxdiskspace(const char *attrname, char *maxdiskspace_str, int logtype, char *errorbuf, int apply)
{
int rv = 0;
- PRInt64 mlogsize = 0; /* in bytes */
- PRInt64 maxdiskspace; /* in bytes */
- int s_maxdiskspace; /* in megabytes */
-
+ int64_t mlogsize = 0; /* in bytes */
+ int64_t maxdiskspace; /* in bytes */
+ int64_t s_maxdiskspace; /* in megabytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1465,7 +1508,14 @@ log_set_maxdiskspace(const char *attrname, char *maxdiskspace_str, int logtype,
if (!apply || !maxdiskspace_str || !*maxdiskspace_str)
return rv;
- s_maxdiskspace = atoi(maxdiskspace_str);
+ errno = 0;
+ s_maxdiskspace = strtol(maxdiskspace_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || s_maxdiskspace < -1 || s_maxdiskspace == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ maxdiskspace_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* Disk space are in MB but store in bytes */
switch (logtype) {
@@ -1538,9 +1588,9 @@ int
log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype, char *errorbuf, int apply)
{
int rv = LDAP_SUCCESS;
- int minfreespace; /* in megabytes */
- PRInt64 minfreespaceB; /* in bytes */
-
+ int64_t minfreespace; /* in megabytes */
+ int64_t minfreespaceB; /* in bytes */
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1556,11 +1606,18 @@ log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype,
return rv;
}
- minfreespace = atoi(minfreespace_str);
+ errno = 0;
+ minfreespace = strtol(minfreespace_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || minfreespace < -1 || minfreespace == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ minfreespace_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
/* Disk space are in MB but store in bytes */
if (minfreespace >= 1) {
- minfreespaceB = (PRInt64)minfreespace * LOG_MB_IN_BYTES;
+ minfreespaceB = minfreespace * LOG_MB_IN_BYTES;
switch (logtype) {
case SLAPD_ACCESS_LOG:
LOG_ACCESS_LOCK_WRITE();
@@ -1602,10 +1659,10 @@ log_set_mindiskspace(const char *attrname, char *minfreespace_str, int logtype,
int
log_set_expirationtime(const char *attrname, char *exptime_str, int logtype, char *errorbuf, int apply)
{
-
- int eunit, value, exptime;
+ int64_t eunit, value, exptime;
int rsec = 0;
int rv = 0;
+ char *endp = NULL;
slapdFrontendConfig_t *fe_cfg = getFrontendConfig();
if (logtype != SLAPD_ACCESS_LOG &&
@@ -1621,7 +1678,14 @@ log_set_expirationtime(const char *attrname, char *exptime_str, int logtype, cha
return rv;
}
- exptime = atoi(exptime_str); /* <= 0: no exptime */
+ errno = 0;
+ exptime = strtol(exptime_str, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || exptime < -1 || exptime == 0) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Invalid value \"%s\" for attribute (%s), value must be \"-1\" or greater than 0",
+ exptime_str, attrname);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
switch (logtype) {
case SLAPD_ACCESS_LOG:
@@ -1734,7 +1798,6 @@ log_set_expirationtimeunit(const char *attrname, char *expunit, int logtype, cha
} else {
slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "%s: invalid time unit \"%s\"", attrname, expunit);
rv = LDAP_OPERATIONS_ERROR;
- ;
}
/* return if we aren't doing this for real */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 4 months