[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 50909 - nsDS5ReplicaId cant be set to the old value it had before
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.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 59e3a4a Issue 50909 - nsDS5ReplicaId cant be set to the old value it had before
59e3a4a is described below
commit 59e3a4af5f126d60d1251a3c9dee4486802600cb
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Feb 24 15:32:03 2020 -0500
Issue 50909 - nsDS5ReplicaId cant be set to the old value it had before
Bug Description: We were not handling the process of changing the replica
type and id correctly. For one, we were not correctly
handling a change to a hub/consumer, but it just happened
to work by accident in most cases. In other caes you
could not change the rid more than once.
Fix Description: Changed the value checking to allow ID changes to 65535
which allowed the type/id pointers to be set correctly.
Then the checking of the type & ID change combination had
to be revised.
Also, removed the option to get just set the RID or type
from dsconf. Only replication promotion/demotion should
be touching these values.
relates: https://pagure.io/389-ds-base/issue/50909
Reviewed by: firstyear & tbordaz(Thanks!!)
(cherry picked from commit ea4fa549e6daeba648ce11c8c2ce4e7688ffab7b)
---
.../suites/replication/replica_config_test.py | 2 +-
.../plugins/replication/repl5_replica_config.c | 22 +++++++++++-----------
src/lib389/lib389/cli_conf/replication.py | 3 ---
3 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/dirsrvtests/tests/suites/replication/replica_config_test.py b/dirsrvtests/tests/suites/replication/replica_config_test.py
index 1855143..c2140a2 100644
--- a/dirsrvtests/tests/suites/replication/replica_config_test.py
+++ b/dirsrvtests/tests/suites/replication/replica_config_test.py
@@ -43,7 +43,7 @@ agmt_dict = {'cn': 'test_agreement',
repl_add_attrs = [('nsDS5ReplicaType', '-1', '4', overflow, notnum, '1'),
('nsDS5Flags', '-1', '2', overflow, notnum, '1'),
- ('nsDS5ReplicaId', '0', '65535', overflow, notnum, '1'),
+ ('nsDS5ReplicaId', '0', '65536', overflow, notnum, '1'),
('nsds5ReplicaPurgeDelay', '-2', too_big, overflow, notnum, '1'),
('nsDS5ReplicaBindDnGroupCheckInterval', '-2', too_big, overflow, notnum, '1'),
('nsds5ReplicaTombstonePurgeInterval', '-2', too_big, overflow, notnum, '1'),
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
index 02b36f6..d64d4bf 100644
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
@@ -461,7 +461,7 @@ replica_config_modify(Slapi_PBlock *pb,
}
} else if (strcasecmp(config_attr, attr_replicaId) == 0) {
int64_t rid = 0;
- if (repl_config_valid_num(config_attr, config_attr_value, 1, 65534, returncode, errortext, &rid) == 0) {
+ if (repl_config_valid_num(config_attr, config_attr_value, 1, MAX_REPLICA_ID, returncode, errortext, &rid) == 0) {
slapi_ch_free_string(&new_repl_id);
new_repl_id = slapi_ch_strdup(config_attr_value);
} else {
@@ -890,7 +890,7 @@ replica_config_search(Slapi_PBlock *pb,
static int
replica_config_change_type_and_id(Replica *r, const char *new_type, const char *new_id, char *returntext, int apply_mods)
{
- int type;
+ int type = REPLICA_TYPE_READONLY; /* by default - replica is read-only */
ReplicaType oldtype;
ReplicaId rid;
ReplicaId oldrid;
@@ -899,21 +899,21 @@ replica_config_change_type_and_id(Replica *r, const char *new_type, const char *
oldtype = replica_get_type(r);
oldrid = replica_get_rid(r);
- if (new_type == NULL) /* by default - replica is read-only */
- {
- type = REPLICA_TYPE_READONLY;
+ if (new_type == NULL) {
+ if (oldtype) {
+ type = oldtype;
+ }
} else {
type = atoi(new_type);
if (type <= REPLICA_TYPE_UNKNOWN || type >= REPLICA_TYPE_END) {
PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "invalid replica type %d", type);
return LDAP_OPERATIONS_ERROR;
}
- }
-
- /* disallow changing type to itself just to permit a replica ID change */
- if (oldtype == type) {
- PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "replica type is already %d - not changing", type);
- return LDAP_OPERATIONS_ERROR;
+ /* disallow changing type to itself just to permit a replica ID change */
+ if (oldtype == type) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "replica type is already %d - not changing", type);
+ return LDAP_OPERATIONS_ERROR;
+ }
}
if (type == REPLICA_TYPE_READONLY) {
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index 7acbb33..1c0c925 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -1189,9 +1189,6 @@ def create_parser(subparsers):
repl_set_parser = repl_subcommands.add_parser('set', help='Set an attribute in the replication configuration')
repl_set_parser.set_defaults(func=set_repl_config)
repl_set_parser.add_argument('--suffix', required=True, help='The DN of the replication suffix')
- repl_set_parser.add_argument('--replica-id', help="The Replication Identifier number")
- repl_set_parser.add_argument('--replica-role', help="The Replication role: master, hub, or consumer")
-
repl_set_parser.add_argument('--repl-add-bind-dn', help="Add a bind (supplier) DN")
repl_set_parser.add_argument('--repl-del-bind-dn', help="Remove a bind (supplier) DN")
repl_set_parser.add_argument('--repl-add-ref', help="Add a replication referral (for consumers only)")
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Ticket 50618 - support cgroupv2
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
tbordaz pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 1c3db9c Ticket 50618 - support cgroupv2
1c3db9c is described below
commit 1c3db9c847b355cae1a9ca604564884f23eaae1e
Author: William Brown <william(a)blackhats.net.au>
AuthorDate: Thu Feb 6 14:13:49 2020 +1000
Ticket 50618 - support cgroupv2
Bug Description: fedora 31 changes to cgroup v2 and I expect suse
to do the same soon. We should support this natively as part of
the memory limit detection.
Fix Description: Add support for cgroup v2
https://pagure.io/389-ds-base/issue/50618
Author: William Brown <william(a)blackhats.net.au>
Review by: tbordaz (Thanks!)
---
ldap/servers/slapd/slapi_pal.c | 133 +++++++++++++++++++++++++++++++++++------
1 file changed, 116 insertions(+), 17 deletions(-)
diff --git a/ldap/servers/slapd/slapi_pal.c b/ldap/servers/slapd/slapi_pal.c
index 600d03d..3ae7d12 100644
--- a/ldap/servers/slapd/slapi_pal.c
+++ b/ldap/servers/slapd/slapi_pal.c
@@ -27,6 +27,9 @@
#include <sys/time.h>
#include <sys/resource.h>
+/* directory check */
+#include <dirent.h>
+
#ifdef OS_solaris
#include <sys/procfs.h>
#endif
@@ -35,6 +38,14 @@
#include <sys/pstat.h>
#endif
+#include <sys/param.h>
+
+/* This warns if we have less than 128M avail */
+#define SPAL_WARN_MIN_BYTES 134217728
+
+#define CG2_HEADER_FORMAT "0::"
+#define CG2_HEADER_LEN strlen(CG2_HEADER_FORMAT)
+
static int_fast32_t
_spal_rlimit_get(int resource, uint64_t *soft_limit, uint64_t *hard_limit)
{
@@ -101,6 +112,48 @@ _spal_uint64_t_file_get(char *name, char *prefix, uint64_t *dest)
return retval;
}
+static int_fast32_t
+_spal_dir_exist(char *path)
+{
+ DIR* dir = opendir(path);
+ if (dir) {
+ closedir(dir);
+ return 1;
+ }
+ return 0;
+}
+
+static char *
+_spal_cgroupv2_path() {
+ FILE *f;
+ char s[256] = {0};
+ char *res = NULL;
+ /* We discover our path by looking at /proc/self/cgroup */
+ f = fopen("/proc/self/cgroup", "r");
+ if (!f) {
+ int errsrv = errno;
+ slapi_log_err(SLAPI_LOG_ERR, "_spal_get_uint64_t_file", "Unable to open file \"/proc/self/cgroup\". errno=%d\n", errsrv);
+ return NULL;
+ }
+
+ if (feof(f) == 0) {
+ if (fgets(s, MAXPATHLEN, f) != NULL) {
+ /* we now have a path in s, and the last byte must be NULL */
+ if ((strlen(s) >= CG2_HEADER_LEN) && strncmp(s, CG2_HEADER_FORMAT, CG2_HEADER_LEN) == 0) {
+ res = slapi_ch_calloc(1, MAXPATHLEN + 17);
+ snprintf(res, MAXPATHLEN + 16, "/sys/fs/cgroup%s", s + CG2_HEADER_LEN);
+ /* This always has a new line, so replace it if possible. */
+ size_t nl = strlen(res) - 1;
+ res[nl] = '\0';
+ }
+ }
+ }
+ /* Will return something like /sys/fs/cgroup/system.slice/system-dirsrv.slice/dirsrv(a)standalone1.service */
+
+ fclose(f);
+ return res;
+}
+
slapi_pal_meminfo *
spal_meminfo_get()
@@ -172,34 +225,73 @@ spal_meminfo_get()
uint64_t cg_mem_usage = 0;
uint64_t cg_mem_soft_avail = 0;
- char *f_cg_soft = "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes";
- char *f_cg_hard = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
- char *f_cg_usage = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
+ /* We have cgroup v1, so attempt to use it. */
+ if (_spal_dir_exist("/sys/fs/cgroup/memory")) {
+ char *f_cg_soft = "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes";
+ char *f_cg_hard = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
+ char *f_cg_usage = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
+ slapi_log_err(SLAPI_LOG_INFO, "spal_meminfo_get", "Found cgroup v1\n");
- if (_spal_uint64_t_file_get(f_cg_soft, NULL, &cg_mem_soft)) {
- slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_soft);
- }
+ if (_spal_uint64_t_file_get(f_cg_soft, NULL, &cg_mem_soft)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_soft);
+ }
- if (_spal_uint64_t_file_get(f_cg_hard, NULL, &cg_mem_hard)) {
- slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_hard);
- }
+ if (_spal_uint64_t_file_get(f_cg_hard, NULL, &cg_mem_hard)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_hard);
+ }
- if (_spal_uint64_t_file_get(f_cg_usage, NULL, &cg_mem_usage)) {
- slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_hard);
+ if (_spal_uint64_t_file_get(f_cg_usage, NULL, &cg_mem_usage)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", f_cg_usage);
+ }
+
+ } else {
+ /* We might have cgroup v2. Attempt to get the controller path ... */
+ char *ctrlpath = _spal_cgroupv2_path();
+ if (ctrlpath != NULL) {
+
+ char s[MAXPATHLEN + 33] = {0};
+ slapi_log_err(SLAPI_LOG_INFO, "spal_meminfo_get", "Found cgroup v2 -> %s\n", ctrlpath);
+ /* There are now three files we care about - memory.current, memory.high and memory.max */
+ /* For simplicity we re-use soft and hard */
+ /* If _spal_uint64_t_file_get() hit's "max" then these remain at 0 */
+ snprintf(s, MAXPATHLEN + 32, "%s/memory.current", ctrlpath);
+ if (_spal_uint64_t_file_get(s, NULL, &cg_mem_usage)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", s);
+ }
+
+ snprintf(s, MAXPATHLEN + 32, "%s/memory.high", ctrlpath);
+ if (_spal_uint64_t_file_get(s, NULL, &cg_mem_soft)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", s);
+ }
+
+ snprintf(s, MAXPATHLEN + 32, "%s/memory.max", ctrlpath);
+ if (_spal_uint64_t_file_get(s, NULL, &cg_mem_hard)) {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "Unable to retrieve %s. There may be no cgroup support on this platform\n", s);
+ }
+
+ slapi_ch_free_string(&ctrlpath);
+ } else {
+ slapi_log_err(SLAPI_LOG_WARNING, "spal_meminfo_get", "cgroups v1 or v2 unable to be read - may not be on this platform ...\n");
+ }
}
/*
* In some conditions, like docker, we only have a *hard* limit set.
* This obviously breaks our logic, so we need to make sure we correct this
*/
-
- if (cg_mem_hard != 0 && cg_mem_soft != 0 && cg_mem_hard < cg_mem_soft) {
- /* Right, we only have a hard limit. Impose a 10% watermark. */
- cg_mem_soft = cg_mem_hard * 0.9;
+ if ((cg_mem_hard != 0 && cg_mem_soft == 0) || (cg_mem_hard < cg_mem_soft)) {
+ /* Right, we only have a hard limit. Impose a 20% watermark. */
+ cg_mem_soft = cg_mem_hard * 0.8;
}
- if (cg_mem_soft != 0 && cg_mem_usage != 0 && cg_mem_soft > cg_mem_usage) {
- cg_mem_soft_avail = cg_mem_soft - cg_mem_usage;
+ if (cg_mem_usage != 0 && (cg_mem_soft != 0 || cg_mem_hard != 0)) {
+ if (cg_mem_soft > cg_mem_usage) {
+ cg_mem_soft_avail = cg_mem_soft - cg_mem_usage;
+ } else if (cg_mem_hard > cg_mem_usage) {
+ cg_mem_soft_avail = cg_mem_hard - cg_mem_usage;
+ } else {
+ slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "Your cgroup memory usage exceeds your hard limit?");
+ }
}
@@ -253,6 +345,13 @@ spal_meminfo_get()
return NULL;
}
+ if (mi->system_available_bytes < SPAL_WARN_MIN_BYTES) {
+ slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "Your system is reporting %" PRIu64" bytes available, which is less than the minimum recommended %" PRIu64 " bytes\n",
+ mi->system_available_bytes, SPAL_WARN_MIN_BYTES);
+ slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "This indicates heavy memory pressure or incorrect system resource allocation\n");
+ slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "Directory Server *may* crash as a result!!!\n");
+ }
+
slapi_log_err(SLAPI_LOG_TRACE, "spal_meminfo_get", "{pagesize_bytes = %" PRIu64 ", system_total_pages = %" PRIu64 ", system_total_bytes = %" PRIu64 ", process_consumed_pages = %" PRIu64 ", process_consumed_bytes = %" PRIu64 ", system_available_pages = %" PRIu64 ", system_available_bytes = %" PRIu64 "},\n",
mi->pagesize_bytes, mi->system_total_pages, mi->system_total_bytes, mi->process_consumed_pages, mi->process_consumed_bytes, mi->system_available_pages, mi->system_available_bytes);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.3.10 updated: Ticket 50898 - ldclt core dumped when run with -e genldif option
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
tbordaz pushed a commit to branch 389-ds-base-1.3.10
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.10 by this push:
new 4ae4468 Ticket 50898 - ldclt core dumped when run with -e genldif option
4ae4468 is described below
commit 4ae44681599385b07618b410cfeb7aa8518e7f3d
Author: Thierry Bordaz <tbordaz(a)redhat.com>
AuthorDate: Fri Feb 14 15:38:19 2020 +0100
Ticket 50898 - ldclt core dumped when run with -e genldif option
Bug Description:
ldctl can generate ldif file. If the template file or option
-e <objectclass> (person/InetOrgPerson/emailPerson) is missing,
then the attribute value is not set.
When dereferencing attribute.mod_values it crashes
Fix Description:
Test that attribute.mod_values is set. If it is not (tha
means the objectclass value was not provided) and return an error
https://pagure.io/389-ds-base/issue/50898
Reviewed by: ?
Platforms tested: F29
Flag Day: no
Doc impact: no
---
ldap/servers/slapd/tools/ldclt/ldapfct.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ldap/servers/slapd/tools/ldclt/ldapfct.c b/ldap/servers/slapd/tools/ldclt/ldapfct.c
index 0a9aba8..cf2660f 100644
--- a/ldap/servers/slapd/tools/ldclt/ldapfct.c
+++ b/ldap/servers/slapd/tools/ldclt/ldapfct.c
@@ -1646,12 +1646,18 @@ buildNewEntry(
nbAttribs = 0; /* No attributes yet */
attribute.mod_op = LDAP_MOD_ADD;
attribute.mod_type = "objectclass";
+ attribute.mod_values = NULL;
if (mctx.mode & OC_PERSON)
attribute.mod_values = strList1("person");
if (mctx.mode & OC_EMAILPERSON)
attribute.mod_values = strList1("emailPerson");
if (mctx.mode & OC_INETORGPRSON) /*JLS 07-11-00*/
attribute.mod_values = strList1("inetOrgPerson"); /*JLS 07-11-00*/
+ if (attribute.mod_values == NULL) {
+ printf("ldclt[%d]: T%03d: attribute objectclass not defined (supported values are person/emailPerson/inetOrgPerson)\n",
+ mctx.pid, tttctx->thrdNum);
+ return -1;
+ }
if (addAttrib(attrs, nbAttribs++, &attribute) < 0)
return (-1);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.1 updated: Ticket 50898 - ldclt core dumped when run with -e genldif option
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
tbordaz pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new 5015b07 Ticket 50898 - ldclt core dumped when run with -e genldif option
5015b07 is described below
commit 5015b07a422e21965b9457aa8b75678ab04ed420
Author: Thierry Bordaz <tbordaz(a)redhat.com>
AuthorDate: Fri Feb 14 15:38:19 2020 +0100
Ticket 50898 - ldclt core dumped when run with -e genldif option
Bug Description:
ldctl can generate ldif file. If the template file or option
-e <objectclass> (person/InetOrgPerson/emailPerson) is missing,
then the attribute value is not set.
When dereferencing attribute.mod_values it crashes
Fix Description:
Test that attribute.mod_values is set. If it is not (tha
means the objectclass value was not provided) and return an error
https://pagure.io/389-ds-base/issue/50898
Reviewed by: ?
Platforms tested: F29
Flag Day: no
Doc impact: no
---
ldap/servers/slapd/tools/ldclt/ldapfct.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ldap/servers/slapd/tools/ldclt/ldapfct.c b/ldap/servers/slapd/tools/ldclt/ldapfct.c
index dbfc553..8ed83cc 100644
--- a/ldap/servers/slapd/tools/ldclt/ldapfct.c
+++ b/ldap/servers/slapd/tools/ldclt/ldapfct.c
@@ -1529,12 +1529,18 @@ buildNewEntry(
nbAttribs = 0; /* No attributes yet */
attribute.mod_op = LDAP_MOD_ADD;
attribute.mod_type = "objectclass";
+ attribute.mod_values = NULL;
if (mctx.mode & OC_PERSON)
attribute.mod_values = strList1("person");
if (mctx.mode & OC_EMAILPERSON)
attribute.mod_values = strList1("emailPerson");
if (mctx.mode & OC_INETORGPRSON) /*JLS 07-11-00*/
attribute.mod_values = strList1("inetOrgPerson"); /*JLS 07-11-00*/
+ if (attribute.mod_values == NULL) {
+ printf("ldclt[%d]: T%03d: attribute objectclass not defined (supported values are person/emailPerson/inetOrgPerson)\n",
+ mctx.pid, tttctx->thrdNum);
+ return -1;
+ }
if (addAttrib(attrs, nbAttribs++, &attribute) < 0)
return (-1);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Ticket 50898 - ldclt core dumped when run with -e genldif option
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
tbordaz pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new cdd6267 Ticket 50898 - ldclt core dumped when run with -e genldif option
cdd6267 is described below
commit cdd62676376ccd0b9dde75fe400928d9346088ef
Author: Thierry Bordaz <tbordaz(a)redhat.com>
AuthorDate: Fri Feb 14 15:38:19 2020 +0100
Ticket 50898 - ldclt core dumped when run with -e genldif option
Bug Description:
ldctl can generate ldif file. If the template file or option
-e <objectclass> (person/InetOrgPerson/emailPerson) is missing,
then the attribute value is not set.
When dereferencing attribute.mod_values it crashes
Fix Description:
Test that attribute.mod_values is set. If it is not (tha
means the objectclass value was not provided) and return an error
https://pagure.io/389-ds-base/issue/50898
Reviewed by: ?
Platforms tested: F29
Flag Day: no
Doc impact: no
---
ldap/servers/slapd/tools/ldclt/ldapfct.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ldap/servers/slapd/tools/ldclt/ldapfct.c b/ldap/servers/slapd/tools/ldclt/ldapfct.c
index dbfc553..8ed83cc 100644
--- a/ldap/servers/slapd/tools/ldclt/ldapfct.c
+++ b/ldap/servers/slapd/tools/ldclt/ldapfct.c
@@ -1529,12 +1529,18 @@ buildNewEntry(
nbAttribs = 0; /* No attributes yet */
attribute.mod_op = LDAP_MOD_ADD;
attribute.mod_type = "objectclass";
+ attribute.mod_values = NULL;
if (mctx.mode & OC_PERSON)
attribute.mod_values = strList1("person");
if (mctx.mode & OC_EMAILPERSON)
attribute.mod_values = strList1("emailPerson");
if (mctx.mode & OC_INETORGPRSON) /*JLS 07-11-00*/
attribute.mod_values = strList1("inetOrgPerson"); /*JLS 07-11-00*/
+ if (attribute.mod_values == NULL) {
+ printf("ldclt[%d]: T%03d: attribute objectclass not defined (supported values are person/emailPerson/inetOrgPerson)\n",
+ mctx.pid, tttctx->thrdNum);
+ return -1;
+ }
if (addAttrib(attrs, nbAttribs++, &attribute) < 0)
return (-1);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch master updated: Bump version to 1.4.3.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 776c6ed Bump version to 1.4.3.3
776c6ed is described below
commit 776c6edf5dbaabccd6d2e12a4ebd6b39598dc142
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 13 14:58:44 2020 -0500
Bump version to 1.4.3.3
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index 0983b05..95c47c1 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=3.2
+VERSION_MAINT=3.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, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Bump version to 1.4.2.8
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.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 3aaa3e8 Bump version to 1.4.2.8
3aaa3e8 is described below
commit 3aaa3e820939a3cf321f8397c4ab541a5be38320
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 13 14:22:41 2020 -0500
Bump version to 1.4.2.8
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index 7f7787f..fa6d2f3 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=2.7
+VERSION_MAINT=2.8
# 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, 3 months
[389-ds-base] branch 389-ds-base-1.4.1 updated: Bump version to 1.4.1.15
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.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new 08c7516 Bump version to 1.4.1.15
08c7516 is described below
commit 08c751660d15c524b5bdea4346a13dd7cf24067a
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 13 13:57:46 2020 -0500
Bump version to 1.4.1.15
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index b529f21..569a556 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.14
+VERSION_MAINT=1.15
# 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, 3 months
[389-ds-base] 02/02: Issue 50855 - remove unused file from UI
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.1
in repository 389-ds-base.
commit 39d6b2134ab52c34339acfaf30106dfa8ab82e0b
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 13 11:47:04 2020 -0500
Issue 50855 - remove unused file from UI
Description: Remove pwpolicy,jsx as it was accidentally added to the last commit
---
.../389-console/src/lib/database/pwpolicy.jsx | 692 ---------------------
1 file changed, 692 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/database/pwpolicy.jsx b/src/cockpit/389-console/src/lib/database/pwpolicy.jsx
deleted file mode 100644
index ae68796..0000000
--- a/src/cockpit/389-console/src/lib/database/pwpolicy.jsx
+++ /dev/null
@@ -1,692 +0,0 @@
-import cockpit from "cockpit";
-import React from "react";
-import { NotificationController } from "../notifications.jsx";
-import { log_cmd } from "../tools.jsx";
-import {
- Form,
- Col,
- Nav,
- NavItem,
- Checkbox,
- TabContainer,
- TabContent,
- TabPane,
- TreeView,
- Spinner,
- Button
-} from "patternfly-react";
-import "../../css/ds.css";
-
-const GLOBAL_POLICY = "global-policy";
-
-export class ServerPwPolicy extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- loading: false,
- loaded: false,
- activeKey: 1,
- notifications: [],
- disableTree: false,
- nodes: [],
- node_name: "",
- node_text: "",
- dbtype: "",
-
- // Tuning settings
-
- }
- this.removeNotification = this.removeNotification.bind(this);
- this.addNotification = this.addNotification.bind(this);
- this.selectNode = this.selectNode.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.loadSuffixTree = this.loadSuffixTree.bind(this);
- this.savePwp = this.savePwp.bind(this);
- this.loadPwp = this.loadPwp.bind(this);
- this.loadGlobal = this.loadGlobal.bind(this);
- }
-
- componentWillMount() {
- // Loading config TODO
- if (!this.state.loaded) {
- this.loadGlobal();
- }
- }
-
- loadGlobal() {
- let cmd = [
- "dsconf", "-j", this.props.serverId, "pwpolicy", "get"
- ];
- log_cmd("loadGlobal", "Load global password policy", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- let config = JSON.parse(content);
- let attrs = config.attrs;
- // Handle the checkbox values
- let pwpLocal = false;
- let pwpIsglobal = false;
- let pwMustChange = false;
- let pwHistory = false;
- let pwTrackUpdate = false;
- let pwExpire = false;
- let pwSendExpire = false;
- let pwLockout = false;
- let pwUnlock = false;
- let pwCheckSyntax = false;
- let pwPalindrome = false;
- let pwDictCheck = false;
- let pwAllowHashed = false;
-
- if (attrs['nsslapd-pwpolicy-local'][0] == "on") {
- pwpLocal = true;
- }
- if (attrs['passwordchange'][0] == "on") {
- pwMustChange = true;
- }
- if (attrs['passwordhistory'][0] == "on") {
- pwHistory = true;
- }
- if (attrs['passwordtrackupdatetime'][0] == "on") {
- pwTrackUpdate = true;
- }
- if (attrs['passwordisglobalpolicy'][0] == "on") {
- pwpIsglobal = true;
- }
- if (attrs['passwordsendexpiringtime'][0] == "on") {
- pwSendExpire = true;
- }
- if (attrs['passwordlockout'][0] == "on") {
- pwLockout = true;
- }
- if (attrs['passwordunlock'][0] == "on") {
- pwUnlock = true;
- }
- if (attrs['passwordexp'][0] == "on") {
- pwExpire = true;
- }
- if (attrs['passwordchecksyntax'][0] == "on") {
- pwCheckSyntax = true;
- }
- if (attrs['passwordpalindrome'][0] == "on") {
- pwPalindrome = true;
- }
- if (attrs['passworddictcheck'][0] == "on") {
- pwExpire = true;
- }
- if (attrs['nsslapd-allow-hashed-passwords'][0] == "on") {
- pwAllowHashed = true;
- }
-
- this.setState(() => (
- {
- loaded: true,
- loading: false,
- // Settings
- pwpLocal: pwpLocal,
- pwpIsglobal: pwpIsglobal,
- pwMustChange: pwMustChange,
- pwTrackUpdate: pwTrackUpdate,
- pwExpire: pwExpire,
- pwSendExpire: pwSendExpire,
- pwLockout: pwLockout,
- pwUnlock: pwUnlock,
- pwCheckSyntax: pwCheckSyntax,
- pwPalindrome: pwPalindrome,
- pwDictCheck: pwDictCheck,
- pwAllowHashed: pwAllowHashed,
- passwordstoragescheme: attrs['passwordstoragescheme'][0],
- pwInHistory: attrs['passwordinhistory'][0],
- pwWarning: attrs['passwordwarning'][0],
- pwMaxAge: attrs['passwordmaxage'][0],
- pwMinAge: attrs['passwordminage'][0],
- pwGraceLimit: attrs['passwordgracelimit'][0],
- pwLockoutDur: attrs['passwordlockoutduration'][0],
- pwMaxFailure: attrs['passwordmaxfailure'][0],
- pwResetFailureCount: attrs['passwordresetfailurecount'][0],
- pwMinLen: attrs['passwordminlength'][0],
- pwMinDigit: attrs['passwordmindigits'][0],
- pwMinAlpha: attrs['passwordminalphas'][0],
- pwMinUppers: attrs['passwordminuppers'][0],
- pwMinLowers: attrs['passwordminlowers'][0],
- pwMinSpecial: attrs['passwordminspecials'][0],
- pwMin8bit: attrs['passwordmin8bit'][0],
- pwMaxRepeats: attrs['passwordmaxrepeats'][0],
- pwMaxSeq: attrs['passwordmaxsequence'][0],
- pwMaxSeqSets: attrs['passwordmaxseqsets'][0],
- pwMaxClass: attrs['passwordmaxclasschars'][0],
- pwMinCat: attrs['passwordmincategories'][0],
- pwMinTokenLen: attrs['passwordmintokenlength'][0],
- pwBadWords: attrs['passwordbadwords'][0],
- pwUserAttrs: attrs['passworduserattributes'][0],
- pwDictPath: attrs['passworddictpath'][0],
- // Record original values
- _pwpLocal: pwpLocal,
- _pwpIsglobal: pwpIsglobal,
- _pwMustChange: pwMustChange,
- _pwTrackUpdate: pwTrackUpdate,
- _pwExpire: pwExpire,
- _pwSendExpire: pwSendExpire,
- _pwLockout: pwLockout,
- _pwUnlock: pwUnlock,
- _pwCheckSyntax: pwCheckSyntax,
- _pwPalindrome: pwPalindrome,
- _pwDictCheck: pwDictCheck,
- _pwAllowHashed: pwAllowHashed,
- _passwordstoragescheme: attrs['passwordstoragescheme'][0],
- _pwWarning: attrs['passwordwarning'][0],
- _pwInHistory: attrs['passwordinhistory'][0],
- _pwMaxAge: attrs['passwordmaxage'][0],
- _pwMinAge: attrs['passwordminage'][0],
- _pwGraceLimit: attrs['passwordgracelimit'][0],
- _pwLockoutDur: attrs['passwordlockoutduration'][0],
- _pwMaxFailure: attrs['passwordmaxfailure'][0],
- _pwResetFailureCount: attrs['passwordresetfailurecount'][0],
- _pwMinLen: attrs['passwordminlength'][0],
- _pwMinDigit: attrs['passwordmindigits'][0],
- _pwMinAlpha: attrs['passwordminalphas'][0],
- _pwMinUppers: attrs['passwordminuppers'][0],
- _pwMinLowers: attrs['passwordminlowers'][0],
- _pwMinSpecial: attrs['passwordminspecials'][0],
- _pwMin8bit: attrs['passwordmin8bit'][0],
- _pwMaxRepeats: attrs['passwordmaxrepeats'][0],
- _pwMaxSeq: attrs['passwordmaxsequence'][0],
- _pwMaxSeqSets: attrs['passwordmaxseqsets'][0],
- _pwMaxClass: attrs['passwordmaxclasschars'][0],
- _pwMinCat: attrs['passwordmincategories'][0],
- _pwMinTokenLen: attrs['passwordmintokenlength'][0],
- _pwBadWords: attrs['passwordbadwords'][0],
- _pwUserAttrs: attrs['passworduserattributes'][0],
- _pwDictPath: attrs['passworddictpath'][0],
- })
- );
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loaded: true,
- loading: false,
- });
- this.addNotification(
- "error",
- `Error loading global password policy - ${errMsg.desc}`
- );
- });
- }
-
- loadLocal(name) {
- // What makes this tough is that local polices can only have a single
- // attribute, while global has them all. So we have to check every
- // single possible attribute if it's present.
- this.setState({
- loading: true,
- });
- let cmd = [
- "dsconf", "-j", this.props.serverId, "localpwp", "get", name
- ];
- log_cmd("loadLocal", "Load local password policy", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- let config = JSON.parse(content);
- let attrs = config.attrs;
- // Handle the checkbox values
- let pwMustChange = false;
- let pwHistory = false;
- let pwTrackUpdate = false;
- let pwExpire = false;
- let pwSendExpire = false;
- let pwLockout = false;
- let pwUnlock = false;
- let pwCheckSyntax = false;
- let pwPalindrome = false;
- let pwDictCheck = false;
- let pwAllowHashed = false;
- let pwStorageScheme = "PBKDF2_SHA256";
- let pwInHistory = "";
- let pwWarning = "";
- let pwMaxAge = "";
- let pwMinAge = "";
- let pwGraceLimit = "";
- let pwLockoutDur = "";
- let pwMaxFailure = "";
- let pwResetFailureCount = "";
- let pwMinLen = "";
- let pwMinDigit = "";
- let pwMinAlpha = "";
- let pwMinUppers = "";
- let pwMinLowers = "";
- let pwMinSpecial = "";
- let pwMin8bit = "";
- let pwMaxRepeats = "";
- let pwMaxSeq = "";
- let pwMaxSeqSets = "";
- let pwMaxClass = "";
- let pwMinCat = "";
- let pwMinTokenLen = "";
- let pwBadWords = "";
- let pwUserAttrs = "";
- let pwDictPath = "";
-
- if ('passwordchange' in attrs && attrs['passwordchange'][0] == "on") {
- pwMustChange = true;
- }
- if ('passwordhistory' in attrs && attrs['passwordhistory'][0] == "on") {
- pwHistory = true;
- }
- if ('passwordtrackupdatetime' in attrs && attrs['passwordtrackupdatetime'][0] == "on") {
- pwTrackUpdate = true;
- }
- if ('passwordsendexpiringtime' in attrs && attrs['passwordsendexpiringtime'][0] == "on") {
- pwSendExpire = true;
- }
- if ('passwordlockout' in attrs && attrs['passwordlockout'][0] == "on") {
- pwLockout = true;
- }
- if ('passwordunlock' in attrs && attrs['passwordunlock'][0] == "on") {
- pwUnlock = true;
- }
- if ('passwordexp' in attrs && attrs['passwordexp'][0] == "on") {
- pwExpire = true;
- }
- if ('passwordchecksyntax' in attrs && attrs['passwordchecksyntax'][0] == "on") {
- pwCheckSyntax = true;
- }
- if ('passwordpalindrome' in attrs && attrs['passwordpalindrome'][0] == "on") {
- pwPalindrome = true;
- }
- if ('passworddictcheck' in attrs && attrs['passworddictcheck'][0] == "on") {
- pwExpire = true;
- }
- if ('passwordstoragescheme' in attrs) {
- pwStorageScheme = attrs['passwordstoragescheme'][0];
- }
- if ('passwordinhistory' in attrs) {
- pwInHistory = attrs['passwordinhistory'][0];
- }
- if ('passwordwarning' in attrs) {
- pwWarning = attrs['passwordwarning'][0];
- }
- if ('passwordmaxage' in attrs) {
- pwMaxAge = attrs['passwordmaxage'][0];
- }
- if ('passwordminage' in attrs) {
- pwMinAge = attrs['passwordminage'][0];
- }
- if ('passwordgracelimit' in attrs) {
- pwMinAge = attrs['passwordgracelimit'][0];
- }
- if ('passwordlockoutduration' in attrs) {
- pwLockoutDur = attrs['passwordlockoutduration'][0];
- }
- if ('passwordmaxfailure' in attrs) {
- pwMaxFailure = attrs['passwordmaxfailure'][0];
- }
- if ('passwordresetfailurecount' in attrs) {
- pwResetFailureCount = attrs['passwordresetfailurecount'][0];
- }
- if ('passwordminlength' in attrs) {
- pwMinLen = attrs['passwordminlength'][0];
- }
- if ('passwordmindigits' in attrs) {
- pwMinDigit = attrs['passwordmindigits'][0];
- }
- if ('passwordminalphas' in attrs) {
- pwMinAlpha = attrs['passwordminalphas'][0];
- }
- if ('passwordminuppers' in attrs) {
- pwMinLen = attrs['passwordminuppers'][0];
- }
- if ('passwordminlowers' in attrs) {
- pwMinUppers = attrs['passwordminlowers'][0];
- }
- if ('passwordminspecials' in attrs) {
- pwMinSpecial = attrs['passwordminspecials'][0];
- }
- if ('passwordmin8bit' in attrs) {
- pwMin8bit = attrs['passwordmin8bit'][0];
- }
- if ('passwordmaxrepeats' in attrs) {
- pwMaxRepeats = attrs['passwordmaxrepeats'][0];
- }
- if ('passwordmaxsequence' in attrs) {
- pwMaxSeq = attrs['passwordmaxsequence'][0];
- }
- if ('passwordmaxseqsets' in attrs) {
- pwMaxSeqSets = attrs['passwordmaxseqsets'][0];
- }
- if ('passwordmaxclasschars' in attrs) {
- pwMaxClass = attrs['passwordmaxclasschars'][0];
- }
- if ('passwordmincategories' in attrs) {
- pwMinCat = attrs['passwordmincategories'][0];
- }
- if ('passwordmintokenlength' in attrs) {
- pwMinTokenLen = attrs['passwordmintokenlength'][0];
- }
- if ('passwordbadwords' in attrs) {
- pwBadWords = attrs['passwordbadwords'][0];
- }
- if ('passworduserattributes' in attrs) {
- pwUserAttrs = attrs['passworduserattributes'][0];
- }
- if ('passworddictpath' in attrs) {
- pwDictPath = attrs['passworddictpath'][0];
- }
-
- this.setState(() => (
- {
- loading: false,
- // Settings
- pwLocalMustChange: pwMustChange,
- pwLocalTrackUpdate: pwTrackUpdate,
- pwLocalExpire: pwExpire,
- pwLocalSendExpire: pwSendExpire,
- pwLocalLockout: pwLockout,
- pwLocalUnlock: pwUnlock,
- pwLocalCheckSyntax: pwCheckSyntax,
- pwLocalPalindrome: pwPalindrome,
- pwLocalDictCheck: pwDictCheck,
- pwLocalStorageScheme: pwStorageScheme,
- pwLocalInHistory: pwInHistory,
- pwLocalWarning: pwWarning,
- pwLocalMaxAge: pwMaxAge,
- pwLocalMinAge: pwMinAge,
- pwLocalGraceLimit: pwGraceLimit,
- pwLocalLockoutDur: pwLockoutDur,
- pwLocalMaxFailure: pwMaxFailure,
- pwLocalResetFailureCount: pwResetFailureCount,
- pwLocalMinLen: pwMinLen,
- pwLocalMinDigit: pwMinDigit,
- pwLocalMinAlpha: pwMinAlpha,
- pwLocalMinUppers: pwMinUppers,
- pwLocalMinLowers: pwMinLowers,
- pwLocalMinSpecial: pwMinSpecial,
- pwLocalMin8bit: pwMin8bit,
- pwLocalMaxRepeats: pwMaxRepeats,
- pwLocalMaxSeq: pwMaxSeq,
- pwLocalMaxSeqSets: pwMaxSeqSets,
- pwLocalMaxClass: pwMaxClass,
- pwLocalMinCat: pwMinCat,
- pwLocalMinTokenLen: pwMinTokenLen,
- pwLocalBadWords: pwBadWords,
- pwLocalUserAttrs: pwUserAttrs,
- pwLocalDictPath: pwDictPath,
- // Record original values
- _pwLocalMustChange: pwMustChange,
- _pwLocalTrackUpdate: pwTrackUpdate,
- _pwLocalExpire: pwExpire,
- _pwLocalSendExpire: pwSendExpire,
- _pwLocalLockout: pwLockout,
- _pwLocalUnlock: pwUnlock,
- _pwLocalCheckSyntax: pwCheckSyntax,
- _pwLocalPalindrome: pwPalindrome,
- _pwLocalDictCheck: pwDictCheck,
- _pwLocalStorageScheme: pwStorageScheme,
- _pwLocalInHistory: pwInHistory,
- _pwLocalWarning: pwWarning,
- _pwLocalMaxAge: pwMaxAge,
- _pwLocalMinAge: pwMinAge,
- _pwLocalGraceLimit: pwGraceLimit,
- _pwLocalLockoutDur: pwLockoutDur,
- _pwLocalMaxFailure: pwMaxFailure,
- _pwLocalResetFailureCount: pwResetFailureCount,
- _pwLocalMinLen: pwMinLen,
- _pwLocalMinDigit: pwMinDigit,
- _pwLocalMinAlpha: pwMinAlpha,
- _pwLocalMinUppers: pwMinUppers,
- _pwLocalMinLowers: pwMinLowers,
- _pwLocalMinSpecial: pwMinSpecial,
- _pwLocalMin8bit: pwMin8bit,
- _pwLocalMaxRepeats: pwMaxRepeats,
- _pwLocalMaxSeq: pwMaxSeq,
- _pwLocalMaxSeqSets: pwMaxSeqSets,
- _pwLocalMaxClass: pwMaxClass,
- _pwLocalMinCat: pwMinCat,
- _pwLocalMinTokenLen: pwMinTokenLen,
- _pwLocalBadWords: pwBadWords,
- _pwLocalUserAttrs: pwUserAttrs,
- _pwLocalDictPath: pwDictPath,
- })
- );
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loaded: true,
- loading: false,
- });
- this.addNotification(
- "error",
- `Error loading local password policy - ${errMsg.desc}`
- );
- });
- }
-
- loadSuffixTree() {
- let cmd = [
- "dsconf", "-j", this.props.serverId, "backend", "get-tree",
- ];
- log_cmd("loadSuffixTree", "Start building the suffix tree for local pwp", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- let treeData = [];
- if (content != "") {
- treeData = JSON.parse(content);
- }
- let basicData = [
- {
- text: "Global Password Policy",
- selectable: true,
- selected: true,
- icon: "pficon-home",
- id: "global-policy",
-
- },
- {
- text: "Suffixes",
- icon: "pficon-catalog",
- state: {"expanded": true},
- selectable: false,
- id: "pwp-suffixes",
- nodes: []
- }
- ];
- let current_node = this.state.node_name;
- basicData[3].nodes = treeData;
-
- this.setState(() => ({
- nodes: basicData,
- node_name: current_node,
- }), this.update_tree_nodes);
- });
- }
-
- selectNode(selectedNode) {
- if (selectedNode.selected) {
- return;
- }
- this.setState({
- disableTree: true // Disable the tree to allow node to be fully loaded
- });
-
- if (selectedNode.id == GLOBAL_POLICY) {
- // Nothing special to do, this has already been loaded
- this.setState(prevState => {
- return {
- nodes: this.nodeSelector(prevState.nodes, selectedNode),
- node_name: selectedNode.id,
- node_text: selectedNode.text,
- dbtype: selectedNode.type,
- bename: "",
- };
- });
- } else {
- if (selectedNode.id in this.state) {
- // This suffix is already cached, just use what we have...
- this.setState(prevState => {
- return {
- nodes: this.nodeSelector(prevState.nodes, selectedNode),
- node_name: selectedNode.id,
- node_text: selectedNode.text,
- dbtype: selectedNode.type,
- bename: selectedNode.be,
- };
- });
- } else {
- // Load this suffix
- if (selectedNode.type == "suffix" || selecetedNode.type == "subsuffix") {
- this.loadSuffix(selectedNode.id);
- }
- this.setState(prevState => {
- return {
- nodes: this.nodeSelector(prevState.nodes, selectedNode),
- node_name: selectedNode.id,
- node_text: selectedNode.text,
- dbtype: selectedNode.type,
- bename: selectedNode.be,
- };
- });
- }
- }
- }
-
- nodeSelector(nodes, targetNode) {
- return nodes.map(node => {
- if (node.nodes) {
- return {
- ...node,
- nodes: this.nodeSelector(node.nodes, targetNode),
- selected: node.id === targetNode.id ? !node.selected : false
- };
- } else if (node.id === targetNode.id) {
- return { ...node, selected: !node.selected };
- } else if (node.id !== targetNode.id && node.selected) {
- return { ...node, selected: false };
- } else {
- return node;
- }
- });
- }
-
- addNotification(type, message, timerdelay, persistent) {
- this.setState(prevState => ({
- notifications: [
- ...prevState.notifications,
- {
- key: prevState.notifications.length + 1,
- type: type,
- persistent: persistent,
- timerdelay: timerdelay,
- message: message,
- }
- ]
- }));
- }
-
- removeNotification(notificationToRemove) {
- this.setState({
- notifications: this.state.notifications.filter(
- notification => notificationToRemove.key !== notification.key
- )
- });
- }
-
- update_tree_nodes() {
- // Set title to the text value of each suffix node. We need to do this
- // so we can read long suffixes in the UI tree div
- let elements = document.getElementsByClassName('treeitem-row');
- for (let el of elements) {
- el.setAttribute('title', el.innerText);
- }
- this.setState({
- disableTree: false
- });
- }
-
- render() {
- let pwp_element = "";
- let disabled = "tree-view-container";
- if (this.state.disableTree) {
- disabled = "tree-view-container ds-disabled";
- }
- let reloadSpinner = "";
- if (this.state.loading) {
- reloadSpinner = <Spinner loading size="md" />;
- }
-
- if (this.state.loaded) {
- if (this.state.node_name == GLOBAL_POLICY || this.state.node_name == "") {
- pwp_element =
- <GlobalPwp
- serverId={this.props.serverId}
- addNotification={this.addNotification}
- reload={this.loadGlobalConfig}
- data={this.state.globalDBConfig}
- enableTree={this.enableTree}
- key={this.state.configUpdated}
- />;
- } else if (this.state.dbtype == "suffix" || this.state.dbtype == "subsuffix") {
- if (this.state.suffixLoading) {
- pwp_element =
- <div className="ds-margin-top ds-loading-spinner ds-center">
- <h4>Loading password policy for <b>{this.state.node_text} ...</b></h4>
- <Spinner className="ds-margin-top-lg" loading size="md" />
- </div>;
- } else {
- db_element =
- <Suffix
-
- />;
- }
- } else {
- // Chaining
- pwp_element =
- <div className="ds-margin-top ds-loading-spinner ds-center">
- <h4>You can not have local password policies on a database link: <b>{this.state.node_text}</b></h4>
- </div>;
- }
- }
-
- return (
- <div className="container-fluid">
- <NotificationController
- notifications={this.state.notifications}
- removeNotificationAction={this.removeNotification}
- />
- <Row>
- <Col sm={12} className="ds-word-wrap">
- <ControlLabel className="ds-suffix-header">
- Password Policy
- <Icon className="ds-left-margin ds-refresh"
- type="fa" name="refresh" title="Refresh password policies"
- onClick={() => {
- this.loadSuffixTree(1);
- }}
- />
- </ControlLabel>
- {reloadSpinner}
- </Col>
- </Row>
- <hr />
- <div className="ds-container">
- <div className="ds-tree">
- <div className={disabled} id="pwp-tree"
- style={treeViewContainerStyles}>
- <TreeView
- nodes={nodes}
- highlightOnHover
- highlightOnSelect
- selectNode={this.selectNode}
- />
- </div>
- </div>
- <div className="ds-tree-content">
- {pwp_element}
- </div>
- </div>
- </div>
- );
- }
-}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] 01/02: Issue 50855 - UI: Port Server Tab to React
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.1
in repository 389-ds-base.
commit 18ce6a41af5d791653415ed3206d1a88d4bdb8c5
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri Jan 31 15:54:21 2020 -0500
Issue 50855 - UI: Port Server Tab to React
Description: Ported the server tab to reactJS. Also made other changes:
- Moved Password policy to the database tab tree.
- Moved the Security Tab to the Server Tab tree.
- Fixed all the typeAhead errors
- Removed unused CSS classes
relates: https://pagure.io/389-ds-base/issue/50855
Reviewed by: spichugi(Thanks!)
---
src/cockpit/389-console/src/css/ds.css | 208 +-
src/cockpit/389-console/src/database.jsx | 89 +-
src/cockpit/389-console/src/ds.js | 647 +++++-
src/cockpit/389-console/src/index.es6 | 13 +-
src/cockpit/389-console/src/index.html | 48 +-
.../src/lib/database/databaseConfig.jsx | 68 +-
.../src/lib/database/databaseTables.jsx | 190 ++
.../389-console/src/lib/database/globalPwp.jsx | 1345 +++++++++++
.../389-console/src/lib/database/indexes.jsx | 10 +-
.../389-console/src/lib/database/localPwp.jsx | 2375 ++++++++++++++++++++
.../389-console/src/lib/database/pwpolicy.jsx | 692 ++++++
.../389-console/src/lib/monitor/monitorModals.jsx | 8 +-
.../389-console/src/lib/plugins/accountPolicy.jsx | 42 +-
.../src/lib/plugins/attributeUniqueness.jsx | 32 +-
.../389-console/src/lib/plugins/autoMembership.jsx | 22 +-
src/cockpit/389-console/src/lib/plugins/dna.jsx | 9 +-
.../src/lib/plugins/linkedAttributes.jsx | 19 +-
.../389-console/src/lib/plugins/managedEntries.jsx | 23 +-
.../389-console/src/lib/plugins/memberOf.jsx | 62 +-
.../src/lib/plugins/passthroughAuthentication.jsx | 17 +-
.../src/lib/plugins/referentialIntegrity.jsx | 13 +-
.../389-console/src/lib/plugins/retroChangelog.jsx | 12 +-
.../src/lib/plugins/rootDNAccessControl.jsx | 16 +-
.../389-console/src/lib/security/ciphers.jsx | 6 +-
.../389-console/src/lib/server/accessLog.jsx | 763 +++++++
.../389-console/src/lib/server/auditLog.jsx | 623 +++++
.../389-console/src/lib/server/auditfailLog.jsx | 621 +++++
.../389-console/src/lib/server/errorLog.jsx | 956 ++++++++
src/cockpit/389-console/src/lib/server/ldapi.jsx | 331 +++
src/cockpit/389-console/src/lib/server/sasl.jsx | 790 +++++++
.../389-console/src/lib/server/serverModals.jsx | 224 ++
.../389-console/src/lib/server/serverTables.jsx | 247 ++
.../389-console/src/lib/server/settings.jsx | 1348 +++++++++++
src/cockpit/389-console/src/lib/server/tuning.jsx | 556 +++++
src/cockpit/389-console/src/replication.jsx | 2 +-
src/cockpit/389-console/src/security.jsx | 141 +-
src/cockpit/389-console/src/server.jsx | 345 +++
src/cockpit/389-console/src/servers.html | 1269 -----------
src/cockpit/389-console/src/servers.js | 1808 ---------------
src/cockpit/389-console/webpack.config.js | 2 -
src/lib389/lib389/_mapped_object.py | 13 +-
src/lib389/lib389/backend.py | 4 +-
src/lib389/lib389/cli_base/__init__.py | 7 +-
src/lib389/lib389/cli_conf/backend.py | 20 +-
src/lib389/lib389/cli_conf/chaining.py | 4 +-
src/lib389/lib389/cli_conf/conflicts.py | 6 +-
src/lib389/lib389/cli_conf/monitor.py | 2 +-
src/lib389/lib389/cli_conf/plugin.py | 4 +-
src/lib389/lib389/cli_conf/pwpolicy.py | 85 +-
src/lib389/lib389/cli_conf/replication.py | 24 +-
src/lib389/lib389/cli_conf/saslmappings.py | 37 +-
src/lib389/lib389/cli_conf/schema.py | 16 +-
src/lib389/lib389/cli_conf/security.py | 10 +-
src/lib389/lib389/cli_ctl/health.py | 4 +-
src/lib389/lib389/cli_ctl/instance.py | 2 +-
src/lib389/lib389/cli_ctl/nsstate.py | 2 +-
src/lib389/lib389/pwpolicy.py | 77 +-
57 files changed, 12535 insertions(+), 3774 deletions(-)
diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css
index a71f615..82b1ab3 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -20,7 +20,7 @@
}
.ds-left-align {
- right: 0 !important;
+ text-align: left !important;
}
/* Main nav page index.html */
@@ -37,36 +37,11 @@
margin-left: 65px;
}
-.ds-oc-add-del-btn {
- padding: 0 !important;
- margin: 10px !important;
- width: 130px;
- height: 26px;
- text-align: center;
-}
-
-.ds-oc-must-buttons {
- padding: 0 !important;
- padding-left: 3px;
- margin-top: 70px;
- margin-bottom: 75px;
- margin-left: 5px;
- margin-right: 9px;
-}
-
/* This can probably be removed once we switch to PF React 4 - suffix action button */
.ds-action-button {
color: white;
}
-.ds-oc-may-buttons {
- padding0: !important;
- padding-left: 3px;
- margin-top: 4px;
- margin-left: 5px;
- margin-right: 9px;
-}
-
.ds-refresh {
background-color: #0088ce;
background-image: linear-gradient(to bottom,#39a5dc 0,#0088ce 100%);
@@ -124,6 +99,10 @@ td {
padding: 0px !important;
}
+.ds-width-sm {
+ max-width: 100px;
+}
+
.ds-tree {
font-size: 0.5;
background-color: #f8f8f8;
@@ -164,12 +143,6 @@ td {
padding-left: 5px;
}
-.ds-input-auto-good {
- width: 100%;
- border-color: green;
- padding-left: 5px;
-}
-
.ds-input-right {
text-align: right;
}
@@ -182,14 +155,6 @@ td {
max-width: 65px !important;
}
-.ds-lag-report {
- min-width: 800px !important;
-}
-
-.ds-repl-mgr {
- max-width: 600px !important;
-}
-
.ds-history-input {
margin-top: !important;
margin-right: 5px;
@@ -204,53 +169,10 @@ td {
width: 50px;
}
-.ds-divider-med {
- width: 15px;
-}
-
-.ds-repl-managers-list {
- width: 420px;
- height: 200px;
-}
-
-.ds-repl-managers-button {
- margin-top: -15px;
- margin-bottom: 10px;
- margin-left: 0px;
-}
-
-.ds-repl-manager-checkbox {
- margin-top: 20px !important;
- padding-top: 20px !important;
- margin-left: 10px !important;
- margin-right: 10px !important;
-}
-
.ds-label {
vertical-align: middle !important; /* Fixes any weird issues in Firefox and IE */
}
-/* Replication styles */
-.ds-agmt-dropdown {
- padding: 0px !important;
- line-height: 0 !important;
- overflow: visible !important;
- width: auto !important;
-}
-
-.ds-oc-dropdown {
- margin-top: 5px;
- color: #181818 !important;
- padding: 0px !important;
- line-height: 0 !important;
- height: 30px;
- max-height: 200px !important;
- width: 315px !important;
- text-align: left;
- outline: 0 !important;
- overflow: auto;
-}
-
.ds-dblink-dropdown {
width: 140px !important;
text-align: left;
@@ -284,15 +206,6 @@ td {
line-height: 1;
}
-.ds-sasl-table {
- background-color: white !important;
- padding: 0px;
- border: 1px solid #909090;
- table-layout: fixed;
- clear: both;
- word-wrap: break-word !important;
- text-align: center;
-}
.ds-db-table {
border: 1px solid #d1d1d1;
@@ -357,36 +270,12 @@ td {
margin-left: 0 !important;
}
-.ds-fractional-list {
- width: 230px;
- min-height: 100px !important;
- max-height: 100px !important;
- overflow-y: scroll;
-}
-
-.ds-fractional-btn {
- width: 150px;
- height: 30px;
- margin: 5px 5px 5px 10px;
-}
-
-.ds-agmt-schedule-checkbox {
- margin: 8px !important;
- padding: 5px;
-}
-
.ds-config-checkbox {
margin-top: 10px !important;
margin-bottom: 10px !important;
margin-right: 5px !important;
}
-.ds-server-checkbox {
- margin-top: 15px !important;
- margin-right: 12px !important;
- padding: 5px;
-}
-
.ds-send-expiring-checkbox {
margin-top: 12px !important;
margin-right: 12px !important;
@@ -411,12 +300,6 @@ td {
margin-bottom: 10px !important;
}
-.ds-config-label-med {
- margin-top: 10px;
- width: 125px !important;
- margin-bottom: 10px !important;
-}
-
.ds-config-sub-label {
margin-top: 10px;
width: 225px !important;
@@ -429,11 +312,6 @@ td {
margin-bottom: 10px !important;
}
-.ds-cleanallruv-label {
- width: 150px !important;
- margin-bottom: 10px !important;
-}
-
.ds-expire-label {
margin-top: 7px;
margin-bottom: 7px;
@@ -444,30 +322,10 @@ td {
padding: 10px;
}
-.ds-config-diskmon-label {
- width: 210px !important;
- padding-left: 30px;
- margin-top: 10px;
-}
-
-.ds-config-indent-sm-label {
- margin-top: 10px;
- margin-bottom: 10px;
- width: 195px;
- margin-left: 40px !important;
- padding-right: 10px !important;
-}
-
.ds-expired-div {
padding-left: 30px !important;
}
-.ds-config-diskmon-checkbox {
- margin: 12px !important;
- padding: 5px;
- margin-left: 30px !important;
-}
-
.ds-modal-row {
margin-left: 20px;
margin-right: 0px !important;
@@ -497,13 +355,6 @@ td {
min-height: 350px !important;
}
-.ds-may-must-list {
- width: 232px !important;
- height: 150px !important;
- margin: 0 !important;
- padding: 0 !important;
-}
-
p {
line-height: 1;
white-space: normal;
@@ -676,10 +527,6 @@ option {
background-color: transparent !important;
}
-.ds-accordion-panel {
- margin-top: 10px;
-}
-
.ds-margin-top {
margin-top: 10px !important;
}
@@ -775,24 +622,11 @@ option {
margin-left: 5px !important;
}
-.ds-td {
- padding-left: 10px !important;
- padding-top: 8px !important;
- padding-bottom: 0px !important;
- valign: middle !important;
-}
-
-.ds-mgr-table {
- max-width: 600px;
- min-width: 600px;
-}
-
.ds-loglevel-table {
max-width: 500px;
min-width: 500px;
table-layout: fixed;
margin-top: 20px;
- margin-left: 20px;
}
.ds-table-checkbox {
@@ -800,14 +634,6 @@ option {
text-align: center;
}
-.ds-page-content {
- margin-top: 30px !important;
-}
-
-.ds-spacing-sm {
- margin-right: 10px;
-}
-
.navbar-default {
margin-bottom: 0px;
}
@@ -832,28 +658,10 @@ option {
margin-left: 140px !important;
}
-.ds-nested-modal {
- margin-top: 100px;
- margin-left: 100px;
- min-width: 275px;
-}
-
-.ds-attr-select {
- min-height: 300px;
- max-height: 300px;
- width: 265px;
- overflow: auto;
-}
-
.ds-center {
text-align: center;
}
-.ds-trailing-btn {
- margin-left: 10px;
- margin-top: -2px;
-}
-
.ds-loading-spinner {
position: fixed;
top: 25%;
@@ -889,6 +697,10 @@ option {
margin-top: -3px;
}
+.ds-lower-field {
+ margin-top: 3px;
+}
+
.content-view-pf-pagination > div > span:last-child {
position: relative;
}
@@ -995,7 +807,7 @@ option {
}
.ds-suffix-header {
- font-size: 18px;
+ font-size: 20px;
margin-bottom: 15px;
padding-top: 5px;
}
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
index e359626..3ac1e7a 100644
--- a/src/cockpit/389-console/src/database.jsx
+++ b/src/cockpit/389-console/src/database.jsx
@@ -9,6 +9,8 @@ import {
import { GlobalDatabaseConfig } from "./lib/database/databaseConfig.jsx";
import { Suffix } from "./lib/database/suffix.jsx";
import { Backups } from "./lib/database/backups.jsx";
+import { GlobalPwPolicy } from "./lib/database/globalPwp.jsx";
+import { LocalPwPolicy } from "./lib/database/localPwp.jsx";
import {
Modal,
Icon,
@@ -28,6 +30,8 @@ import "./css/ds.css";
const DB_CONFIG = "dbconfig";
const CHAINING_CONFIG = "chaining-config";
const BACKUP_CONFIG = "backups";
+const PWP_CONFIG = "pwpolicy";
+const LOCAL_PWP_CONFIG = "localpwpolicy";
const treeViewContainerStyles = {
width: '295px',
};
@@ -189,7 +193,6 @@ export class Database extends React.Component {
importcacheauto: attrs['nsslapd-import-cache-autosize'],
importcachesize: attrs['nsslapd-import-cachesize'],
},
- loaded: true,
configUpdated: 1
}), this.setState({configUpdated: 0}));
})
@@ -360,18 +363,38 @@ export class Database extends React.Component {
id: "backups",
},
{
- "text": "Suffixes",
- "icon": "pficon-catalog",
- "state": {"expanded": true},
+ text: "Password Policies",
+ icon: "pficon-key",
selectable: false,
- "nodes": []
+ state: {"expanded": true},
+ "nodes": [
+ {
+ text: "Global Policy",
+ icon: "glyphicon glyphicon-globe",
+ selectable: true,
+ id: "pwpolicy",
+ },
+ {
+ text: "Local Policies",
+ icon: "pficon-home",
+ selectable: true,
+ id: "localpwpolicy",
+ },
+ ]
+ },
+ {
+ text: "Suffixes",
+ icon: "pficon-catalog",
+ state: {"expanded": true},
+ selectable: false,
+ nodes: []
}
];
let current_node = this.state.node_name;
if (fullReset) {
current_node = DB_CONFIG;
}
- basicData[3].nodes = treeData;
+ basicData[4].nodes = treeData;
this.setState(() => ({
nodes: basicData,
@@ -463,6 +486,8 @@ export class Database extends React.Component {
if (selectedNode.id == "dbconfig" ||
selectedNode.id == "chaining-config" ||
+ selectedNode.id == "pwpolicy" ||
+ selectedNode.id == "localpwpolicy" ||
selectedNode.id == "backups") {
// Nothing special to do, these configurations have already been loaded
this.setState(prevState => {
@@ -557,8 +582,8 @@ export class Database extends React.Component {
el.setAttribute('title', el.innerText);
}
this.setState({
- disableTree: false
- });
+ disableTree: false,
+ }, this.loadAttrs());
}
showSuffixModal () {
@@ -1070,17 +1095,18 @@ export class Database extends React.Component {
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
"schema", "attributetypes", "list"
];
- log_cmd("Suffixes", "Get attrs", attr_cmd);
+ log_cmd("loadAttrs", "Get attrs", attr_cmd);
cockpit
.spawn(attr_cmd, { superuser: true, err: "message" })
.done(content => {
let attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent['items']) {
- attrs.push(content.name);
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs,
+ loaded: true
});
})
.fail(err => {
@@ -1101,6 +1127,7 @@ export class Database extends React.Component {
render() {
const { nodes } = this.state;
let db_element = "";
+ let body = "";
let disabled = "tree-view-container";
if (this.state.disableTree) {
disabled = "tree-view-container ds-disabled";
@@ -1127,6 +1154,22 @@ export class Database extends React.Component {
enableTree={this.enableTree}
key={this.state.chainingUpdated}
/>;
+ } else if (this.state.node_name == PWP_CONFIG) {
+ db_element =
+ <GlobalPwPolicy
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ attrs={this.state.attributes}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == LOCAL_PWP_CONFIG) {
+ db_element =
+ <LocalPwPolicy
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ attrs={this.state.attributes}
+ enableTree={this.enableTree}
+ />;
} else if (this.state.node_name == BACKUP_CONFIG) {
db_element =
<Backups
@@ -1192,14 +1235,7 @@ export class Database extends React.Component {
}
}
}
- }
-
- return (
- <div className="container-fluid">
- <NotificationController
- notifications={this.state.notifications}
- removeNotificationAction={this.removeNotification}
- />
+ body =
<div className="ds-container">
<div>
<div className="ds-tree">
@@ -1221,7 +1257,22 @@ export class Database extends React.Component {
<div className="ds-tree-content">
{db_element}
</div>
- </div>
+ </div>;
+ } else {
+ body =
+ <div className="ds-loading-spinner ds-margin-top ds-center">
+ <h4>Loading database configuration ...</h4>
+ <Spinner className="ds-margin-top" loading size="md" />
+ </div>;
+ }
+
+ return (
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ {body}
<CreateSuffixModal
showModal={this.state.showSuffixModal}
closeHandler={this.closeSuffixModal}
diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js
index 0bddcef..5c7e302 100644
--- a/src/cockpit/389-console/src/ds.js
+++ b/src/cockpit/389-console/src/ds.js
@@ -7,7 +7,7 @@ var dn_regex = new RegExp( "^([A-Za-z]+=.*)" );
* We can't load the config until all the html pages are load, so we'll use vars
* to track the loading, and once all the pages are loaded, then we can load the config
*/
-var server_page_loaded = 0;
+var server_page_loaded = 1;
var security_page_loaded = 1;
var db_page_loaded = 1;
var repl_page_loaded = 1;
@@ -175,7 +175,6 @@ function get_date_diff(start, end) {
return("${days} days, ${hours} hours, ${minutes} minutes, and ${seconds} seconds");
}
-
function set_no_insts () {
$("#select-server").empty();
$("#select-server").append('<option value="No instances">No instances</option>');
@@ -221,7 +220,6 @@ function check_inst_alive (connect_err) {
$(".all-pages").hide();
$("#ds-navigation").show();
$("#server-content").show();
- $("#server-config").show();
}
$("#not-running").hide();
$("#no-connect").hide();
@@ -378,9 +376,6 @@ function load_config (refresh){
progress = 10;
update_progress();
- // Load the configuration for all the pages.
- var dropdowns = ['local-pwp-suffix', 'select-repl-cfg-suffix'];
-
// Show the spinner, and reset the pages
$("#loading-msg").html("Loading Directory Server configuration for <i><b>" + server_id + "</b></i>...");
$("#everything").hide();
@@ -395,22 +390,8 @@ function load_config (refresh){
var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','backend', 'suffix', 'list', '--suffix'];
log_cmd('load_config', 'get backend list', cmd);
cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- // Update dropdowns
- for (var idx in dropdowns) {
- $("#" + dropdowns[idx]).find('option').remove();
- }
- var obj = JSON.parse(data);
- for (var idx in obj['items']) {
- for (var list in dropdowns){
- $("#" + dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>');
- }
- }
-
- // Server page
- get_and_set_config();
- get_and_set_sasl();
- get_and_set_localpwp();
update_progress();
+ config_loaded = 1;
// Initialize the tabs
$(".ds-tab-list").css( 'color', '#777');
@@ -422,7 +403,6 @@ function load_config (refresh){
$("#loading-page").hide();
$("#everything").show();
$("#server-content").show();
- $("#server-config").show();
clearInterval(loading_config);
loading_cfg = 0;
@@ -444,6 +424,313 @@ function load_config (refresh){
});
}
+
+
+// Create Instance
+$("#create-inst-save").on("click", function() {
+ $(".ds-modal-error").hide();
+ $(".ds-inst-input").css("border-color", "initial");
+
+ /*
+ * Validate settings and update the INF settings
+ */
+ let setup_inf = create_inf_template;
+
+ // Server ID
+ let new_server_id = $("#create-inst-serverid").val();
+ if (new_server_id == ""){
+ report_err($("#create-inst-serverid"), 'You must provide an Instance name');
+ $("#create-inst-serverid").css("border-color", "red");
+ return;
+ } else {
+ new_server_id = new_server_id.replace(/^slapd-/i, ""); // strip "slapd-"
+ if (new_server_id.length > 128) {
+ report_err($("#create-inst-serverid"), 'Instance name is too long, it must not exceed 128 characters');
+ $("#create-inst-serverid").css("border-color", "red");
+ return;
+ }
+ if (new_server_id.match(/^[#%:A-Za-z0-9_\-]+$/g)) {
+ setup_inf = setup_inf.replace('INST_NAME', new_server_id);
+ } else {
+ report_err($("#create-inst-serverid"), 'Instance name can only contain letters, numbers, and: # % : - _');
+ $("#create-inst-serverid").css("border-color", "red");
+ return;
+ }
+ }
+
+ // Port
+ let server_port = $("#create-inst-port").val();
+ if (server_port == ""){
+ report_err($("#create-inst-port"), 'You must provide a port number');
+ $("#create-inst-port").css("border-color", "red");
+ return;
+ } else if (!valid_port(server_port)) {
+ report_err($("#create-inst-port"), 'Port must be a number between 1 and 65534!');
+ $("#create-inst-port").css("border-color", "red");
+ return;
+ } else {
+ setup_inf = setup_inf.replace('PORT', server_port);
+ }
+
+ // Secure Port
+ let secure_port = $("#create-inst-secureport").val();
+ if (secure_port == ""){
+ report_err($("#create-inst-secureport"), 'You must provide a secure port number');
+ $("#create-inst-secureport").css("border-color", "red");
+ return;
+ } else if (!valid_port(secure_port)) {
+ report_err($("#create-inst-secureport"), 'Secure port must be a number!');
+ $("#create-inst-secureport").css("border-color", "red");
+ return;
+ } else {
+ setup_inf = setup_inf.replace('SECURE_PORT', secure_port);
+ }
+
+ // Root DN
+ let server_rootdn = $("#create-inst-rootdn").val();
+ if (server_rootdn == ""){
+ report_err($("#create-inst-rootdn"), 'You must provide a Directory Manager DN');
+ $("#create-inst-rootdn").css("border-color", "red");
+ return;
+ } else {
+ setup_inf = setup_inf.replace('ROOTDN', server_rootdn);
+ }
+
+ // Setup Self-Signed Certs
+ if ( $("#create-inst-tls").is(":checked") ){
+ setup_inf = setup_inf.replace('SELF_SIGN', 'True');
+ } else {
+ setup_inf = setup_inf.replace('SELF_SIGN', 'False');
+ }
+
+ // Root DN password
+ let root_pw = $("#rootdn-pw").val();
+ let root_pw_confirm = $("#rootdn-pw-confirm").val();
+ if (root_pw != root_pw_confirm) {
+ report_err($("#rootdn-pw"), 'Directory Manager passwords do not match!');
+ $("#rootdn-pw-confirm").css("border-color", "red");
+ return;
+ } else if (root_pw == ""){
+ report_err($("#rootdn-pw"), 'Directory Manager password can not be empty!');
+ $("#rootdn-pw-confirm").css("border-color", "red");
+ return;
+ } else if (root_pw.length < 8) {
+ report_err($("#rootdn-pw"), 'Directory Manager password must have at least 8 characters');
+ $("#rootdn-pw-confirm").css("border-color", "red");
+ return;
+ } else {
+ setup_inf = setup_inf.replace('ROOTPW', root_pw);
+ }
+
+ // Backend/Suffix
+ let backend_name = $("#backend-name").val();
+ let backend_suffix = $("#backend-suffix").val();
+ if ( (backend_name != "" && backend_suffix == "") || (backend_name == "" && backend_suffix != "") ) {
+ if (backend_name == ""){
+ report_err($("#backend-name"), 'If you specify a backend suffix, you must also specify a backend name');
+ $("#backend-name").css("border-color", "red");
+ return;
+ } else {
+ report_err($("#backend-suffix"), 'If you specify a backend name, you must also specify a backend suffix');
+ $("#backend-suffix").css("border-color", "red");
+ return;
+ }
+ }
+ if (backend_name != ""){
+ // We definitely have a backend name and suffix, next validate the suffix is a DN
+ if (valid_dn(backend_suffix)) {
+ // It's valid, add it
+ setup_inf += "\n[backend-" + backend_name + "]\nsuffix = " + backend_suffix + "\n";
+ } else {
+ // Not a valid DN
+ report_err($("#backend-suffix"), 'Invalid DN for Backend Suffix');
+ return;
+ }
+ if ( $("#create-sample-entries").is(":checked") ) {
+ setup_inf += '\nsample_entries = yes\n';
+ } else if ( $("#create-suffix-entry").is(":checked") ) {
+ setup_inf += '\ncreate_suffix_entry = yes\n';
+ }
+ }
+
+ /*
+ * Here are steps we take to create the instance
+ *
+ * [1] Get FQDN Name for nsslapd-localhost setting in setup file
+ * [2] Create a file for the inf setup parameters
+ * [3] Set strict permissions on that file
+ * [4] Populate the new setup file with settings (including cleartext password)
+ * [5] Create the instance
+ * [6] Remove setup file
+ */
+ cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex, data) {
+ // Failed to get FQDN
+ popup_err("Failed to get hostname!", data);
+ }).done(function (data) {
+ /*
+ * We have FQDN, so set the hostname in inf file, and create the setup file
+ */
+ setup_inf = setup_inf.replace('FQDN', data);
+ let setup_file = "/tmp/389-setup-" + (new Date).getTime() + ".inf";
+ let rm_cmd = ['rm', setup_file];
+ let create_file_cmd = ['touch', setup_file];
+ cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
+ // Failed to create setup file
+ popup_err("Failed to create installation file!", data);
+ }).done(function (){
+ /*
+ * We have our new setup file, now set permissions on that setup file before we add sensitive data
+ */
+ let chmod_cmd = ['chmod', '600', setup_file];
+ cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
+ // Failed to set permissions on setup file
+ cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
+ $("#create-inst-spinner").hide();
+ popup_err("Failed to set permission on setup file " + setup_file + ": ", data);
+ }).done(function () {
+ /*
+ * Success we have our setup file and it has the correct permissions.
+ * Now populate the setup file...
+ */
+ let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file];
+ cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
+ // Failed to populate setup file
+ popup_err("Failed to populate installation file!", data);
+ }).done(function (){
+ /*
+ * Next, create the instance...
+ */
+ let cmd = [DSCREATE, 'from-file', setup_file];
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV] }).fail(function(ex, data) {
+ // Failed to create the new instance!
+ cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
+ $("#create-inst-spinner").hide();
+ popup_err("Failed to create instance!", data);
+ }).done(function (){
+ // Success!!! Now cleanup everything up...
+ cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
+ $("#create-inst-spinner").hide();
+ $("#server-list-menu").attr('disabled', false);
+ $("#no-instances").hide();
+ get_insts(); // Refresh server list
+ popup_success("Successfully created instance: <b>slapd-" + new_server_id + "</b>");
+ $("#create-inst-form").modal('toggle');
+ });
+ });
+ $("#create-inst-spinner").show();
+ });
+ });
+ }).fail(function(data) {
+ console.log("failed: " + data.message);
+ });
+});
+
+var create_full_template =
+ "[general]\n" +
+ "config_version = 2\n" +
+ "defaults = 999999999\n" +
+ "full_machine_name = FQDN\n" +
+ "selinux = True\n" +
+ "strict_host_checking = True\n" +
+ "systemd = True\n" +
+ "[slapd]\n" +
+ "backup_dir = /var/lib/dirsrv/slapd-{instance_name}/bak\n" +
+ "bin_dir = /usr/bin\n" +
+ "cert_dir = /etc/dirsrv/slapd-{instance_name}\n" +
+ "config_dir = /etc/dirsrv/slapd-{instance_name}\n" +
+ "data_dir = /usr/share\n" +
+ "db_dir = /var/lib/dirsrv/slapd-{instance_name}/db\n" +
+ "user = dirsrv\n" +
+ "group = dirsrv\n" +
+ "initconfig_dir = /etc/sysconfig\n" +
+ "inst_dir = /usr/lib64/dirsrv/slapd-{instance_name}\n" +
+ "instance_name = localhost\n" +
+ "ldif_dir = /var/lib/dirsrv/slapd-{instance_name}/ldif\n" +
+ "lib_dir = /usr/lib64\n" +
+ "local_state_dir = /var\n" +
+ "lock_dir = /var/lock/dirsrv/slapd-{instance_name}\n" +
+ "log_dir = /var/log/dirsrv/slapd-{instance_name}\n" +
+ "port = PORT\n" +
+ "prefix = /usr\n" +
+ "root_dn = ROOTDN\n" +
+ "root_password = ROOTPW\n" +
+ "run_dir = /var/run/dirsrv\n" +
+ "sbin_dir = /usr/sbin\n" +
+ "schema_dir = /etc/dirsrv/slapd-{instance_name}/schema\n" +
+ "secure_port = SECURE_PORT\n" +
+ "self_sign_cert = True\n" +
+ "sysconf_dir = /etc\n" +
+ "tmp_dir = /tmp\n";
+
+var create_inf_template =
+ "[general]\n" +
+ "config_version = 2\n" +
+ "full_machine_name = FQDN\n\n" +
+ "[slapd]\n" +
+ "user = dirsrv\n" +
+ "group = dirsrv\n" +
+ "instance_name = INST_NAME\n" +
+ "port = PORT\n" +
+ "root_dn = ROOTDN\n" +
+ "root_password = ROOTPW\n" +
+ "secure_port = SECURE_PORT\n" +
+ "self_sign_cert = SELF_SIGN\n";
+
+
+function clear_inst_form() {
+ $(".ds-modal-error").hide();
+ $("#create-inst-serverid").val("");
+ $("#create-inst-port").val("389");
+ $("#create-inst-secureport").val("636");
+ $("#create-inst-rootdn").val("cn=Directory Manager");
+ $("#rootdn-pw").val("");
+ $("#rootdn-pw-confirm").val("");
+ $("#backend-suffix").val("dc=example,dc=com");
+ $("#backend-name").val("userRoot");
+ $("#create-sample-entries").prop('checked', false);
+ $("#create-inst-tls").prop('checked', true);
+ $(".ds-inst-input").css("border-color", "initial");
+}
+
+function do_backup(server_inst, backup_name) {
+ var cmd = [DSCTL, '-j', server_inst, 'status'];
+ $("#backup-spinner").show();
+ cockpit.spawn(cmd, { superuser: true}).
+ done(function(status_data) {
+ var status_json = JSON.parse(status_data);
+ if (status_json.running == true) {
+ var cmd = [DSCONF, "-j", server_inst, 'backup', 'create', backup_name];
+ log_cmd('#ds-backup-btn (click)', 'Backup server instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
+ done(function(data) {
+ $("#backup-spinner").hide();
+ popup_success("Backup has been created");
+ $("#backup-form").modal('toggle');
+ }).
+ fail(function(data) {
+ $("#backup-spinner").hide();
+ popup_err("Failed to backup the server", data.message);
+ })
+ } else {
+ var cmd = [DSCTL, server_inst, 'db2bak', backup_name];
+ log_cmd('#ds-backup-btn (click)', 'Backup server instance (offline)', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
+ done(function(data) {
+ $("#backup-spinner").hide();
+ popup_success("Backup has been created");
+ $("#backup-form").modal('toggle');
+ }).
+ fail(function(data) {
+ $("#backup-spinner").hide();
+ popup_err("Failed to backup the server", data.message);
+ });
+ }
+ }).
+ fail(function() {
+ popup_err("Failed to check the server status", data.message);
+ });
+}
+
$(window.document).ready(function() {
if(navigator.userAgent.toLowerCase().indexOf('firefoxf') > -1) {
$("select@@@").focus( function() {
@@ -452,6 +739,134 @@ $(window.document).ready(function() {
this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );
});
}
+
+ // Set an interval event to wait for all the pages to load, then load the config
+ var init_config = setInterval(function() {
+ /*
+ * Stop, Start, and Restart server
+ */
+
+ let banner = document.getElementById("start-server-btn");
+ if (banner == null) {
+ // Not ready yet, return and try again
+ return;
+ }
+
+ get_insts();
+
+ /* Restore. load restore table with current backups */
+ document.getElementById("restore-server-btn").addEventListener("click", function() {
+ var cmd = [DSCTL, '-j', server_inst, 'backups'];
+ log_cmd('#restore-server-btn (click)', 'Restore server instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ let backup_btn = "<button class=\"btn btn-default restore-btn\" type=\"button\">Restore</button>";
+ let del_btn = "<button title=\"Delete backup directory\" class=\"btn btn-default ds-del-backup-btn\" type=\"button\"><span class='glyphicon glyphicon-trash'></span></button>";
+ let obj = JSON.parse(data);
+ backup_table.clear().draw( false );
+ for (var i = 0; i < obj.items.length; i++) {
+ let backup_name = obj.items[i][0];
+ let backup_date = obj.items[i][1];
+ let backup_size = obj.items[i][2];
+ backup_table.row.add([backup_name, backup_date, backup_size, backup_btn, del_btn]).draw( false );
+ }
+ }).fail(function(data) {
+ popup_err("Failed to get list of backups", data.message);
+ });
+ });
+
+ document.getElementById("backup-server-btn").addEventListener("click", function() {
+ $("#backup-name").val("");
+ });
+
+ document.getElementById("start-server-btn").addEventListener("click", function() {
+ $("#ds-start-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Starting instance <b>" + server_id + "</b>...");
+ $("#start-instance-form").modal('toggle');
+ var cmd = [DSCTL, server_inst, 'start'];
+ log_cmd('#start-server-btn (click)', 'Start server instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ $("#start-instance-form").modal('toggle');
+ load_config(true);
+ popup_success("Started instance \"" + server_id + "\"");
+ }).fail(function(data) {
+ $("#start-instance-form").modal('toggle');
+ popup_err("Failed to start instance \"" + server_id, data.message);
+ });
+ });
+
+ document.getElementById("stop-server-btn").addEventListener("click", function() {
+ $("#ds-stop-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Stopping instance <b>" + server_id + "</b>...");
+ $("#stop-instance-form").modal('toggle');
+ var cmd = [DSCTL, server_inst, 'stop'];
+ log_cmd('#stop-server-btn (click)', 'Stop server instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ $("#stop-instance-form").modal('toggle');
+ popup_success("Stopped instance \"" + server_id + "\"");
+ check_inst_alive();
+ }).fail(function(data) {
+ $("#stop-instance-form").modal('toggle');
+ popup_err("Error", "Failed to stop instance \"" + server_id+ "\"", data.message);
+ check_inst_alive();
+ });
+ });
+
+
+ document.getElementById("restart-server-btn").addEventListener("click", function() {
+ $("#ds-restart-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Restarting instance <b>" + server_id + "</b>...");
+ $("#restart-instance-form").modal('toggle');
+ var cmd = [DSCTL, server_inst, 'restart'];
+ log_cmd('#restart-server-btn (click)', 'Restart server instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ $("#restart-instance-form").modal('toggle');
+ load_config(true);
+ popup_success("Restarted instance \"" + server_id + "\"");
+ }).fail(function(data) {
+ $("#restart-instance-form").modal('toggle');
+ popup_err("Failed to restart instance \"" + server_id + "\"", data.message);
+ });
+ });
+
+ document.getElementById("remove-server-btn").addEventListener("click", function() {
+ popup_confirm("Are you sure you want to this remove instance: <b>" + server_id + "</b>", "Confirmation", function (yes) {
+ if (yes) {
+ var cmd = [DSCTL, server_inst, "remove", "--do-it"];
+ $("#ds-remove-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Removing instance <b>" + server_id + "</b>...");
+ $("#remove-instance-form").modal('toggle');
+ log_cmd('#remove-server-btn (click)', 'Remove instance', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ $("#remove-instance-form").modal('toggle');
+ popup_success("Instance has been deleted");
+ get_insts();
+ }).fail(function(data) {
+ $("#remove-instance-form").modal('toggle');
+ popup_err("Failed to remove instance", data.message);
+ });
+ }
+ });
+ });
+ clearInterval(init_config);
+ }, 250);
+
+ $("#main-banner").load("banner.html");
+ check_for_389();
+
+ $("#server-tab").css( 'color', '#228bc0'); // Set first tab as highlighted
+
+ // Events
+ $(".ds-nav-choice").on('click', function (){
+ // This highlights each nav tab when clicked
+ $(".ds-tab-list").css( 'color', '#777');
+ var tab = $(this).attr("parent-id");
+ $("#" + tab).css( 'color', '#228bc0');
+ });
+
+ $("#server-tasks-btn").on("click", function() {
+ $(".all-pages").hide();
+ $("#server-tasks").show();
+ });
+ $("#server-tab").on("click", function() {
+ $(".all-pages").hide();
+ $("#server-content").show();
+ });
$("#plugin-tab").on("click", function() {
$(".all-pages").hide();
$("#plugin-content").show();
@@ -464,10 +879,6 @@ $(window.document).ready(function() {
$(".all-pages").hide();
$("#monitor-content").show();
});
- $("#security-tab").on("click", function() {
- $(".all-pages").hide();
- $("#security-content").show();
- });
$("#schema-tab").on("click", function() {
$(".all-pages").hide();
$("#schema-content").show();
@@ -476,4 +887,188 @@ $(window.document).ready(function() {
$(".all-pages").hide();
$("#replication-content").show();
});
+
+ // Create instance form
+ $("#create-server-btn").on("click", function() {
+ clear_inst_form();
+ set_ports();
+ });
+ $("#no-inst-create-btn").on("click", function () {
+ clear_inst_form();
+ });
+
+ // backup/restore table
+ var backup_table = $('#backup-table').DataTable( {
+ "paging": true,
+ "bAutoWidth": false,
+ "dom": '<"pull-left"f><"pull-right"l>tip',
+ "lengthMenu": [ 10, 25, 50, 100],
+ "language": {
+ "emptyTable": "No backups available for restore",
+ "search": "Search Backups"
+ },
+ "columnDefs": [ {
+ "targets": [3, 4],
+ "orderable": false
+ } ],
+ "columns": [
+ { "width": "120px" },
+ { "width": "80px" },
+ { "width": "30px" },
+ { "width": "40px" },
+ { "width": "30px" }
+ ],
+ });
+
+ $(".all-pages").hide();
+ $("#server-content").show();
+
+ // To remove text border on firefox on dropdowns)
+ if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
+ $("select").focus( function() {
+ this.style.setProperty( 'outline', 'none', 'important' );
+ this.style.setProperty( 'color', 'rgba(0,0,0,0)', 'important' );
+ this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );
+ });
+ }
+
+ $(".ds-tab-standalone").on('click', function (){
+ $(".ds-tab-list").css( 'color', '#777');
+ $(this).css( 'color', '#228bc0');
+ });
+
+ /* Backup server */
+ $("#ds-backup-btn").on('click', function () {
+ var backup_name = $("#backup-name").val();
+ if (backup_name == ""){
+ popup_msg("Error", "Backup must have a name");
+ return;
+ }
+ if (backup_name.indexOf(' ') >= 0) {
+ popup_msg("Error", "Backup name can not contain any spaces");
+ return;
+ }
+ if (backup_name.indexOf('/') >= 0) {
+ popup_msg("Error", "Backup name can not contain a forward slash. " +
+ "Backups are written to the server's backup directory (nsslapd-bakdir)");
+ return;
+ }
+
+ // First check if backup name is already used
+ var check_cmd = [DSCTL, '-j', server_inst, 'backups'];
+ log_cmd('#ds-backup-btn (click)', 'Restore server instance', check_cmd);
+ cockpit.spawn(check_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ var obj = JSON.parse(data);
+ var found_backup = false;
+ for (var i = 0; i < obj.items.length; i++) {
+ if (obj.items[i][0] == backup_name) {
+ found_backup = true;
+ break;
+ }
+ }
+ if (found_backup) {
+ popup_confirm("A backup already exists with this name, replace it?", "Confirmation", function (yes) {
+ if (yes) {
+ do_backup(server_inst, backup_name);
+ } else {
+ return;
+ }
+ });
+ } else {
+ do_backup(server_inst, backup_name);
+ }
+ });
+ });
+
+ /* Restore server */
+ $(document).on('click', '.restore-btn', function(e) {
+ e.preventDefault();
+ var data = backup_table.row( $(this).parents('tr') ).data();
+ var restore_name = data[0];
+ popup_confirm("Are you sure you want to restore this backup: <b>" + restore_name + "<b>", "Confirmation", function (yes) {
+ if (yes) {
+ var cmd = [DSCTL, '-j', server_inst, 'status'];
+ $("#restore-spinner").show();
+ cockpit.spawn(cmd, { superuser: true}).
+ done(function(status_data) {
+ var status_json = JSON.parse(status_data);
+ if (status_json.running == true) {
+ var cmd = [DSCONF, server_inst, 'backup', 'restore', restore_name];
+ log_cmd('.restore-btn (click)', 'Restore server instance(online)', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
+ done(function(data) {
+ $("#restore-spinner").hide();
+ popup_success("The backup has been restored");
+ $("#restore-form").modal('toggle');
+ }).
+ fail(function(data) {
+ $("#restore-spinner").hide();
+ popup_err("Failed to restore from the backup", data.message);
+ });
+ } else {
+ var cmd = [DSCTL, server_inst, 'bak2db', restore_name];
+ log_cmd('.restore-btn (click)', 'Restore server instance(offline)', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
+ done(function(data) {
+ $("#restore-spinner").hide();
+ popup_success("The backup has been restored");
+ $("#restore-form").modal('toggle');
+ }).
+ fail(function(data) {
+ $("#restore-spinner").hide();
+ popup_err("Failed to restore from the backup", data.message);
+ });
+ }
+ }).
+ fail(function() {
+ popup_err("Failed to check the server status", data.message);
+ });
+ }
+ });
+ });
+
+ /* Delete backup directory */
+ $(document).on('click', '.ds-del-backup-btn', function(e) {
+ e.preventDefault();
+ var data = backup_table.row( $(this).parents('tr') ).data();
+ var restore_name = data[0];
+ var backup_row = $(this);
+ popup_confirm("Are you sure you want to delete this backup: <b>" + restore_name + "</b>", "Confirmation", function (yes) {
+ if (yes) {
+ var cmd = [DSCTL, server_inst, 'backups', '--delete', restore_name];
+ $("#restore-spinner").show();
+ log_cmd('.ds-del-backup-btn (click)', 'Delete backup', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ $("#restore-spinner").hide();
+ backup_table.row( backup_row.parents('tr') ).remove().draw( false );
+ popup_success("The backup has been deleted");
+ }).fail(function(data) {
+ $("#restore-spinner").hide();
+ popup_err("Failed to delete the backup", data.message);
+ });
+ }
+ });
+ });
+
+ /* reload schema */
+ $("#schema-reload-btn").on("click", function () {
+ var schema_dir = $("#reload-dir").val();
+ if (schema_dir != ""){
+ var cmd = [DSCONF, server_inst, 'schema', 'reload', '--schemadir', schema_dir, '--wait'];
+ } else {
+ var cmd = [DSCONF, server_inst, 'schema', 'reload', '--wait'];
+ }
+ $("#reload-spinner").show();
+ log_cmd('#schema-reload-btn (click)', 'Reload schema files', cmd);
+ cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
+ popup_success("Successfully reloaded schema"); // TODO use timed interval success msg (waiting for another PR top be merged before we can add it)
+ $("#schema-reload-form").modal('toggle');
+ $("#reload-spinner").hide();
+ }).fail(function(data) {
+ popup_err("Failed to reload schema files", data.message);
+ $("#reload-spinner").hide();
+ });
+ });
+
+
});
diff --git a/src/cockpit/389-console/src/index.es6 b/src/cockpit/389-console/src/index.es6
index 6f2673f..9344ffa 100644
--- a/src/cockpit/389-console/src/index.es6
+++ b/src/cockpit/389-console/src/index.es6
@@ -4,8 +4,8 @@ import { Plugins } from "./plugins.jsx";
import { Database } from "./database.jsx";
import { Monitor } from "./monitor.jsx";
import { Schema } from "./schema.jsx";
-import { Security } from "./security.jsx";
import { Replication } from "./replication.jsx";
+import { Server } from "./server.jsx";
var serverIdElem;
@@ -21,6 +21,11 @@ function renderReactDOM(clear) {
let d = new Date();
let tabKey = d.getTime();
+ // Server Tab
+ ReactDOM.render(
+ <Server serverId={serverIdElem} key={tabKey} />,
+ document.getElementById("server")
+ );
// Plugins Tab
ReactDOM.render(
<Plugins serverId={serverIdElem} key={tabKey} />,
@@ -45,12 +50,6 @@ function renderReactDOM(clear) {
document.getElementById("monitor")
);
- // Security tab
- ReactDOM.render(
- <Security serverId={serverIdElem} key={tabKey} />,
- document.getElementById("security")
- );
-
// Schema tab
ReactDOM.render(
<Schema serverId={serverIdElem} key={tabKey} />,
diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html
index c1b9952..1df2d19 100644
--- a/src/cockpit/389-console/src/index.html
+++ b/src/cockpit/389-console/src/index.html
@@ -18,7 +18,6 @@
<script src="static/c3.min.js"></script>
<script src="static/d3.min.js"></script>
<script src="ds.js"></script>
- <script src="servers.js"></script>
<link href="static/bootstrap.min.css" rel="stylesheet">
<link href="static/jquery.dataTables.min.css" type="text/css" rel="stylesheet">
<link href="static/jquery.timepicker.min.css" type="text/css" rel="stylesheet">
@@ -56,39 +55,9 @@
<ul class="nav navbar-nav navbar-primary">
<!-- Server Config navtab -->
- <li class="dropdown ds-nav-tab">
- <a href="#0" class="ds-tab-list" data-toggle="dropdown" id="server-tab">
- Server Settings
- <b class="caret"></b>
- </a>
- <ul class="dropdown-menu dropup ds-nav-item">
- <li><a href="#0" class="ds-nav-choice" id="server-config-btn" parent-id="server-tab">Server Configuration</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-tuning-btn" parent-id="server-tab">Tuning & Limits</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-sasl-btn" parent-id="server-tab">SASL</a></li>
- <li class="dropdown-submenu">
- <a tabindex="-1" href="#0">Password Policy</a>
- <ul class="dropdown-menu">
- <li><a href="#0" class="ds-nav-choice" id="server-gbl-pwp-btn" parent-id="server-tab">Global Password Policy</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-local-pwp-btn" parent-id="server-tab">Local Password Policy</a></li>
- </ul>
- </li>
- <li><a href="#0" class="ds-nav-choice" id="server-ldapi-btn" parent-id="server-tab">LDAPI & Autobind</a></li>
- <li class="dropdown-submenu">
- <a tabindex="-1" href="#0">Logging</a>
- <ul class="dropdown-menu">
- <li><a href="#0" class="ds-nav-choice" id="server-log-access-btn" parent-id="server-tab">Access Log</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-log-audit-btn" parent-id="server-tab">Audit Log</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-log-auditfail-btn" parent-id="server-tab">Audit Failure Log</a></li>
- <li><a href="#0" class="ds-nav-choice" id="server-log-errors-btn" parent-id="server-tab">Errors Log</a></li>
- </ul>
- </li>
- </ul>
- </li>
-
- <!-- Security navtab -->
<li class="ds-nav-tab">
- <a href="#0" class="ds-tab-list ds-tab-standalone" id="security-tab">
- Security
+ <a href="#0" class="ds-tab-list ds-tab-standalone" id="server-tab">
+ Server Settings
</a>
</li>
@@ -485,17 +454,8 @@
<h3>This server instance is running, but we can not connect to it. Check LDAPI is properly configured on this instance.</h3>
</div>
- <div id="server-content" class="all-pages" hidden>
- <div id="server-settings"></div>
- <div id="server-tuning"></div>
- <div id="server-sasl"></div>
- <div id="server-pwp"></div>
- <div id="server-ldapi"></div>
- <div id="server-logs"></div>
- </div>
-
- <div id="security-content" class="all-pages" hidden>
- <div id="security"></div>
+ <div id="server-content" class="all-pages">
+ <div id="server"></div>
</div>
<div id="database-content" class="all-pages" hidden>
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
index 5adac79..ecc9ffc 100644
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -3,10 +3,11 @@ import React from "react";
import CustomCollapse from "../customCollapse.jsx";
import { log_cmd } from "../tools.jsx";
import {
- Row,
+ Checkbox,
Col,
ControlLabel,
Form,
+ Row,
Spinner,
noop
} from "patternfly-react";
@@ -355,28 +356,49 @@ export class GlobalDatabaseConfig extends React.Component {
</Form>
<div className="ds-container">
- <div>
- <h4 className="ds-sub-header">Database Cache Settings</h4>
- <hr />
- <div>
- <label htmlFor="autoCacheChkbox" title="Set Database/Entry to be set automatically"><input
- className="ds-left-indent" type="checkbox" id="autoCacheChkbox" checked={db_auto_checked}
- onChange={this.select_auto_cache} /> Automatic Cache Tuning</label>
- {db_cache_form}
- </div>
- </div>
- <div className="ds-divider-lrg" />
- <div>
- <h4 className="ds-sub-header">Import Cache Settings</h4>
- <hr />
- <div>
- <label htmlFor="autoImportCacheChkbox"title="Set Database/Entry to be set automatically"><input
- className="ds-left-indent" type="checkbox" id="autoImportCacheChkbox"
- onChange={this.select_auto_import_cache}
- checked={import_auto_checked} /> Automatic Import Cache Tuning</label>
- {import_cache_form}
- </div>
- </div>
+ <Form className="container-fluid" horizontal>
+ <Row>
+ <Col sm={5}>
+ <h4 className="ds-sub-header">Database Cache Settings</h4>
+ <hr />
+ </Col>
+ <Col sm={1} />
+ <Col sm={5}>
+ <h4 className="ds-sub-header">Import Cache Settings</h4>
+ <hr />
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={5}>
+ <Checkbox title="Set Database/Entry to be set automatically"
+ id="autoCacheChkbox"
+ checked={db_auto_checked}
+ onChange={this.select_auto_cache}
+ >
+ Automatic Cache Tuning
+ </Checkbox>
+ </Col>
+ <Col sm={1} />
+ <Col sm={5}>
+ <Checkbox title="Set input to be set automatically"
+ id="autoImportCacheChkbox"
+ checked={import_auto_checked}
+ onChange={ this.select_auto_import_cache}
+ >
+ Automatic Import Cache Tuning
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={6}>
+ {db_cache_form}
+ </Col>
+
+ <Col sm={6}>
+ {import_cache_form}
+ </Col>
+ </Row>
+ </Form>
</div>
<CustomCollapse>
<div className="ds-margin-top">
diff --git a/src/cockpit/389-console/src/lib/database/databaseTables.jsx b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
index b8715e8..beca690 100644
--- a/src/cockpit/389-console/src/lib/database/databaseTables.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
@@ -946,8 +946,198 @@ class BackupTable extends React.Component {
}
}
+export class PwpTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "targetdn",
+ columns: [
+ {
+ property: "targetdn",
+ header: {
+ label: "Target DN",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "pwp_type",
+ header: {
+ label: "Policy Type",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "basedn",
+ header: {
+ label: "Suffix",
+ 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.targetdn}>
+ <DropdownButton id={rowData.targetdn}
+ className="ds-action-button"
+ bsStyle="primary" title="Actions">
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.editPolicy(
+ rowData.targetdn,
+ );
+ }}
+ >
+ Edit Policy
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="2" onClick={() => {
+ this.props.deletePolicy(rowData.targetdn);
+ }}
+ >
+ Delete Policy
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ],
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ } // Constructor
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Local Password Policies",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let PwpTable;
+ if (this.props.rows.length == 0) {
+ PwpTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Policies"}]}
+ />;
+ } else {
+ PwpTable =
+ <DSTable
+ noSearchBar
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+ return (
+ <div>
+ {PwpTable}
+ </div>
+ );
+ }
+}
+
// Property types and defaults
+PwpTable.propTypes = {
+ rows: PropTypes.array,
+ editPolicy: PropTypes.func,
+ deletePolicy: PropTypes.func
+};
+
+PwpTable.defaultProps = {
+ rows: [],
+ editPolicy: noop,
+ deletePolicy: noop
+};
+
BackupTable.propTypes = {
rows: PropTypes.array,
confirmRestore: PropTypes.func,
diff --git a/src/cockpit/389-console/src/lib/database/globalPwp.jsx b/src/cockpit/389-console/src/lib/database/globalPwp.jsx
new file mode 100644
index 0000000..d3cac0d
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/globalPwp.jsx
@@ -0,0 +1,1345 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ TabContainer,
+ TabContent,
+ TabPane,
+ Spinner,
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+const general_attrs = [
+ "nsslapd-pwpolicy-local",
+ "passwordstoragescheme",
+ "passwordadmindn",
+ "passwordtrackupdatetime",
+ "nsslapd-allow-hashed-passwords",
+ "nsslapd-pwpolicy-inherit-global",
+ "passwordisglobalpolicy",
+ "passwordchange",
+ "passwordmustchange",
+ "passwordhistory",
+ "passwordinhistory",
+ "passwordminage",
+];
+
+const exp_attrs = [
+ "passwordexp",
+ "passwordgracelimit",
+ "passwordsendexpiringtime",
+ "passwordmaxage",
+ "passwordwarning",
+];
+
+const lockout_attrs = [
+ "passwordlockout",
+ "passwordunlock",
+ "passwordlockoutduration",
+ "passwordmaxfailure",
+ "passwordresetfailurecount",
+];
+
+const syntax_attrs = [
+ "passwordchecksyntax",
+ "passwordminlength",
+ "passwordmindigits",
+ "passwordminalphas",
+ "passwordminuppers",
+ "passwordminlowers",
+ "passwordminspecials",
+ "passwordmin8bit",
+ "passwordmaxrepeats",
+ "passwordpalindrome",
+ "passwordmaxsequence",
+ "passwordmaxseqsets",
+ "passwordmaxclasschars",
+ "passwordmincategories",
+ "passwordmintokenlength",
+ "passwordbadwords",
+ "passworduserattributes",
+ "passworddictcheck",
+];
+
+export class GlobalPwPolicy extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: true,
+ loaded: false,
+ activeKey: 1,
+ // Lists of all the attributes for each tab/section.
+ // We use the exact attribute name for the ID of
+ // each field, so we can loop over them to efficently
+ // check for changes, and updating/saving the config.
+
+ saveGeneralDisabled: true,
+ saveExpDisabled: true,
+ saveLockoutDisabled: true,
+ saveSyntaxDisabled: true,
+ };
+
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleGeneralChange = this.handleGeneralChange.bind(this);
+ this.saveGeneral = this.saveGeneral.bind(this);
+ this.handleExpChange = this.handleExpChange.bind(this);
+ this.saveExp = this.saveExp.bind(this);
+ this.handleLockoutChange = this.handleLockoutChange.bind(this);
+ this.saveLockout = this.saveLockout.bind(this);
+ this.handleSyntaxChange = this.handleSyntaxChange.bind(this);
+ this.saveSyntax = this.saveSyntax.bind(this);
+ this.loadGlobal = this.loadGlobal.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config TODO
+ if (!this.state.loaded) {
+ this.loadGlobal();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleGeneralChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let general_attr of general_attrs) {
+ if (attr == general_attr && this.state['_' + general_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let general_attr of general_attrs) {
+ if (attr != general_attr && this.state['_' + general_attr] != this.state[general_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveGeneralDisabled: disableSaveBtn,
+ });
+ }
+
+ saveGeneral() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of general_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveGeneral", "Saving general pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleUserChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let user_attr of this.state.user_attrs) {
+ if (attr == user_attr && this.state['_' + user_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let user_attr of this.state.user_attrs) {
+ if (attr != user_attr && this.state['_' + user_attr] != this.state[user_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveUserDisabled: disableSaveBtn,
+ });
+ }
+
+ handleExpChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let exp_attr of exp_attrs) {
+ if (attr == exp_attr && this.state['_' + exp_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let exp_attr of exp_attrs) {
+ if (attr != exp_attr && this.state['_' + exp_attr] != this.state[exp_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveExpDisabled: disableSaveBtn,
+ });
+ }
+
+ saveExp() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of exp_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveExp", "Saving Expiration pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleLockoutChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let lockout_attr of lockout_attrs) {
+ if (attr == lockout_attr && this.state['_' + lockout_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let lockout_attr of lockout_attrs) {
+ if (attr != lockout_attr && this.state['_' + lockout_attr] != this.state[lockout_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveLockoutDisabled: disableSaveBtn,
+ });
+ }
+
+ saveLockout() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of lockout_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveLockout", "Saving lockout pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleSyntaxChange(e) {
+ // Could be a typeahead change, check if "e" is an Array
+ let attr;
+ let value;
+ if (Array.isArray(e)) {
+ // Typeahead - convert array to string
+ attr = "passworduserattributes";
+ value = e.join(' ');
+ } else {
+ value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ attr = e.target.id;
+ }
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let syntax_attr of syntax_attrs) {
+ if (syntax_attr == 'passworduserattributes' && attr == 'passworduserattributes') {
+ let orig_val = this.state['_' + syntax_attr].join(' ');
+ if (orig_val != value) {
+ value = e; // restore value
+ disableSaveBtn = false;
+ break;
+ }
+ value = e; // restore value
+ } else if (attr == syntax_attr && this.state['_' + syntax_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let syntax_attr of syntax_attrs) {
+ if (syntax_attr == 'passworduserattributes' && attr != 'passworduserattributes') {
+ // Typeahead attribute needs special care
+ let orig_val = this.state['_' + syntax_attr].join(' ');
+ let new_val = this.state[syntax_attr].join(' ');
+ if (orig_val != new_val) {
+ disableSaveBtn = false;
+ break;
+ }
+ } else if (attr != syntax_attr && this.state['_' + syntax_attr] != this.state[syntax_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveSyntaxDisabled: disableSaveBtn,
+ });
+ }
+
+ saveSyntax() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of syntax_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveSyntax", "Saving syntax checking pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadGlobal();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadGlobal() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("loadGlobal", "Load global password policy", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let pwpLocal = false;
+ let pwIsGlobal = false;
+ let pwChange = false;
+ let pwMustChange = false;
+ let pwHistory = false;
+ let pwTrackUpdate = false;
+ let pwExpire = false;
+ let pwSendExpire = false;
+ let pwLockout = false;
+ let pwUnlock = false;
+ let pwCheckSyntax = false;
+ let pwPalindrome = false;
+ let pwDictCheck = false;
+ let pwAllowHashed = false;
+ let pwInheritGlobal = false;
+ let pwUserAttrs = [];
+
+ if (attrs['nsslapd-pwpolicy-local'][0] == "on") {
+ pwpLocal = true;
+ }
+ if (attrs['passwordchange'][0] == "on") {
+ pwChange = true;
+ }
+ if (attrs['passwordmustchange'][0] == "on") {
+ pwMustChange = true;
+ }
+ if (attrs['passwordhistory'][0] == "on") {
+ pwHistory = true;
+ }
+ if (attrs['passwordtrackupdatetime'][0] == "on") {
+ pwTrackUpdate = true;
+ }
+ if (attrs['passwordisglobalpolicy'][0] == "on") {
+ pwIsGlobal = true;
+ }
+ if (attrs['passwordsendexpiringtime'][0] == "on") {
+ pwSendExpire = true;
+ }
+ if (attrs['passwordlockout'][0] == "on") {
+ pwLockout = true;
+ }
+ if (attrs['passwordunlock'][0] == "on") {
+ pwUnlock = true;
+ }
+ if (attrs['passwordexp'][0] == "on") {
+ pwExpire = true;
+ }
+ if (attrs['passwordchecksyntax'][0] == "on") {
+ pwCheckSyntax = true;
+ }
+ if (attrs['passwordpalindrome'][0] == "on") {
+ pwPalindrome = true;
+ }
+ if (attrs['passworddictcheck'][0] == "on") {
+ pwExpire = true;
+ }
+ if (attrs['nsslapd-allow-hashed-passwords'][0] == "on") {
+ pwAllowHashed = true;
+ }
+ if (attrs['nsslapd-pwpolicy-inherit-global'][0] == "on") {
+ pwInheritGlobal = true;
+ }
+ if (attrs['passwordbadwords'][0] != "") {
+ // Hack until this is fixed: https://pagure.io/389-ds-base/issue/50875
+ if (attrs['passwordbadwords'].length > 1) {
+ attrs['passwordbadwords'][0] = attrs['passwordbadwords'].join(' ');
+ }
+ }
+ if (attrs['passworduserattributes'][0] != "") {
+ if (attrs['passworduserattributes'].length > 1) {
+ // Hack until this is fixed: https://pagure.io/389-ds-base/issue/50875
+ attrs['passworduserattributes'][0] = attrs['passworduserattributes'].join(' ');
+ }
+ // Could be space or comma separated list
+ if (attrs['passworduserattributes'][0].indexOf(',') > -1) {
+ pwUserAttrs = attrs['passworduserattributes'][0].trim();
+ pwUserAttrs = pwUserAttrs.split(',');
+ } else {
+ pwUserAttrs = attrs['passworduserattributes'][0].split();
+ }
+ }
+
+ this.setState(() => (
+ {
+ loaded: true,
+ loading: false,
+ saveGeneralDisabled: true,
+ saveUserDisabled: true,
+ saveExpDisabled: true,
+ saveLockoutDisabled: true,
+ saveSyntaxDisabled: true,
+ // Settings
+ 'nsslapd-pwpolicy-local': pwpLocal,
+ passwordisglobalpolicy: pwIsGlobal,
+ passwordchange: pwChange,
+ passwordmustchange: pwMustChange,
+ passwordhistory: pwHistory,
+ passwordtrackupdatetime: pwTrackUpdate,
+ passwordexp: pwExpire,
+ passwordsendexpiringtime: pwSendExpire,
+ passwordlockout: pwLockout,
+ passwordunlock: pwUnlock,
+ passwordchecksyntax: pwCheckSyntax,
+ passwordpalindrome: pwPalindrome,
+ passworddictcheck: pwDictCheck,
+ 'nsslapd-allow-hashed-passwords': pwAllowHashed,
+ 'nsslapd-pwpolicy-inherit-global': pwInheritGlobal,
+ passwordstoragescheme: attrs['passwordstoragescheme'][0],
+ passwordinhistory: attrs['passwordinhistory'][0],
+ passwordwarning: attrs['passwordwarning'][0],
+ passwordmaxage: attrs['passwordmaxage'][0],
+ passwordminage: attrs['passwordminage'][0],
+ passwordgracelimit: attrs['passwordgracelimit'][0],
+ passwordlockoutduration: attrs['passwordlockoutduration'][0],
+ passwordmaxfailure: attrs['passwordmaxfailure'][0],
+ passwordresetfailurecount: attrs['passwordresetfailurecount'][0],
+ passwordminlength: attrs['passwordminlength'][0],
+ passwordmindigits: attrs['passwordmindigits'][0],
+ passwordminalphas: attrs['passwordminalphas'][0],
+ passwordminuppers: attrs['passwordminuppers'][0],
+ passwordminlowers: attrs['passwordminlowers'][0],
+ passwordminspecials: attrs['passwordminspecials'][0],
+ passwordmin8bit: attrs['passwordmin8bit'][0],
+ passwordmaxrepeats: attrs['passwordmaxrepeats'][0],
+ passwordmaxsequence: attrs['passwordmaxsequence'][0],
+ passwordmaxseqsets: attrs['passwordmaxseqsets'][0],
+ passwordmaxclasschars: attrs['passwordmaxclasschars'][0],
+ passwordmincategories: attrs['passwordmincategories'][0],
+ passwordmintokenlength: attrs['passwordmintokenlength'][0],
+ passwordbadwords: attrs['passwordbadwords'][0],
+ passworduserattributes: pwUserAttrs,
+ passwordadmindn: attrs['passwordadmindn'][0],
+ // Record original values
+ '_nsslapd-pwpolicy-local': pwpLocal,
+ _passwordisglobalpolicy: pwIsGlobal,
+ _passwordchange: pwChange,
+ _passwordmustchange: pwMustChange,
+ _passwordhistory: pwHistory,
+ _passwordtrackupdatetime: pwTrackUpdate,
+ _passwordexp: pwExpire,
+ _passwordsendexpiringtime: pwSendExpire,
+ _passwordlockout: pwLockout,
+ _passwordunlock: pwUnlock,
+ _passwordchecksyntax: pwCheckSyntax,
+ _passwordpalindrome: pwPalindrome,
+ _passworddictcheck: pwDictCheck,
+ '_nsslapd-allow-hashed-passwords': pwAllowHashed,
+ '_nsslapd-pwpolicy-inherit-global': pwInheritGlobal,
+ _passwordstoragescheme: attrs['passwordstoragescheme'][0],
+ _passwordinhistory: attrs['passwordinhistory'][0],
+ _passwordwarning: attrs['passwordwarning'][0],
+ _passwordmaxage: attrs['passwordmaxage'][0],
+ _passwordminage: attrs['passwordminage'][0],
+ _passwordgracelimit: attrs['passwordgracelimit'][0],
+ _passwordlockoutduration: attrs['passwordlockoutduration'][0],
+ _passwordmaxfailure: attrs['passwordmaxfailure'][0],
+ _passwordresetfailurecount: attrs['passwordresetfailurecount'][0],
+ _passwordminlength: attrs['passwordminlength'][0],
+ _passwordmindigits: attrs['passwordmindigits'][0],
+ _passwordminalphas: attrs['passwordminalphas'][0],
+ _passwordminuppers: attrs['passwordminuppers'][0],
+ _passwordminlowers: attrs['passwordminlowers'][0],
+ _passwordminspecials: attrs['passwordminspecials'][0],
+ _passwordmin8bit: attrs['passwordmin8bit'][0],
+ _passwordmaxrepeats: attrs['passwordmaxrepeats'][0],
+ _passwordmaxsequence: attrs['passwordmaxsequence'][0],
+ _passwordmaxseqsets: attrs['passwordmaxseqsets'][0],
+ _passwordmaxclasschars: attrs['passwordmaxclasschars'][0],
+ _passwordmincategories: attrs['passwordmincategories'][0],
+ _passwordmintokenlength: attrs['passwordmintokenlength'][0],
+ _passwordbadwords: attrs['passwordbadwords'][0],
+ _passworduserattributes: pwUserAttrs,
+ _passwordadmindn: attrs['passwordadmindn'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ loading: false,
+ });
+ this.props.addNotification(
+ "error",
+ `Error loading global password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ render() {
+ let pwp_element = "";
+ let pwExpirationRows = "";
+ let pwLockoutRows = "";
+ let pwSyntaxRows = "";
+
+ if (this.state.passwordchecksyntax) {
+ pwSyntaxRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The minimum number of characters in the password (passwordMinLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordminlength"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminlength}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3} title="Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">
+ Min Alpha's
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordminalphas"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminalphas}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Digits
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmindigits"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmindigits}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Special
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">
+ <FormControl
+ id="passwordminspecials"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminspecials}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Uppercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">
+ <FormControl
+ id="passwordminuppers"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminuppers}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Lowercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">
+ <FormControl
+ id="passwordminlowers"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminlowers}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min 8-bit
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmin8bit"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmin8bit}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Categories
+ </Col>
+ <Col sm={2} title="The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">
+ <FormControl
+ id="passwordmincategories"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmincategories}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Token Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmintokenlength"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmintokenlength}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Repeated Chars
+ </Col>
+ <Col sm={2} title="The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">
+ <FormControl
+ id="passwordmaxrepeats"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxrepeats}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequences
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxsequence"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxsequence}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequence Sets
+ </Col>
+ <Col sm={2} title="The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">
+ <FormControl
+ id="passwordmaxseqsets"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxseqsets}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Seq Per Class
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxclasschars"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxclasschars}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Prohibited Words
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="passwordbadwords"
+ type="text"
+ value={this.state.passwordbadwords}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of entry attributes to compare to the new password (passwordUserAttributes)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Check User Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ onChange={values => {
+ this.handleSyntaxChange(values);
+ }}
+ multiple
+ selected={this.state.passworduserattributes}
+ options={this.props.attrs}
+ placeholder="Type attributes to check..."
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Check the password against the system's CrackLib dictionary (passwordDictCheck).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="passworddictcheck"
+ defaultChecked={this.state.passworddictcheck}
+ onChange={this.handleSyntaxChange}
+ >
+ Dictionary Check
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject a password if it is a palindrome (passwordPalindrome).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="passwordpalindrome"
+ defaultChecked={this.state.passwordpalindrome}
+ onChange={this.handleSyntaxChange}
+ >
+ Reject Palindromes
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (this.state.passwordlockout) {
+ pwLockoutRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The maximum number of failed logins before account gets locked (passwordMaxFailure).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Number of Failed Logins That Locks out Account
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxfailure"
+ type="number"
+ min="1"
+ max="100"
+ value={this.state.passwordmaxfailure}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Time Until <i>Failure Count</i> Resets
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordresetfailurecount"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordresetfailurecount}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds, duration, before the account gets unlocked (passwordLockoutDuration).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Time Until Account Unlocked
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordlockoutduration"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordlockoutduration}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Do not lockout the user account forever, instead the account will unlock based on the lockout duration (passwordUnlock)">
+ <Col componentClass={ControlLabel} sm={5}>
+ <Checkbox
+ id="passwordunlock"
+ defaultChecked={this.state.passwordunlock}
+ onChange={this.handleLockoutChange}
+ >
+ Do Not Lockout Account Forever
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (this.state.passwordexp) {
+ pwExpirationRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The maxiumum age of a password in seconds before it expires (passwordMaxAge).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Password Expiration Time
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxage"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordmaxage}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of logins that are allowed after the password has expired (passwordGraceLimit).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Allowed Logins After Password Expires
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordgracelimit"
+ type="number"
+ min="0"
+ max="128"
+ value={this.state.passwordgracelimit}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Send Password Expiring Warning
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordwarning"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordwarning}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Always return a password expiring control when requested (passwordSendExpiringTime).">
+ <Col componentClass={ControlLabel} sm={5}>
+ <Checkbox
+ id="passwordsendexpiringtime"
+ defaultChecked={this.state.passwordsendexpiringtime}
+ onChange={this.handleExpChange}
+ >
+ Always Send <i>Password Expiring</i> Control
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (this.state.loading || !this.state.loaded) {
+ pwp_element = <Spinner loading size="md" />;
+ } else {
+ pwp_element =
+ <div>
+ <div className={this.state.loading ? 'ds-fadeout' : 'ds-fadein ds-margin-left'}>
+ <TabContainer id="server-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'General Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Expiration'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Account Lockout'}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: 'Syntax Checking'}} />
+ </NavItem>
+ </Nav>
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row title="Set the password storage scheme (passwordstoragescheme)." className="ds-margin-top">
+ <Col sm={8}>
+ <ControlLabel>
+ Password Storage Scheme
+ </ControlLabel>
+ <select
+ className="btn btn-default dropdown ds-margin-left-sm" id="passwordstoragescheme"
+ onChange={this.handleGeneralChange} value={this.state.passwordstoragescheme}>
+ <option>PBKDF2_SHA256</option>
+ <option>SSHA512</option>
+ <option>SSHA384</option>
+ <option>SSHA256</option>
+ <option>SSHA</option>
+ <option>MD5</option>
+ <option>SMD5</option>
+ <option>CRYPT-MD5</option>
+ <option>CRYPT-SHA512</option>
+ <option>CRYPT-SHA256</option>
+ <option>CRYPT</option>
+ <option>CLEAR</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Allow subtree/user defined local password policies (nsslapd-pwpolicy-local).">
+ <Col sm={11}>
+ <Checkbox
+ id="nsslapd-pwpolicy-local"
+ defaultChecked={this.state['nsslapd-pwpolicy-local']}
+ onChange={this.handleGeneralChange}
+ >
+ Allow Local Password Policies
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="If a local password policy does not defined any syntax rules then inherit the local policy syntax (nsslapd-pwpolicy-inherit-global).">
+ <Col sm={11}>
+ <Checkbox
+ id="nsslapd-pwpolicy-inherit-global"
+ defaultChecked={this.state["nsslapd-pwpolicy-inherit-global"]}
+ onChange={this.handleGeneralChange}
+ >
+ Local Policies Inherit Global Policy
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow anyone to add a prehashed password (nsslapd-allow-hashed-passwords).">
+ <Col sm={11}>
+ <Checkbox
+ id="nsslapd-allow-hashed-passwords"
+ defaultChecked={this.state["nsslapd-allow-hashed-passwords"]}
+ onChange={this.handleGeneralChange}
+ >
+ Allow Adding Pre-Hashed Passwords
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow password policy state attributes to replicate (passwordIsGlobalPolicy).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordisglobalpolicy"
+ defaultChecked={this.state.passwordisglobalpolicy}
+ onChange={this.handleGeneralChange}
+ >
+ Replicate Password Policy State Attributes
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordtrackupdatetime"
+ defaultChecked={this.state.passwordtrackupdatetime}
+ onChange={this.handleGeneralChange}
+ >
+ Track Password Update Time
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow user's to change their passwords (passwordChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordchange"
+ defaultChecked={this.state.passwordchange}
+ onChange={this.handleGeneralChange}
+ >
+ Allow Users To Change Their Passwords
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="User must change its password after its been reset by an administrator (passwordMustChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordmustchange"
+ defaultChecked={this.state.passwordmustchange}
+ onChange={this.handleGeneralChange}
+ >
+ User Must Change Password After Reset
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Maintain a password history for each user (passwordHistory).">
+ <Col sm={11}>
+ <div className="ds-inline">
+ <Checkbox
+ id="passwordhistory"
+ defaultChecked={this.state.passwordhistory}
+ onChange={this.handleGeneralChange}
+ >
+ Keep Password History
+ </Checkbox>
+ </div>
+ <div className="ds-inline ds-left-margin ds-raise-field ds-width-sm">
+ <FormControl
+ id="passwordinhistory"
+ type="number"
+ min="0"
+ max="24"
+ value={this.state.passwordinhistory}
+ onChange={this.handleGeneralChange}
+ disabled={!this.state.passwordhistory}
+ />
+ </div>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Indicates the number of seconds that must pass before a user can change their password again. (passwordMinAge)">
+ <Col sm={3}>
+ <ControlLabel>
+ Password Minimum Age
+ </ControlLabel>
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordminage"
+ type="number"
+ min="0"
+ max="2147483647"
+ value={this.state.passwordminage}
+ onChange={this.handleGeneralChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="The DN for a password administrator or administrator group (passwordAdminDN).">
+ <Col sm={3}>
+ <ControlLabel>
+ Password Administrator
+ </ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ id="passwordadmindn"
+ type="text"
+ value={this.state.passwordadmindn}
+ onChange={this.handleGeneralChange}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveGeneralDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg"
+ onClick={this.saveGeneral}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={2}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable a password expiration policy (passwordExp).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordexp"
+ defaultChecked={this.state.passwordexp}
+ onChange={this.handleExpChange}
+ >
+ Enforce Password Expiration
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwExpirationRows}
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveExp}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={3}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable account lockout (passwordLockout).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordlockout"
+ defaultChecked={this.state.passwordlockout}
+ onChange={this.handleLockoutChange}
+ >
+ Enable Account Lockout
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwLockoutRows}
+ <Button
+ disabled={this.state.saveLockoutDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveLockout}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={4}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable password syntax checking (passwordCheckSyntax).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordchecksyntax"
+ defaultChecked={this.state.passwordchecksyntax}
+ onChange={this.handleSyntaxChange}
+ >
+ Enable Password Syntax Checking
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwSyntaxRows}
+ <Button
+ disabled={this.state.saveSyntaxDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveSyntax}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>
+ </div>;
+ }
+
+ return (
+ <div>
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg ds-margin-left-sm">
+ Global Password Policy
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh global password policy settings"
+ onClick={this.reloadConfig}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {pwp_element}
+ </div>
+ );
+ }
+}
+
+GlobalPwPolicy.propTypes = {
+ attrs: PropTypes.array,
+};
+
+GlobalPwPolicy.defaultProps = {
+ attrs: [],
+};
diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx b/src/cockpit/389-console/src/lib/database/indexes.jsx
index d33db25..80d457b 100644
--- a/src/cockpit/389-console/src/lib/database/indexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/indexes.jsx
@@ -600,17 +600,11 @@ class AddIndexModal extends React.Component {
let availMR = [];
for (let mr of matchingRules) {
- availMR.push({
- id: mr,
- label: mr
- });
+ availMR.push(mr[0]);
}
let availAttrs = [];
for (let attr of attributes) {
- availAttrs.push({
- id: attr,
- label: attr
- });
+ availAttrs.push(attr[0]);
}
return (
diff --git a/src/cockpit/389-console/src/lib/database/localPwp.jsx b/src/cockpit/389-console/src/lib/database/localPwp.jsx
new file mode 100644
index 0000000..3b9bac0
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/localPwp.jsx
@@ -0,0 +1,2375 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import CustomCollapse from "../customCollapse.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
+import { PwpTable } from "./databaseTables.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ TabContainer,
+ TabContent,
+ TabPane,
+ Spinner,
+} from "patternfly-react";
+import { Typeahead } from "react-bootstrap-typeahead";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+const general_attrs = [
+ "passwordstoragescheme",
+ "passwordtrackupdatetime",
+ "passwordchange",
+ "passwordmustchange",
+ "passwordhistory",
+ "passwordinhistory",
+ "passwordminage",
+];
+
+const exp_attrs = [
+ "passwordexp",
+ "passwordgracelimit",
+ "passwordsendexpiringtime",
+ "passwordmaxage",
+ "passwordwarning",
+];
+
+const lockout_attrs = [
+ "passwordlockout",
+ "passwordunlock",
+ "passwordlockoutduration",
+ "passwordmaxfailure",
+ "passwordresetfailurecount",
+];
+
+const syntax_attrs = [
+ "passwordchecksyntax",
+ "passwordminlength",
+ "passwordmindigits",
+ "passwordminalphas",
+ "passwordminuppers",
+ "passwordminlowers",
+ "passwordminspecials",
+ "passwordmin8bit",
+ "passwordmaxrepeats",
+ "passwordpalindrome",
+ "passwordmaxsequence",
+ "passwordmaxseqsets",
+ "passwordmaxclasschars",
+ "passwordmincategories",
+ "passwordmintokenlength",
+ "passwordbadwords",
+ "passworduserattributes",
+ "passworddictcheck",
+];
+
+class CreatePolicy extends React.Component {
+ render() {
+ return (
+ <div>
+ <Form className="ds-margin-left" horizontal>
+ <Row>
+ <h4>Create A New Local Password Policy</h4>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={3}>
+ Password Policy Type
+ </Col>
+ <Col sm={8}>
+ <select
+ className="btn btn-default dropdown ds-margin-left-sm" id="createPolicyType"
+ onChange={this.props.handleChange}>
+ <option>Subtree Policy</option>
+ <option>User Policy</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top ds-config-header" title="The DN of the entry to apply this password policy to.">
+ <Col componentClass={ControlLabel} sm={3}>
+ Target DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="policyDN"
+ type="text"
+ onChange={this.props.handleChange}
+ className={this.props.invalid_dn ? "ds-margin-left-sm ds-input-auto ds-input-bad" : "ds-margin-left-sm ds-input-auto"}
+ />
+ </Col>
+ </Row>
+
+ <CustomCollapse textClosed="Show General Settings" textOpened="Hide General Settings" className="ds-margin-right ds-margin-top-lg">
+ <div className="ds-margin-left">
+ <Row title="Set the password storage scheme (passwordstoragescheme)." className="ds-margin-top">
+ <Col sm={3}>
+ <ControlLabel>
+ Password Storage Scheme
+ </ControlLabel>
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown" id="create_passwordstoragescheme"
+ onChange={this.props.handleChange}>
+ <option>PBKDF2_SHA256</option>
+ <option>SSHA512</option>
+ <option>SSHA384</option>
+ <option>SSHA256</option>
+ <option>SSHA</option>
+ <option>MD5</option>
+ <option>SMD5</option>
+ <option>CRYPT-MD5</option>
+ <option>CRYPT-SHA512</option>
+ <option>CRYPT-SHA256</option>
+ <option>CRYPT</option>
+ <option>CLEAR</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Indicates the number of seconds that must pass before a user can change their password again. (passwordMinAge)">
+ <Col sm={3}>
+ <ControlLabel>
+ Password Minimum Age
+ </ControlLabel>
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordminage"
+ type="number"
+ min="0"
+ max="2147483647"
+ onChange={this.props.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordtrackupdatetime"
+ onChange={this.props.handleChange}
+ >
+ Track Password Update Time
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow user's to change their passwords (passwordChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordchange"
+ onChange={this.props.handleChange}
+ >
+ Allow Users To Change Their Passwords
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="User must change its password after its been reset by an administrator (passwordMustChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordmustchange"
+ onChange={this.props.handleChange}
+ >
+ User Must Change Password After Reset
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Maintain a password history for each user (passwordHistory).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordhistory"
+ onChange={this.props.handleChange}
+ >
+ Keep Password History
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top ds-margin-bottom" title="The number of passwords to remember for each user (passwordInHistory).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Passwords In History
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordinhistory"
+ type="number"
+ min="0"
+ max="24"
+ onChange={this.props.handleChange}
+ />
+ </Col>
+ </Row>
+ </div>
+ </CustomCollapse>
+
+ <CustomCollapse textClosed="Show Expiration Settings" textOpened="Hide Expiration Settings" className="ds-margin-right">
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable a password expiration policy (passwordExp).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordexp"
+ onChange={this.props.handleChange}
+ >
+ Enforce Password Expiration
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maxiumum age of a password in seconds before it expires (passwordMaxAge).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Password Expiration Time
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmaxage"
+ type="number"
+ min="1"
+ max="2147483647"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordexp}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of logins that are allowed after the password has expired (passwordGraceLimit).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Allowed Logins After Password Expires
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordgracelimit"
+ type="number"
+ min="0"
+ max="128"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordexp}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Send Password Expiring Warning
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordwarning"
+ type="number"
+ min="1"
+ max="2147483647"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordexp}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Always return a password expiring control when requested (passwordSendExpiringTime).">
+ <Col componentClass={ControlLabel} sm={6}>
+ <Checkbox
+ id="create_passwordsendexpiringtime"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordexp}
+ >
+ Always Send <i>Password Expiring</i> Control
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ </CustomCollapse>
+
+ <CustomCollapse textClosed="Show Lockout Settings" textOpened="Hide Lockout Settings" className="ds-margin-right">
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable account lockout (passwordLockout).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordlockout"
+ onChange={this.props.handleChange}
+ >
+ Enable Account Lockout
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of failed logins before account gets locked (passwordMaxFailure).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Number of Failed Logins That Locks out Account
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmaxfailure"
+ type="number"
+ min="1"
+ max="100"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordlockout}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Time Until <i>Failure Count</i> Resets
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordresetfailurecount"
+ type="number"
+ min="1"
+ max="2147483647"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordlockout}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds, duration, before the account gets unlocked (passwordLockoutDuration).">
+ <Col componentClass={ControlLabel} sm={6}>
+ Time Until Account Unlocked
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordlockoutduration"
+ type="number"
+ min="1"
+ max="2147483647"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordlockout}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Do not lockout the user account forever, instead the account will unlock based on the lockout duration (passwordUnlock)">
+ <Col componentClass={ControlLabel} sm={6}>
+ <Checkbox
+ id="create_passwordunlock"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordlockout}
+ >
+ Do Not Lockout Account Forever
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ </CustomCollapse>
+
+ <CustomCollapse textClosed="Show Syntax Settings" textOpened="Hide Syntax Settings" className="ds-margin-right">
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable password syntax checking (passwordCheckSyntax).">
+ <Col sm={11}>
+ <Checkbox
+ id="create_passwordchecksyntax"
+ onChange={this.props.handleChange}
+ >
+ Enable Password Syntax Checking
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The minimum number of characters in the password (passwordMinLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordminlength"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3} title="Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">
+ Min Alpha's
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordminalphas"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Digits
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmindigits"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Special
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">
+ <FormControl
+ id="create_passwordminspecials"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Uppercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">
+ <FormControl
+ id="create_passwordminuppers"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Lowercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">
+ <FormControl
+ id="create_passwordminlowers"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min 8-bit
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmin8bit"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Categories
+ </Col>
+ <Col sm={2} title="The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">
+ <FormControl
+ id="create_passwordmincategories"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Token Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmintokenlength"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Repeated Chars
+ </Col>
+ <Col sm={2} title="The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">
+ <FormControl
+ id="create_passwordmaxrepeats"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequences
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmaxsequence"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequence Sets
+ </Col>
+ <Col sm={2} title="The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">
+ <FormControl
+ id="create_passwordmaxseqsets"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Seq Per Class
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="create_passwordmaxclasschars"
+ type="number"
+ min="0"
+ max="1000"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Prohibited Words
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="create_passwordbadwords"
+ type="text"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of entry attributes to compare to the new password (passwordUserAttributes)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Check User Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ onChange={values => {
+ this.props.handleChange(values);
+ }}
+ multiple
+ selected={this.props.passworduserattributes}
+ options={this.props.attrs}
+ placeholder="Type attributes to check..."
+ disabled={!this.props.passwordchecksyntax}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Check the password against the system's CrackLib dictionary (passwordDictCheck).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="create_passworddictcheck"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ >
+ Dictionary Check
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject a password if it is a palindrome (passwordPalindrome).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="create_passwordpalindrome"
+ onChange={this.props.handleChange}
+ disabled={!this.props.passwordchecksyntax}
+ >
+ Reject Palindromes
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ </CustomCollapse>
+ </Form>
+ <Button
+ disabled={this.props.createDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.props.createPolicy}
+ >
+ Create New Policy
+ </Button>
+ </div>
+ );
+ }
+}
+
+export class LocalPwPolicy extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: true,
+ loaded: false,
+ activeKey: 1,
+ localActiveKey: 1,
+ modalChecked: false,
+ editPolicy: false,
+ tableLoading: false,
+ // Create policy
+ policyType: "Subtree Policy",
+ policyDN: "",
+ policyName: "",
+ deleteName: "",
+ createDisabled: true,
+ createPolicyType: "Subtree Policy",
+ // Lists of all the attributes for each tab/section.
+ // We use the exact attribute name for the ID of
+ // each field, so we can loop over them to efficently
+ // check for changes, and updating/saving the config.
+
+ rows: [],
+ saveGeneralDisabled: true,
+ saveUserDisabled: true,
+ saveExpDisabled: true,
+ saveLockoutDisabled: true,
+ saveSyntaxDisabled: true,
+ showDeletePolicy: false,
+ attrMap: {
+ "passwordstoragescheme": "--pwdscheme",
+ "passwordtrackupdatetime": "--pwdtrack",
+ "passwordchange": "--pwdchange",
+ "passwordmustchange": "--pwdmustchange",
+ "passwordhistory": "--pwdhistory",
+ "passwordinhistory": "--pwdhistorycount",
+ "passwordminage": "--pwdminage",
+ "passwordexp": "--pwdexpire",
+ "passwordgracelimit": "--pwdgracelimit",
+ "passwordsendexpiringtime": "--pwdsendexpiring",
+ "passwordmaxage": "--pwdmaxage",
+ "passwordwarning": "--pwdwarning",
+ "passwordlockout": "--pwdlockout",
+ "passwordunlock": "--pwdunlock",
+ "passwordlockoutduration": "--pwdlockoutduration",
+ "passwordmaxfailure": "--pwdmaxfailures",
+ "passwordresetfailurecount": "--pwdresetfailcount",
+ "passwordchecksyntax": "--pwdchecksyntax",
+ "passwordminlength": "--pwdminlen",
+ "passwordmindigits": "--pwdmindigits",
+ "passwordminalphas": "--pwdminalphas",
+ "passwordminuppers": "--pwdminuppers",
+ "passwordminlowers": "--pwdminlowers",
+ "passwordminspecials": "--pwdminspecials",
+ "passwordmin8bit": "--pwdmin8bits",
+ "passwordmaxrepeats": "--pwdmaxrepeats",
+ "passwordpalindrome": "--pwdpalindrome",
+ "passwordmaxsequence": "--pwdmaxseq",
+ "passwordmaxseqsets": "--pwdmaxseqsets",
+ "passwordmaxclasschars": "--pwdmaxclasschars",
+ "passwordmincategories": "--pwdmincatagories",
+ "passwordmintokenlength": "--pwdmintokenlen",
+ "passwordbadwords": "--pwdbadwords",
+ "passworduserattributes": "--pwduserattrs",
+ "passworddictcheck": "--pwddictcheck",
+ },
+ };
+
+ this.createPolicy = this.createPolicy.bind(this);
+ this.closeDeletePolicy = this.closeDeletePolicy.bind(this);
+ this.deletePolicy = this.deletePolicy.bind(this);
+ this.handleCreateChange = this.handleCreateChange.bind(this);
+ this.handleExpChange = this.handleExpChange.bind(this);
+ this.handleGeneralChange = this.handleGeneralChange.bind(this);
+ this.handleLocalNavSelect = this.handleLocalNavSelect.bind(this);
+ this.handleLockoutChange = this.handleLockoutChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleSyntaxChange = this.handleSyntaxChange.bind(this);
+ this.loadLocal = this.loadLocal.bind(this);
+ this.loadPolicies = this.loadPolicies.bind(this);
+ this.resetTab = this.resetTab.bind(this);
+ this.saveExp = this.saveExp.bind(this);
+ this.saveGeneral = this.saveGeneral.bind(this);
+ this.saveLockout = this.saveLockout.bind(this);
+ this.saveSyntax = this.saveSyntax.bind(this);
+ this.showDeletePolicy = this.showDeletePolicy.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadPolicies();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ showDeletePolicy(name) {
+ this.setState({
+ showDeletePolicy: true,
+ modalChecked: false,
+ deleteName: name
+ });
+ }
+
+ closeDeletePolicy() {
+ this.setState({
+ showDeletePolicy: false,
+ deleteName: "",
+ });
+ }
+
+ resetTab() {
+ // Reset to the table tab
+ this.setState({ localActiveKey: 1 });
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleLocalNavSelect(key) {
+ this.setState({ localActiveKey: key });
+ }
+
+ handleModalChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+
+ this.setState({
+ [attr]: value,
+ });
+ }
+
+ handleCreateChange(e) {
+ let attr;
+ let value;
+ let disableSaveBtn = true;
+ let invalid_dn = false;
+ let all_attrs = general_attrs.concat(exp_attrs, lockout_attrs, syntax_attrs);
+
+ if (Array.isArray(e)) {
+ // Typeahead - convert array to string
+ attr = "create_passworduserattributes";
+ value = e.join(' ');
+ } else {
+ value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ attr = e.target.id;
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ for (let all_attr of all_attrs) {
+ if (all_attr == 'passworduserattributes' && attr == 'create_passworduserattributes') {
+ let orig_val = this.state['_' + all_attr].join(' ');
+ if (orig_val != value) {
+ value = e; // restore value
+ disableSaveBtn = false;
+ break;
+ }
+ value = e; // restore value
+ } else if (attr == "create_" + all_attr && this.state['_create_' + all_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+ if (attr == "policyDN" && value != "") {
+ if (valid_dn(value)) {
+ disableSaveBtn = false;
+ } else {
+ invalid_dn = true;
+ }
+ }
+ // Now check for differences in values that we did not touch
+ for (let all_attr of all_attrs) {
+ if (all_attr == 'passworduserattributes' && attr != 'create_passworduserattributes') {
+ // Typeahead attribute needs special care
+ let orig_val = this.state['_' + all_attr].join(' ');
+ let new_val = this.state[all_attr].join(' ');
+ if (orig_val != new_val) {
+ disableSaveBtn = false;
+ break;
+ }
+ } else if (attr != "create_" + all_attr && this.state['_create_' + all_attr] != this.state["create_" + all_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ if (this.state.policyDN == "" || (attr == "policyDN" && value == "")) {
+ disableSaveBtn = true;
+ }
+
+ this.setState({
+ [attr]: value,
+ createDisabled: disableSaveBtn,
+ invalid_dn: invalid_dn,
+ });
+ }
+
+ createPolicy() {
+ let all_attrs = general_attrs.concat(exp_attrs, lockout_attrs, syntax_attrs);
+ let action = "adduser";
+
+ this.setState({
+ loading: true
+ });
+
+ if (this.state.policyType == "Subtree Policy") {
+ action = "addsubtree";
+ }
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'localpwp', action, this.state.policyDN
+ ];
+
+ for (let attr of all_attrs) {
+ let old_val = this.state['_create_' + attr];
+ let new_val = this.state['create_' + attr];
+ if (new_val != old_val) {
+ if (typeof new_val === "boolean") {
+ if (new_val) {
+ new_val = "on";
+ } else {
+ new_val = "off";
+ }
+ } else if (attr == 'passworduserattributes') {
+ if (old_val.join(' ') == new_val.join(' ')) {
+ continue;
+ }
+ }
+ cmd.push(this.state.attrMap[attr] + "=" + new_val);
+ }
+ }
+
+ log_cmd("createPolicy", "Create a local password policy", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadPolicies();
+ this.setState({
+ loading: false,
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully created new password policy"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadPolicies();
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error creating password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleGeneralChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let general_attr of general_attrs) {
+ if (attr == general_attr && this.state['_' + general_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let general_attr of general_attrs) {
+ if (attr != general_attr && this.state['_' + general_attr] != this.state[general_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveGeneralDisabled: disableSaveBtn,
+ });
+ }
+
+ saveGeneral() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'localpwp', 'set', this.state.policyName
+ ];
+
+ for (let attr of general_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(this.state.attrMap[attr] + "=" + val);
+ }
+ }
+
+ log_cmd("saveGeneral", "Saving general pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleExpChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let exp_attr of exp_attrs) {
+ if (attr == exp_attr && this.state['_' + exp_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let exp_attr of exp_attrs) {
+ if (attr != exp_attr && this.state['_' + exp_attr] != this.state[exp_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveExpDisabled: disableSaveBtn,
+ });
+ }
+
+ saveExp() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'localpwp', 'set', this.state.policyName
+ ];
+
+ for (let attr of exp_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(this.state.attrMap[attr] + "=" + val);
+ }
+ }
+
+ log_cmd("saveExp", "Saving Expiration pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleLockoutChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let lockout_attr of lockout_attrs) {
+ if (attr == lockout_attr && this.state['_' + lockout_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let lockout_attr of lockout_attrs) {
+ if (attr != lockout_attr && this.state['_' + lockout_attr] != this.state[lockout_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveLockoutDisabled: disableSaveBtn,
+ });
+ }
+
+ saveLockout() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'localpwp', 'set', this.state.policyName
+ ];
+
+ for (let attr of lockout_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(this.state.attrMap[attr] + "=" + val);
+ }
+ }
+
+ log_cmd("saveLockout", "Saving lockout pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ handleSyntaxChange(e) {
+ // Could be a typeahead change, check if "e" is an Array
+ let attr;
+ let value;
+ if (Array.isArray(e)) {
+ // Typeahead - convert array to string
+ attr = "passworduserattributes";
+ value = e.join(' ');
+ } else {
+ value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ attr = e.target.id;
+ }
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let syntax_attr of syntax_attrs) {
+ if (syntax_attr == 'passworduserattributes' && attr == 'passworduserattributes') {
+ let orig_val = this.state['_' + syntax_attr].join(' ');
+ if (orig_val != value) {
+ value = e; // restore value
+ disableSaveBtn = false;
+ break;
+ }
+ value = e; // restore value
+ } else if (attr == syntax_attr && this.state['_' + syntax_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let syntax_attr of syntax_attrs) {
+ if (syntax_attr == 'passworduserattributes' && attr != 'passworduserattributes') {
+ // Typeahead attribute needs special care
+ let orig_val = this.state['_' + syntax_attr].join(' ');
+ let new_val = this.state[syntax_attr].join(' ');
+ if (orig_val != new_val) {
+ disableSaveBtn = false;
+ break;
+ }
+ } else if (attr != syntax_attr && this.state['_' + syntax_attr] != this.state[syntax_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveSyntaxDisabled: disableSaveBtn,
+ });
+ }
+
+ saveSyntax() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'localpwp', 'set', this.state.policyName
+ ];
+
+ for (let attr of syntax_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(this.state.attrMap[attr] + "=" + val);
+ }
+ }
+
+ log_cmd("saveSyntax", "Saving syntax checking pwpolicy settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "success",
+ "Successfully updated password policy configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadLocal(this.state.policyName);
+ this.setState({
+ loading: false
+ });
+ this.props.addNotification(
+ "error",
+ `Error updating password policy configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deletePolicy() {
+ // Start spinning
+ let new_rows = this.state.rows;
+ for (let row of new_rows) {
+ if (row.targetdn == this.state.deleteName) {
+ row.pwp_type = [<Spinner className="ds-lower-field" key={row.pwp_type} loading size="sm" />];
+ row.basedn = [<Spinner className="ds-lower-field" key={row.basedn} loading size="sm" />];
+ row.actions = [<Spinner className="ds-lower-field" key={row.targetdn} loading size="sm" />];
+ }
+ }
+ this.setState({
+ rows: new_rows,
+ editPolicy: false,
+ });
+
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "localpwp", "remove", this.state.deleteName
+ ];
+ log_cmd("deletePolicy", "delete policy", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.loadPolicies();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadPolicies();
+ this.props.addNotification(
+ "error",
+ `Error deleting local password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadPolicies() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "localpwp", "list"
+ ];
+ log_cmd("loadPolicies", "Load all the local password policies for the table", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let policy_obj = JSON.parse(content);
+ this.setState({
+ localActiveKey: 1,
+ activeKey: 1,
+ rows: policy_obj.items,
+ loaded: true,
+ loading: false,
+ editPolicy: false,
+ policyDN: "",
+ policyType: "Subtree Policy",
+ policyName: "",
+ deleteName: "",
+ showDeletePolicy: false,
+ // Reset edit and create tab
+ saveGeneralDisabled: true,
+ saveUserDisabled: true,
+ saveExpDisabled: true,
+ saveLockoutDisabled: true,
+ saveSyntaxDisabled: true,
+ // Edit policy
+ passwordchange: false,
+ passwordmustchange: false,
+ passwordhistory: false,
+ passwordtrackupdatetime: false,
+ passwordexp: false,
+ passwordsendexpiringtime: false,
+ passwordlockout: false,
+ passwordunlock: "0",
+ passwordchecksyntax: false,
+ passwordpalindrome: false,
+ passworddictcheck: false,
+ passwordstoragescheme: "",
+ passwordinhistory: "0",
+ passwordwarning: "0",
+ passwordmaxage: "0",
+ passwordminage: "0",
+ passwordgracelimit: "0",
+ passwordlockoutduration: "0",
+ passwordmaxfailure: "0",
+ passwordresetfailurecount: "0",
+ passwordminlength: "0",
+ passwordmindigits: "0",
+ passwordminalphas: "0",
+ passwordminuppers: "0",
+ passwordminlowers: "0",
+ passwordminspecials: "0",
+ passwordmin8bit: "0",
+ passwordmaxrepeats: "0",
+ passwordmaxsequence: "0",
+ passwordmaxseqsets: "0",
+ passwordmaxclasschars: "0",
+ passwordmincategories: "0",
+ passwordmintokenlength: "0",
+ passwordbadwords: "",
+ passworduserattributes: [],
+ _passwordchange: false,
+ _passwordmustchange: false,
+ _passwordhistory: false,
+ _passwordtrackupdatetime: false,
+ _passwordexp: false,
+ _passwordsendexpiringtime: false,
+ _passwordlockout: false,
+ _passwordunlock: "0",
+ _passwordchecksyntax: false,
+ _passwordpalindrome: false,
+ _passworddictcheck: false,
+ _passwordstoragescheme: "",
+ _passwordinhistory: "0",
+ _passwordwarning: "0",
+ _passwordmaxage: "0",
+ _passwordminage: "0",
+ _passwordgracelimit: "0",
+ _passwordlockoutduration: "0",
+ _passwordmaxfailure: "0",
+ _passwordresetfailurecount: "0",
+ _passwordminlength: "0",
+ _passwordmindigits: "0",
+ _passwordminalphas: "0",
+ _passwordminuppers: "0",
+ _passwordminlowers: "0",
+ _passwordminspecials: "0",
+ _passwordmin8bit: "0",
+ _passwordmaxrepeats: "0",
+ _passwordmaxsequence: "0",
+ _passwordmaxseqsets: "0",
+ _passwordmaxclasschars: "0",
+ _passwordmincategories: "0",
+ _passwordmintokenlength: "0",
+ _passwordbadwords: "",
+ _passworduserattributes: [],
+ // Create policy
+ create_passwordchange: false,
+ create_passwordmustchange: false,
+ create_passwordhistory: false,
+ create_passwordtrackupdatetime: false,
+ create_passwordexp: false,
+ create_passwordsendexpiringtime: false,
+ create_passwordlockout: false,
+ create_passwordunlock: "0",
+ create_passwordchecksyntax: false,
+ create_passwordpalindrome: false,
+ create_passworddictcheck: false,
+ create_passwordstoragescheme: "",
+ create_passwordinhistory: "0",
+ create_passwordwarning: "0",
+ create_passwordmaxage: "0",
+ create_passwordminage: "0",
+ create_passwordgracelimit: "0",
+ create_passwordlockoutduration: "0",
+ create_passwordmaxfailure: "0",
+ create_passwordresetfailurecount: "0",
+ create_passwordminlength: "0",
+ create_passwordmindigits: "0",
+ create_passwordminalphas: "0",
+ create_passwordminuppers: "0",
+ create_passwordminlowers: "0",
+ create_passwordminspecials: "0",
+ create_passwordmin8bit: "0",
+ create_passwordmaxrepeats: "0",
+ create_passwordmaxsequence: "0",
+ create_passwordmaxseqsets: "0",
+ create_passwordmaxclasschars: "0",
+ create_passwordmincategories: "0",
+ create_passwordmintokenlength: "0",
+ create_passwordbadwords: "",
+ create_passworduserattributes: [],
+ _create_passwordchange: false,
+ _create_passwordmustchange: false,
+ _create_passwordhistory: false,
+ _create_passwordtrackupdatetime: false,
+ _create_passwordexp: false,
+ _create_passwordsendexpiringtime: false,
+ _create_passwordlockout: false,
+ _create_passwordunlock: "0",
+ _create_passwordchecksyntax: false,
+ _create_passwordpalindrome: false,
+ _create_passworddictcheck: false,
+ _create_passwordstoragescheme: "",
+ _create_passwordinhistory: "0",
+ _create_passwordwarning: "0",
+ _create_passwordmaxage: "0",
+ _create_passwordminage: "0",
+ _create_passwordgracelimit: "0",
+ _create_passwordlockoutduration: "0",
+ _create_passwordmaxfailure: "0",
+ _create_passwordresetfailurecount: "0",
+ _create_passwordminlength: "0",
+ _create_passwordmindigits: "0",
+ _create_passwordminalphas: "0",
+ _create_passwordminuppers: "0",
+ _create_passwordminlowers: "0",
+ _create_passwordminspecials: "0",
+ _create_passwordmin8bit: "0",
+ _create_passwordmaxrepeats: "0",
+ _create_passwordmaxsequence: "0",
+ _create_passwordmaxseqsets: "0",
+ _create_passwordmaxclasschars: "0",
+ _create_passwordmincategories: "0",
+ _create_passwordmintokenlength: "0",
+ _create_passwordbadwords: "",
+ _create_passworduserattributes: [],
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ loading: false,
+ });
+ this.props.addNotification(
+ "error",
+ `Error loading local password policies - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadLocal(name) {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "localpwp", "get", name
+ ];
+ log_cmd("loadLocal", "Load a local password policy", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let pwChange = false;
+ let pwMustChange = false;
+ let pwHistory = false;
+ let pwTrackUpdate = false;
+ let pwExpire = false;
+ let pwSendExpire = false;
+ let pwLockout = false;
+ let pwUnlock = false;
+ let pwCheckSyntax = false;
+ let pwPalindrome = false;
+ let pwDictCheck = false;
+ let pwUserAttrs = [];
+ let pwInHistory = "0";
+ let pwBadWords = "";
+ let pwScheme = "";
+ let pwWarning = "0";
+ let pwMaxAge = "0";
+ let pwMinAge = "0";
+ let pwGraceLimit = "0";
+ let pwLockoutDur = "0";
+ let pwMaxFailure = "0";
+ let pwFailCount = "0";
+ let pwMinLen = "0";
+ let pwMinDigits = "0";
+ let pwMinAlphas = "0";
+ let pwMinUppers = "0";
+ let pwMinLowers = "0";
+ let pwMinSpecials = "0";
+ let pwMin8bit = "0";
+ let pwMaxRepeats = "0";
+ let pwMaxSeq = "0";
+ let pwMaxSeqSets = "0";
+ let pwMaxClassChars = "0";
+ let pwMinCat = "0";
+ let pwMinTokenLen = "0";
+
+ if ('passwordmintokenlength' in attrs) {
+ pwMinTokenLen = attrs['passwordmintokenlength'][0];
+ }
+ if ('passwordmincategories' in attrs) {
+ pwMinCat = attrs['passwordmincategories'][0];
+ }
+ if ('passwordmaxclasschars' in attrs) {
+ pwMaxClassChars = attrs['passwordmaxclasschars'][0];
+ }
+ if ('passwordmaxseqsets' in attrs) {
+ pwMaxSeqSets = attrs['passwordmaxseqsets'][0];
+ }
+ if ('passwordmaxsequence' in attrs) {
+ pwMaxSeq = attrs['passwordmaxsequence'][0];
+ }
+ if ('passwordmaxrepeats' in attrs) {
+ pwMaxRepeats = attrs['passwordmaxrepeats'][0];
+ }
+ if ('passwordmin8bit' in attrs) {
+ pwMin8bit = attrs['passwordmin8bit'][0];
+ }
+ if ('passwordminspecials' in attrs) {
+ pwMinSpecials = attrs['passwordminspecials'][0];
+ }
+ if ('passwordminlowers' in attrs) {
+ pwMinLowers = attrs['passwordminlowers'][0];
+ }
+ if ('passwordminuppers' in attrs) {
+ pwMinUppers = attrs['passwordminuppers'][0];
+ }
+ if ('passwordminalphas' in attrs) {
+ pwMinAlphas = attrs['passwordminalphas'][0];
+ }
+ if ('passwordmindigits' in attrs) {
+ pwMinDigits = attrs['passwordmindigits'][0];
+ }
+ if ('passwordminlength' in attrs) {
+ pwMinLen = attrs['passwordminlength'][0];
+ }
+ if ('passwordresetfailurecount' in attrs) {
+ pwFailCount = attrs['passwordresetfailurecount'][0];
+ }
+ if ('passwordmaxfailure' in attrs) {
+ pwMaxFailure = attrs['passwordmaxfailure'][0];
+ }
+ if ('passwordlockoutduration' in attrs) {
+ pwLockoutDur = attrs['passwordlockoutduration'][0];
+ }
+ if ('passwordgracelimit' in attrs) {
+ pwGraceLimit = attrs['passwordgracelimit'][0];
+ }
+ if ('passwordmaxage' in attrs) {
+ pwMaxAge = attrs['passwordmaxage'][0];
+ }
+ if ('passwordminage' in attrs) {
+ pwMinAge = attrs['passwordminage'][0];
+ }
+ if ('passwordwarning' in attrs) {
+ pwWarning = attrs['passwordwarning'][0];
+ }
+ if ('passwordstoragescheme' in attrs) {
+ pwScheme = attrs['passwordstoragescheme'][0];
+ }
+ if ('passwordinhistory' in attrs) {
+ pwInHistory = attrs['passwordinhistory'][0];
+ }
+ if ('passwordchange' in attrs && attrs['passwordchange'][0] == "on") {
+ pwChange = true;
+ }
+ if ('passwordmustchange' in attrs && attrs['passwordmustchange'][0] == "on") {
+ pwMustChange = true;
+ }
+ if ('passwordhistory' in attrs && attrs['passwordhistory'][0] == "on") {
+ pwHistory = true;
+ }
+ if ('passwordtrackupdatetime' in attrs && attrs['passwordtrackupdatetime'][0] == "on") {
+ pwTrackUpdate = true;
+ }
+ if ('passwordsendexpiringtime' in attrs && attrs['passwordsendexpiringtime'][0] == "on") {
+ pwSendExpire = true;
+ }
+ if ('passwordlockout' in attrs && attrs['passwordlockout'][0] == "on") {
+ pwLockout = true;
+ }
+ if ('passwordunlock' in attrs && attrs['passwordunlock'][0] == "on") {
+ pwUnlock = true;
+ }
+ if ('passwordexp' in attrs && attrs['passwordexp'][0] == "on") {
+ pwExpire = true;
+ }
+ if ('passwordchecksyntax' in attrs && attrs['passwordchecksyntax'][0] == "on") {
+ pwCheckSyntax = true;
+ }
+ if ('passwordpalindrome' in attrs && attrs['passwordpalindrome'][0] == "on") {
+ pwPalindrome = true;
+ }
+ if ('passworddictcheck' in attrs && attrs['passworddictcheck'][0] == "on") {
+ pwDictCheck = true;
+ }
+ if ('passwordbadwords' in attrs && attrs['passwordbadwords'][0] != "") {
+ // Hack until this is fixed: https://pagure.io/389-ds-base/issue/50875
+ if (attrs['passwordbadwords'].length > 1) {
+ pwBadWords = attrs['passwordbadwords'].join(' ');
+ } else {
+ pwBadWords = attrs['passwordbadwords'][0];
+ }
+ }
+ if ('passworduserattributes' in attrs && attrs['passworduserattributes'][0] != "") {
+ if (attrs['passworduserattributes'].length > 1) {
+ // Hack until this is fixed: https://pagure.io/389-ds-base/issue/50875
+ attrs['passworduserattributes'][0] = attrs['passworduserattributes'].join(' ');
+ }
+ // Could be space or comma separated list
+ if (attrs['passworduserattributes'][0].indexOf(',') > -1) {
+ pwUserAttrs = attrs['passworduserattributes'][0].trim();
+ pwUserAttrs = pwUserAttrs.split(',');
+ } else {
+ pwUserAttrs = attrs['passworduserattributes'][0].split();
+ }
+ }
+
+ this.setState(() => (
+ {
+ editPolicy: true,
+ loading: false,
+ localActiveKey: 2,
+ activeKey: 1,
+ policyName: name,
+ saveGeneralDisabled: true,
+ saveUserDisabled: true,
+ saveExpDisabled: true,
+ saveLockoutDisabled: true,
+ saveSyntaxDisabled: true,
+ // Settings
+ passwordchange: pwChange,
+ passwordmustchange: pwMustChange,
+ passwordhistory: pwHistory,
+ passwordtrackupdatetime: pwTrackUpdate,
+ passwordexp: pwExpire,
+ passwordsendexpiringtime: pwSendExpire,
+ passwordlockout: pwLockout,
+ passwordunlock: pwUnlock,
+ passwordchecksyntax: pwCheckSyntax,
+ passwordpalindrome: pwPalindrome,
+ passworddictcheck: pwDictCheck,
+ passwordstoragescheme: pwScheme,
+ passwordinhistory: pwInHistory,
+ passwordwarning: pwWarning,
+ passwordmaxage: pwMaxAge,
+ passwordminage: pwMinAge,
+ passwordgracelimit: pwGraceLimit,
+ passwordlockoutduration: pwLockoutDur,
+ passwordmaxfailure: pwMaxFailure,
+ passwordresetfailurecount: pwFailCount,
+ passwordminlength: pwMinLen,
+ passwordmindigits: pwMinDigits,
+ passwordminalphas: pwMinAlphas,
+ passwordminuppers: pwMinUppers,
+ passwordminlowers: pwMinLowers,
+ passwordminspecials: pwMinSpecials,
+ passwordmin8bit: pwMin8bit,
+ passwordmaxrepeats: pwMaxRepeats,
+ passwordmaxsequence: pwMaxSeq,
+ passwordmaxseqsets: pwMaxSeqSets,
+ passwordmaxclasschars: pwMaxClassChars,
+ passwordmincategories: pwMinCat,
+ passwordmintokenlength: pwMinTokenLen,
+ passwordbadwords: pwBadWords,
+ passworduserattributes: pwUserAttrs,
+ // Record original values
+ _passwordchange: pwChange,
+ _passwordmustchange: pwMustChange,
+ _passwordhistory: pwHistory,
+ _passwordtrackupdatetime: pwTrackUpdate,
+ _passwordexp: pwExpire,
+ _passwordsendexpiringtime: pwSendExpire,
+ _passwordlockout: pwLockout,
+ _passwordunlock: pwUnlock,
+ _passwordchecksyntax: pwCheckSyntax,
+ _passwordpalindrome: pwPalindrome,
+ _passworddictcheck: pwDictCheck,
+ _passwordstoragescheme: pwScheme,
+ _passwordinhistory: pwInHistory,
+ _passwordwarning: pwWarning,
+ _passwordmaxage: pwMaxAge,
+ _passwordminage: pwMinAge,
+ _passwordgracelimit: pwGraceLimit,
+ _passwordlockoutduration: pwLockoutDur,
+ _passwordmaxfailure: pwMaxFailure,
+ _passwordresetfailurecount: pwFailCount,
+ _passwordminlength: pwMinLen,
+ _passwordmindigits: pwMinDigits,
+ _passwordminalphas: pwMinAlphas,
+ _passwordminuppers: pwMinUppers,
+ _passwordminlowers: pwMinLowers,
+ _passwordminspecials: pwMinSpecials,
+ _passwordmin8bit: pwMin8bit,
+ _passwordmaxrepeats: pwMaxRepeats,
+ _passwordmaxsequence: pwMaxSeq,
+ _passwordmaxseqsets: pwMaxSeqSets,
+ _passwordmaxclasschars: pwMaxClassChars,
+ _passwordmincategories: pwMinCat,
+ _passwordmintokenlength: pwMinTokenLen,
+ _passwordbadwords: pwBadWords,
+ _passworduserattributes: pwUserAttrs,
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ loading: false,
+ });
+ this.props.addNotification(
+ "error",
+ `Error loading local password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ render() {
+ let edit_tab = "";
+ let pwExpirationRows = "";
+ let pwLockoutRows = "";
+ let pwSyntaxRows = "";
+
+ if (this.state.passwordchecksyntax) {
+ pwSyntaxRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The minimum number of characters in the password (passwordMinLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordminlength"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminlength}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3} title="Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">
+ Min Alpha's
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordminalphas"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminalphas}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Digits
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmindigits"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmindigits}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Special
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">
+ <FormControl
+ id="passwordminspecials"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminspecials}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Uppercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">
+ <FormControl
+ id="passwordminuppers"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminuppers}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Lowercase
+ </Col>
+ <Col sm={2} title="Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">
+ <FormControl
+ id="passwordminlowers"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordminlowers}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min 8-bit
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmin8bit"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmin8bit}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Categories
+ </Col>
+ <Col sm={2} title="The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">
+ <FormControl
+ id="passwordmincategories"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmincategories}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Min Token Length
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmintokenlength"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmintokenlength}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Repeated Chars
+ </Col>
+ <Col sm={2} title="The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">
+ <FormControl
+ id="passwordmaxrepeats"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxrepeats}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequences
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxsequence"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxsequence}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Sequence Sets
+ </Col>
+ <Col sm={2} title="The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">
+ <FormControl
+ id="passwordmaxseqsets"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxseqsets}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max Seq Per Class
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxclasschars"
+ type="number"
+ min="0"
+ max="1000"
+ value={this.state.passwordmaxclasschars}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Prohibited Words
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="passwordbadwords"
+ type="text"
+ value={this.state.passwordbadwords}
+ onChange={this.handleSyntaxChange}
+ />
+ </Col>
+ </Row>
+ <Row title="A space-separated list of entry attributes to compare to the new password (passwordUserAttributes)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Check User Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ onChange={values => {
+ this.handleSyntaxChange(values);
+ }}
+ multiple
+ selected={this.state.passworduserattributes}
+ options={this.props.attrs}
+ placeholder="Type attributes to check..."
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Check the password against the system's CrackLib dictionary (passwordDictCheck).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="passworddictcheck"
+ defaultChecked={this.state.passworddictcheck}
+ onChange={this.handleSyntaxChange}
+ >
+ Dictionary Check
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Reject a password if it is a palindrome (passwordPalindrome).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="passwordpalindrome"
+ defaultChecked={this.state.passwordpalindrome}
+ onChange={this.handleSyntaxChange}
+ >
+ Reject Palindromes
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (this.state.passwordlockout) {
+ pwLockoutRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The maximum number of failed logins before account gets locked (passwordMaxFailure).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Number of Failed Logins That Locks out Account
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxfailure"
+ type="number"
+ min="1"
+ max="100"
+ value={this.state.passwordmaxfailure}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Time Until <i>Failure Count</i> Resets
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordresetfailurecount"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordresetfailurecount}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds, duration, before the account gets unlocked (passwordLockoutDuration).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Time Until Account Unlocked
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordlockoutduration"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordlockoutduration}
+ onChange={this.handleLockoutChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Do not lockout the user account forever, instead the account will unlock based on the lockout duration (passwordUnlock)">
+ <Col sm={5}>
+ <Checkbox
+ id="passwordunlock"
+ defaultChecked={this.state.passwordunlock}
+ onChange={this.handleLockoutChange}
+ >
+ Do Not Lockout Account Forever
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (this.state.passwordexp) {
+ pwExpirationRows =
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="The maxiumum age of a password in seconds before it expires (passwordMaxAge).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Password Expiration Time
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordmaxage"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordmaxage}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of logins that are allowed after the password has expired (passwordGraceLimit).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Allowed Logins After Password Expires
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordgracelimit"
+ type="number"
+ min="0"
+ max="128"
+ value={this.state.passwordgracelimit}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">
+ <Col componentClass={ControlLabel} sm={5}>
+ Send Password Expiring Warning
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="passwordwarning"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state.passwordwarning}
+ onChange={this.handleExpChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Always return a password expiring control when requested (passwordSendExpiringTime).">
+ <Col componentClass={ControlLabel} sm={5}>
+ <Checkbox
+ id="passwordsendexpiringtime"
+ defaultChecked={this.state.passwordsendexpiringtime}
+ onChange={this.handleExpChange}
+ >
+ Always Send <i>Password Expiring</i> Control
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ if (!this.state.editPolicy) {
+ edit_tab =
+ <div className="ds-margin-top-xlg ds-center">
+ <h4>Please choose a policy from the <a onClick={this.resetTab}>Local Policy Table</a>.</h4>
+ </div>;
+ } else {
+ edit_tab =
+ <div className="ds-margin-left">
+ <h4 className="ds-margin-top-xlg">{this.state.policyName}</h4>
+ <TabContainer id="server-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top-xlg">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'General Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Expiration'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Account Lockout'}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: 'Syntax Checking'}} />
+ </NavItem>
+ </Nav>
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row title="Set the password storage scheme (passwordstoragescheme)." className="ds-margin-top">
+ <Col sm={8}>
+ <ControlLabel>
+ Password Storage Scheme
+ </ControlLabel>
+ <select
+ className="btn btn-default dropdown ds-margin-left-sm" id="passwordstoragescheme"
+ onChange={this.handleGeneralChange} value={this.state.passwordstoragescheme}>
+ <option>PBKDF2_SHA256</option>
+ <option>SSHA512</option>
+ <option>SSHA384</option>
+ <option>SSHA256</option>
+ <option>SSHA</option>
+ <option>MD5</option>
+ <option>SMD5</option>
+ <option>CRYPT-MD5</option>
+ <option>CRYPT-SHA512</option>
+ <option>CRYPT-SHA256</option>
+ <option>CRYPT</option>
+ <option>CLEAR</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordtrackupdatetime"
+ defaultChecked={this.state.passwordtrackupdatetime}
+ onChange={this.handleGeneralChange}
+ >
+ Track Password Update Time
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow user's to change their passwords (passwordChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordchange"
+ defaultChecked={this.state.passwordchange}
+ onChange={this.handleGeneralChange}
+ >
+ Allow Users To Change Their Passwords
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="User must change its password after its been reset by an administrator (passwordMustChange).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordmustchange"
+ defaultChecked={this.state.passwordmustchange}
+ onChange={this.handleGeneralChange}
+ >
+ User Must Change Password After Reset
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Maintain a password history for each user (passwordHistory).">
+ <Col sm={11}>
+ <div className="ds-inline">
+ <Checkbox
+ id="passwordhistory"
+ defaultChecked={this.state.passwordhistory}
+ onChange={this.handleUserChange}
+ >
+ Keep Password History
+ </Checkbox>
+ </div>
+ <div className="ds-inline ds-left-margin ds-raise-field ds-width-sm">
+ <FormControl
+ id="passwordinhistory"
+ type="number"
+ min="0"
+ max="24"
+ value={this.state.passwordinhistory}
+ onChange={this.handleGeneralChange}
+ disabled={!this.state.passwordhistory}
+ />
+ </div>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Indicates the number of seconds that must pass before a user can change their password again. (passwordMinAge)">
+ <Col sm={12}>
+ <div className="ds-inline">
+ <ControlLabel>
+ Password Minimum Age
+ </ControlLabel>
+ </div>
+ <div className="ds-inline ds-left-margin ds-raise-field">
+ <FormControl
+ id="passwordminage"
+ type="number"
+ min="0"
+ max="2147483647"
+ value={this.state.passwordminage}
+ onChange={this.handleGeneralChange}
+ />
+ </div>
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveGeneralDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg"
+ onClick={this.saveGeneral}
+ title="Save the General Settings"
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={2}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable a password expiration policy (passwordExp).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordexp"
+ defaultChecked={this.state.passwordexp}
+ onChange={this.handleExpChange}
+ >
+ Enforce Password Expiration
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwExpirationRows}
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveExp}
+ title="Save the Expiration Settings"
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={3}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable account lockout (passwordLockout).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordlockout"
+ defaultChecked={this.state.passwordlockout}
+ onChange={this.handleLockoutChange}
+ >
+ Enable Account Lockout
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwLockoutRows}
+ <Button
+ disabled={this.state.saveLockoutDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveLockout}
+ title="Save the Lockout Settings"
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={4}>
+ <Form className="ds-margin-top-lg ds-margin-left" horizontal>
+ <Row className="ds-margin-top" title="Enable password syntax checking (passwordCheckSyntax).">
+ <Col sm={11}>
+ <Checkbox
+ id="passwordchecksyntax"
+ checked={this.state.passwordchecksyntax}
+ onChange={this.handleSyntaxChange}
+ >
+ Enable Password Syntax Checking
+ </Checkbox>
+ </Col>
+ </Row>
+ {pwSyntaxRows}
+ <Button
+ disabled={this.state.saveSyntaxDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveSyntax}
+ title="Save the Syntax Settings"
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+ }
+
+ let body =
+ <div className="ds-margin-top-lg">
+ <TabContainer id="local-passwords" onSelect={this.handleLocalNavSelect} activeKey={this.state.localActiveKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Local Policy Table'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Edit Policy'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Create Policy'}} />
+ </NavItem>
+ </Nav>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <div className="ds-margin-top-xlg">
+ <PwpTable
+ rows={this.state.rows}
+ editPolicy={this.loadLocal}
+ deletePolicy={this.showDeletePolicy}
+ />
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ {edit_tab}
+ </TabPane>
+ <TabPane eventKey={3}>
+ <CreatePolicy
+ handleChange={this.handleCreateChange}
+ attrs={this.props.attrs}
+ passwordexp={this.state.create_passwordexp}
+ passwordchecksyntax={this.state.create_passwordchecksyntax}
+ passwordlockout={this.state.create_passwordlockout}
+ createDisabled={this.state.createDisabled}
+ passworduserattributes={this.state.create_passworduserattributes}
+ createPolicy={this.createPolicy}
+ invalid_dn={this.state.invalid_dn}
+ key={this.state.rows}
+ />
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ if (this.state.loading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div>
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ Local Password Policies
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the local password policies"
+ onClick={this.reloadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ <DoubleConfirmModal
+ showModal={this.state.showDeletePolicy}
+ closeHandler={this.closeDeletePolicy}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deletePolicy}
+ item={this.state.deleteName}
+ checked={this.state.modalChecked}
+ spinning={this.state.tableLoading}
+ mTitle="Delete Local Password Policy"
+ mMsg="Are you sure you want to delete this local password policy?"
+ mSpinningMsg="Deleting local password policy ..."
+ mBtnName="Delete Policy"
+ />
+ </div>
+ );
+ }
+}
+
+LocalPwPolicy.propTypes = {
+ attrs: PropTypes.array,
+};
+
+LocalPwPolicy.defaultProps = {
+ attrs: [],
+};
diff --git a/src/cockpit/389-console/src/lib/database/pwpolicy.jsx b/src/cockpit/389-console/src/lib/database/pwpolicy.jsx
new file mode 100644
index 0000000..ae68796
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/pwpolicy.jsx
@@ -0,0 +1,692 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Form,
+ Col,
+ Nav,
+ NavItem,
+ Checkbox,
+ TabContainer,
+ TabContent,
+ TabPane,
+ TreeView,
+ Spinner,
+ Button
+} from "patternfly-react";
+import "../../css/ds.css";
+
+const GLOBAL_POLICY = "global-policy";
+
+export class ServerPwPolicy extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ disableTree: false,
+ nodes: [],
+ node_name: "",
+ node_text: "",
+ dbtype: "",
+
+ // Tuning settings
+
+ }
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.selectNode = this.selectNode.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ this.savePwp = this.savePwp.bind(this);
+ this.loadPwp = this.loadPwp.bind(this);
+ this.loadGlobal = this.loadGlobal.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config TODO
+ if (!this.state.loaded) {
+ this.loadGlobal();
+ }
+ }
+
+ loadGlobal() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "pwpolicy", "get"
+ ];
+ log_cmd("loadGlobal", "Load global password policy", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let pwpLocal = false;
+ let pwpIsglobal = false;
+ let pwMustChange = false;
+ let pwHistory = false;
+ let pwTrackUpdate = false;
+ let pwExpire = false;
+ let pwSendExpire = false;
+ let pwLockout = false;
+ let pwUnlock = false;
+ let pwCheckSyntax = false;
+ let pwPalindrome = false;
+ let pwDictCheck = false;
+ let pwAllowHashed = false;
+
+ if (attrs['nsslapd-pwpolicy-local'][0] == "on") {
+ pwpLocal = true;
+ }
+ if (attrs['passwordchange'][0] == "on") {
+ pwMustChange = true;
+ }
+ if (attrs['passwordhistory'][0] == "on") {
+ pwHistory = true;
+ }
+ if (attrs['passwordtrackupdatetime'][0] == "on") {
+ pwTrackUpdate = true;
+ }
+ if (attrs['passwordisglobalpolicy'][0] == "on") {
+ pwpIsglobal = true;
+ }
+ if (attrs['passwordsendexpiringtime'][0] == "on") {
+ pwSendExpire = true;
+ }
+ if (attrs['passwordlockout'][0] == "on") {
+ pwLockout = true;
+ }
+ if (attrs['passwordunlock'][0] == "on") {
+ pwUnlock = true;
+ }
+ if (attrs['passwordexp'][0] == "on") {
+ pwExpire = true;
+ }
+ if (attrs['passwordchecksyntax'][0] == "on") {
+ pwCheckSyntax = true;
+ }
+ if (attrs['passwordpalindrome'][0] == "on") {
+ pwPalindrome = true;
+ }
+ if (attrs['passworddictcheck'][0] == "on") {
+ pwExpire = true;
+ }
+ if (attrs['nsslapd-allow-hashed-passwords'][0] == "on") {
+ pwAllowHashed = true;
+ }
+
+ this.setState(() => (
+ {
+ loaded: true,
+ loading: false,
+ // Settings
+ pwpLocal: pwpLocal,
+ pwpIsglobal: pwpIsglobal,
+ pwMustChange: pwMustChange,
+ pwTrackUpdate: pwTrackUpdate,
+ pwExpire: pwExpire,
+ pwSendExpire: pwSendExpire,
+ pwLockout: pwLockout,
+ pwUnlock: pwUnlock,
+ pwCheckSyntax: pwCheckSyntax,
+ pwPalindrome: pwPalindrome,
+ pwDictCheck: pwDictCheck,
+ pwAllowHashed: pwAllowHashed,
+ passwordstoragescheme: attrs['passwordstoragescheme'][0],
+ pwInHistory: attrs['passwordinhistory'][0],
+ pwWarning: attrs['passwordwarning'][0],
+ pwMaxAge: attrs['passwordmaxage'][0],
+ pwMinAge: attrs['passwordminage'][0],
+ pwGraceLimit: attrs['passwordgracelimit'][0],
+ pwLockoutDur: attrs['passwordlockoutduration'][0],
+ pwMaxFailure: attrs['passwordmaxfailure'][0],
+ pwResetFailureCount: attrs['passwordresetfailurecount'][0],
+ pwMinLen: attrs['passwordminlength'][0],
+ pwMinDigit: attrs['passwordmindigits'][0],
+ pwMinAlpha: attrs['passwordminalphas'][0],
+ pwMinUppers: attrs['passwordminuppers'][0],
+ pwMinLowers: attrs['passwordminlowers'][0],
+ pwMinSpecial: attrs['passwordminspecials'][0],
+ pwMin8bit: attrs['passwordmin8bit'][0],
+ pwMaxRepeats: attrs['passwordmaxrepeats'][0],
+ pwMaxSeq: attrs['passwordmaxsequence'][0],
+ pwMaxSeqSets: attrs['passwordmaxseqsets'][0],
+ pwMaxClass: attrs['passwordmaxclasschars'][0],
+ pwMinCat: attrs['passwordmincategories'][0],
+ pwMinTokenLen: attrs['passwordmintokenlength'][0],
+ pwBadWords: attrs['passwordbadwords'][0],
+ pwUserAttrs: attrs['passworduserattributes'][0],
+ pwDictPath: attrs['passworddictpath'][0],
+ // Record original values
+ _pwpLocal: pwpLocal,
+ _pwpIsglobal: pwpIsglobal,
+ _pwMustChange: pwMustChange,
+ _pwTrackUpdate: pwTrackUpdate,
+ _pwExpire: pwExpire,
+ _pwSendExpire: pwSendExpire,
+ _pwLockout: pwLockout,
+ _pwUnlock: pwUnlock,
+ _pwCheckSyntax: pwCheckSyntax,
+ _pwPalindrome: pwPalindrome,
+ _pwDictCheck: pwDictCheck,
+ _pwAllowHashed: pwAllowHashed,
+ _passwordstoragescheme: attrs['passwordstoragescheme'][0],
+ _pwWarning: attrs['passwordwarning'][0],
+ _pwInHistory: attrs['passwordinhistory'][0],
+ _pwMaxAge: attrs['passwordmaxage'][0],
+ _pwMinAge: attrs['passwordminage'][0],
+ _pwGraceLimit: attrs['passwordgracelimit'][0],
+ _pwLockoutDur: attrs['passwordlockoutduration'][0],
+ _pwMaxFailure: attrs['passwordmaxfailure'][0],
+ _pwResetFailureCount: attrs['passwordresetfailurecount'][0],
+ _pwMinLen: attrs['passwordminlength'][0],
+ _pwMinDigit: attrs['passwordmindigits'][0],
+ _pwMinAlpha: attrs['passwordminalphas'][0],
+ _pwMinUppers: attrs['passwordminuppers'][0],
+ _pwMinLowers: attrs['passwordminlowers'][0],
+ _pwMinSpecial: attrs['passwordminspecials'][0],
+ _pwMin8bit: attrs['passwordmin8bit'][0],
+ _pwMaxRepeats: attrs['passwordmaxrepeats'][0],
+ _pwMaxSeq: attrs['passwordmaxsequence'][0],
+ _pwMaxSeqSets: attrs['passwordmaxseqsets'][0],
+ _pwMaxClass: attrs['passwordmaxclasschars'][0],
+ _pwMinCat: attrs['passwordmincategories'][0],
+ _pwMinTokenLen: attrs['passwordmintokenlength'][0],
+ _pwBadWords: attrs['passwordbadwords'][0],
+ _pwUserAttrs: attrs['passworduserattributes'][0],
+ _pwDictPath: attrs['passworddictpath'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ loading: false,
+ });
+ this.addNotification(
+ "error",
+ `Error loading global password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadLocal(name) {
+ // What makes this tough is that local polices can only have a single
+ // attribute, while global has them all. So we have to check every
+ // single possible attribute if it's present.
+ this.setState({
+ loading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "localpwp", "get", name
+ ];
+ log_cmd("loadLocal", "Load local password policy", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let pwMustChange = false;
+ let pwHistory = false;
+ let pwTrackUpdate = false;
+ let pwExpire = false;
+ let pwSendExpire = false;
+ let pwLockout = false;
+ let pwUnlock = false;
+ let pwCheckSyntax = false;
+ let pwPalindrome = false;
+ let pwDictCheck = false;
+ let pwAllowHashed = false;
+ let pwStorageScheme = "PBKDF2_SHA256";
+ let pwInHistory = "";
+ let pwWarning = "";
+ let pwMaxAge = "";
+ let pwMinAge = "";
+ let pwGraceLimit = "";
+ let pwLockoutDur = "";
+ let pwMaxFailure = "";
+ let pwResetFailureCount = "";
+ let pwMinLen = "";
+ let pwMinDigit = "";
+ let pwMinAlpha = "";
+ let pwMinUppers = "";
+ let pwMinLowers = "";
+ let pwMinSpecial = "";
+ let pwMin8bit = "";
+ let pwMaxRepeats = "";
+ let pwMaxSeq = "";
+ let pwMaxSeqSets = "";
+ let pwMaxClass = "";
+ let pwMinCat = "";
+ let pwMinTokenLen = "";
+ let pwBadWords = "";
+ let pwUserAttrs = "";
+ let pwDictPath = "";
+
+ if ('passwordchange' in attrs && attrs['passwordchange'][0] == "on") {
+ pwMustChange = true;
+ }
+ if ('passwordhistory' in attrs && attrs['passwordhistory'][0] == "on") {
+ pwHistory = true;
+ }
+ if ('passwordtrackupdatetime' in attrs && attrs['passwordtrackupdatetime'][0] == "on") {
+ pwTrackUpdate = true;
+ }
+ if ('passwordsendexpiringtime' in attrs && attrs['passwordsendexpiringtime'][0] == "on") {
+ pwSendExpire = true;
+ }
+ if ('passwordlockout' in attrs && attrs['passwordlockout'][0] == "on") {
+ pwLockout = true;
+ }
+ if ('passwordunlock' in attrs && attrs['passwordunlock'][0] == "on") {
+ pwUnlock = true;
+ }
+ if ('passwordexp' in attrs && attrs['passwordexp'][0] == "on") {
+ pwExpire = true;
+ }
+ if ('passwordchecksyntax' in attrs && attrs['passwordchecksyntax'][0] == "on") {
+ pwCheckSyntax = true;
+ }
+ if ('passwordpalindrome' in attrs && attrs['passwordpalindrome'][0] == "on") {
+ pwPalindrome = true;
+ }
+ if ('passworddictcheck' in attrs && attrs['passworddictcheck'][0] == "on") {
+ pwExpire = true;
+ }
+ if ('passwordstoragescheme' in attrs) {
+ pwStorageScheme = attrs['passwordstoragescheme'][0];
+ }
+ if ('passwordinhistory' in attrs) {
+ pwInHistory = attrs['passwordinhistory'][0];
+ }
+ if ('passwordwarning' in attrs) {
+ pwWarning = attrs['passwordwarning'][0];
+ }
+ if ('passwordmaxage' in attrs) {
+ pwMaxAge = attrs['passwordmaxage'][0];
+ }
+ if ('passwordminage' in attrs) {
+ pwMinAge = attrs['passwordminage'][0];
+ }
+ if ('passwordgracelimit' in attrs) {
+ pwMinAge = attrs['passwordgracelimit'][0];
+ }
+ if ('passwordlockoutduration' in attrs) {
+ pwLockoutDur = attrs['passwordlockoutduration'][0];
+ }
+ if ('passwordmaxfailure' in attrs) {
+ pwMaxFailure = attrs['passwordmaxfailure'][0];
+ }
+ if ('passwordresetfailurecount' in attrs) {
+ pwResetFailureCount = attrs['passwordresetfailurecount'][0];
+ }
+ if ('passwordminlength' in attrs) {
+ pwMinLen = attrs['passwordminlength'][0];
+ }
+ if ('passwordmindigits' in attrs) {
+ pwMinDigit = attrs['passwordmindigits'][0];
+ }
+ if ('passwordminalphas' in attrs) {
+ pwMinAlpha = attrs['passwordminalphas'][0];
+ }
+ if ('passwordminuppers' in attrs) {
+ pwMinLen = attrs['passwordminuppers'][0];
+ }
+ if ('passwordminlowers' in attrs) {
+ pwMinUppers = attrs['passwordminlowers'][0];
+ }
+ if ('passwordminspecials' in attrs) {
+ pwMinSpecial = attrs['passwordminspecials'][0];
+ }
+ if ('passwordmin8bit' in attrs) {
+ pwMin8bit = attrs['passwordmin8bit'][0];
+ }
+ if ('passwordmaxrepeats' in attrs) {
+ pwMaxRepeats = attrs['passwordmaxrepeats'][0];
+ }
+ if ('passwordmaxsequence' in attrs) {
+ pwMaxSeq = attrs['passwordmaxsequence'][0];
+ }
+ if ('passwordmaxseqsets' in attrs) {
+ pwMaxSeqSets = attrs['passwordmaxseqsets'][0];
+ }
+ if ('passwordmaxclasschars' in attrs) {
+ pwMaxClass = attrs['passwordmaxclasschars'][0];
+ }
+ if ('passwordmincategories' in attrs) {
+ pwMinCat = attrs['passwordmincategories'][0];
+ }
+ if ('passwordmintokenlength' in attrs) {
+ pwMinTokenLen = attrs['passwordmintokenlength'][0];
+ }
+ if ('passwordbadwords' in attrs) {
+ pwBadWords = attrs['passwordbadwords'][0];
+ }
+ if ('passworduserattributes' in attrs) {
+ pwUserAttrs = attrs['passworduserattributes'][0];
+ }
+ if ('passworddictpath' in attrs) {
+ pwDictPath = attrs['passworddictpath'][0];
+ }
+
+ this.setState(() => (
+ {
+ loading: false,
+ // Settings
+ pwLocalMustChange: pwMustChange,
+ pwLocalTrackUpdate: pwTrackUpdate,
+ pwLocalExpire: pwExpire,
+ pwLocalSendExpire: pwSendExpire,
+ pwLocalLockout: pwLockout,
+ pwLocalUnlock: pwUnlock,
+ pwLocalCheckSyntax: pwCheckSyntax,
+ pwLocalPalindrome: pwPalindrome,
+ pwLocalDictCheck: pwDictCheck,
+ pwLocalStorageScheme: pwStorageScheme,
+ pwLocalInHistory: pwInHistory,
+ pwLocalWarning: pwWarning,
+ pwLocalMaxAge: pwMaxAge,
+ pwLocalMinAge: pwMinAge,
+ pwLocalGraceLimit: pwGraceLimit,
+ pwLocalLockoutDur: pwLockoutDur,
+ pwLocalMaxFailure: pwMaxFailure,
+ pwLocalResetFailureCount: pwResetFailureCount,
+ pwLocalMinLen: pwMinLen,
+ pwLocalMinDigit: pwMinDigit,
+ pwLocalMinAlpha: pwMinAlpha,
+ pwLocalMinUppers: pwMinUppers,
+ pwLocalMinLowers: pwMinLowers,
+ pwLocalMinSpecial: pwMinSpecial,
+ pwLocalMin8bit: pwMin8bit,
+ pwLocalMaxRepeats: pwMaxRepeats,
+ pwLocalMaxSeq: pwMaxSeq,
+ pwLocalMaxSeqSets: pwMaxSeqSets,
+ pwLocalMaxClass: pwMaxClass,
+ pwLocalMinCat: pwMinCat,
+ pwLocalMinTokenLen: pwMinTokenLen,
+ pwLocalBadWords: pwBadWords,
+ pwLocalUserAttrs: pwUserAttrs,
+ pwLocalDictPath: pwDictPath,
+ // Record original values
+ _pwLocalMustChange: pwMustChange,
+ _pwLocalTrackUpdate: pwTrackUpdate,
+ _pwLocalExpire: pwExpire,
+ _pwLocalSendExpire: pwSendExpire,
+ _pwLocalLockout: pwLockout,
+ _pwLocalUnlock: pwUnlock,
+ _pwLocalCheckSyntax: pwCheckSyntax,
+ _pwLocalPalindrome: pwPalindrome,
+ _pwLocalDictCheck: pwDictCheck,
+ _pwLocalStorageScheme: pwStorageScheme,
+ _pwLocalInHistory: pwInHistory,
+ _pwLocalWarning: pwWarning,
+ _pwLocalMaxAge: pwMaxAge,
+ _pwLocalMinAge: pwMinAge,
+ _pwLocalGraceLimit: pwGraceLimit,
+ _pwLocalLockoutDur: pwLockoutDur,
+ _pwLocalMaxFailure: pwMaxFailure,
+ _pwLocalResetFailureCount: pwResetFailureCount,
+ _pwLocalMinLen: pwMinLen,
+ _pwLocalMinDigit: pwMinDigit,
+ _pwLocalMinAlpha: pwMinAlpha,
+ _pwLocalMinUppers: pwMinUppers,
+ _pwLocalMinLowers: pwMinLowers,
+ _pwLocalMinSpecial: pwMinSpecial,
+ _pwLocalMin8bit: pwMin8bit,
+ _pwLocalMaxRepeats: pwMaxRepeats,
+ _pwLocalMaxSeq: pwMaxSeq,
+ _pwLocalMaxSeqSets: pwMaxSeqSets,
+ _pwLocalMaxClass: pwMaxClass,
+ _pwLocalMinCat: pwMinCat,
+ _pwLocalMinTokenLen: pwMinTokenLen,
+ _pwLocalBadWords: pwBadWords,
+ _pwLocalUserAttrs: pwUserAttrs,
+ _pwLocalDictPath: pwDictPath,
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ loading: false,
+ });
+ this.addNotification(
+ "error",
+ `Error loading local password policy - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadSuffixTree() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "backend", "get-tree",
+ ];
+ log_cmd("loadSuffixTree", "Start building the suffix tree for local pwp", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let treeData = [];
+ if (content != "") {
+ treeData = JSON.parse(content);
+ }
+ let basicData = [
+ {
+ text: "Global Password Policy",
+ selectable: true,
+ selected: true,
+ icon: "pficon-home",
+ id: "global-policy",
+
+ },
+ {
+ text: "Suffixes",
+ icon: "pficon-catalog",
+ state: {"expanded": true},
+ selectable: false,
+ id: "pwp-suffixes",
+ nodes: []
+ }
+ ];
+ let current_node = this.state.node_name;
+ basicData[3].nodes = treeData;
+
+ this.setState(() => ({
+ nodes: basicData,
+ node_name: current_node,
+ }), this.update_tree_nodes);
+ });
+ }
+
+ selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+ this.setState({
+ disableTree: true // Disable the tree to allow node to be fully loaded
+ });
+
+ if (selectedNode.id == GLOBAL_POLICY) {
+ // Nothing special to do, this has already been loaded
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ dbtype: selectedNode.type,
+ bename: "",
+ };
+ });
+ } else {
+ if (selectedNode.id in this.state) {
+ // This suffix is already cached, just use what we have...
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ dbtype: selectedNode.type,
+ bename: selectedNode.be,
+ };
+ });
+ } else {
+ // Load this suffix
+ if (selectedNode.type == "suffix" || selecetedNode.type == "subsuffix") {
+ this.loadSuffix(selectedNode.id);
+ }
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ dbtype: selectedNode.type,
+ bename: selectedNode.be,
+ };
+ });
+ }
+ }
+ }
+
+ nodeSelector(nodes, targetNode) {
+ return nodes.map(node => {
+ if (node.nodes) {
+ return {
+ ...node,
+ nodes: this.nodeSelector(node.nodes, targetNode),
+ selected: node.id === targetNode.id ? !node.selected : false
+ };
+ } else if (node.id === targetNode.id) {
+ return { ...node, selected: !node.selected };
+ } else if (node.id !== targetNode.id && node.selected) {
+ return { ...node, selected: false };
+ } else {
+ return node;
+ }
+ });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ update_tree_nodes() {
+ // Set title to the text value of each suffix node. We need to do this
+ // so we can read long suffixes in the UI tree div
+ let elements = document.getElementsByClassName('treeitem-row');
+ for (let el of elements) {
+ el.setAttribute('title', el.innerText);
+ }
+ this.setState({
+ disableTree: false
+ });
+ }
+
+ render() {
+ let pwp_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+ let reloadSpinner = "";
+ if (this.state.loading) {
+ reloadSpinner = <Spinner loading size="md" />;
+ }
+
+ if (this.state.loaded) {
+ if (this.state.node_name == GLOBAL_POLICY || this.state.node_name == "") {
+ pwp_element =
+ <GlobalPwp
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ reload={this.loadGlobalConfig}
+ data={this.state.globalDBConfig}
+ enableTree={this.enableTree}
+ key={this.state.configUpdated}
+ />;
+ } else if (this.state.dbtype == "suffix" || this.state.dbtype == "subsuffix") {
+ if (this.state.suffixLoading) {
+ pwp_element =
+ <div className="ds-margin-top ds-loading-spinner ds-center">
+ <h4>Loading password policy for <b>{this.state.node_text} ...</b></h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+ } else {
+ db_element =
+ <Suffix
+
+ />;
+ }
+ } else {
+ // Chaining
+ pwp_element =
+ <div className="ds-margin-top ds-loading-spinner ds-center">
+ <h4>You can not have local password policies on a database link: <b>{this.state.node_text}</b></h4>
+ </div>;
+ }
+ }
+
+ return (
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={12} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header">
+ Password Policy
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh password policies"
+ onClick={() => {
+ this.loadSuffixTree(1);
+ }}
+ />
+ </ControlLabel>
+ {reloadSpinner}
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-container">
+ <div className="ds-tree">
+ <div className={disabled} id="pwp-tree"
+ style={treeViewContainerStyles}>
+ <TreeView
+ nodes={nodes}
+ highlightOnHover
+ highlightOnSelect
+ selectNode={this.selectNode}
+ />
+ </div>
+ </div>
+ <div className="ds-tree-content">
+ {pwp_element}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index e48809a..213847a 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -77,10 +77,10 @@ class AgmtDetailsModal extends React.Component {
let dateAttrs = ['last-update-start', 'last-update-end',
'last-init-start', 'last-init-end'];
for (let attr of dateAttrs) {
- if (agmt[attr] == "19700101000000Z") {
+ if (agmt[attr][0] == "19700101000000Z") {
convertedDate[attr] = "Unavailable";
} else {
- convertedDate[attr] = get_date_string(agmt[attr]);
+ convertedDate[attr] = get_date_string(agmt[attr][0]);
}
}
let initButton = null;
@@ -223,10 +223,10 @@ class WinsyncAgmtDetailsModal extends React.Component {
let dateAttrs = ['last-update-start', 'last-update-end',
'last-init-start', 'last-init-end'];
for (let attr of dateAttrs) {
- if (agmt[attr] == "19700101000000Z") {
+ if (agmt[attr][0] == "19700101000000Z") {
agmt[attr] = "Unavailable";
} else {
- agmt[attr] = get_date_string(agmt[attr]);
+ agmt[attr] = get_date_string(agmt[attr][0]);
}
}
diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
index f2bc245..74ba3da 100644
--- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
@@ -105,10 +105,7 @@ class AccountPolicy extends React.Component {
configEntry["altstateattrname"] === undefined
? []
: [
- {
- id: configEntry["altstateattrname"][0],
- label: configEntry["altstateattrname"][0]
- }
+ configEntry["altstateattrname"][0]
],
alwaysRecordLogin: !(
configEntry["alwaysrecordlogin"] === undefined ||
@@ -118,37 +115,25 @@ class AccountPolicy extends React.Component {
configEntry["alwaysrecordloginattr"] === undefined
? []
: [
- {
- id: configEntry["alwaysrecordloginattr"][0],
- label: configEntry["alwaysrecordloginattr"][0]
- }
+ configEntry["alwaysrecordloginattr"][0]
],
limitAttrName:
configEntry["limitattrname"] === undefined
? []
: [
- {
- id: configEntry["limitattrname"][0],
- label: configEntry["limitattrname"][0]
- }
+ configEntry["limitattrname"][0]
],
specAttrName:
configEntry["specattrname"] === undefined
? []
: [
- {
- id: configEntry["specattrname"][0],
- label: configEntry["specattrname"][0]
- }
+ configEntry["specattrname"][0]
],
stateAttrName:
configEntry["stateattrname"] === undefined
? []
: [
- {
- id: configEntry["stateattrname"][0],
- label: configEntry["stateattrname"][0]
- }
+ configEntry["stateattrname"][0],
]
});
this.props.toggleLoadingHandler();
@@ -200,7 +185,7 @@ class AccountPolicy extends React.Component {
cmd = [...cmd, "--alt-state-attr"];
if (altStateAttrName.length != 0) {
- cmd = [...cmd, altStateAttrName[0].label];
+ cmd = [...cmd, altStateAttrName[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -209,7 +194,7 @@ class AccountPolicy extends React.Component {
cmd = [...cmd, "--always-record-login-attr"];
if (alwaysRecordLoginAttr.length != 0) {
- cmd = [...cmd, alwaysRecordLoginAttr[0].label];
+ cmd = [...cmd, alwaysRecordLoginAttr[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -218,7 +203,7 @@ class AccountPolicy extends React.Component {
cmd = [...cmd, "--limit-attr"];
if (limitAttrName.length != 0) {
- cmd = [...cmd, limitAttrName[0].label];
+ cmd = [...cmd, limitAttrName[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -227,7 +212,7 @@ class AccountPolicy extends React.Component {
cmd = [...cmd, "--spec-attr"];
if (specAttrName.length != 0) {
- cmd = [...cmd, specAttrName[0].label];
+ cmd = [...cmd, specAttrName[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -236,7 +221,7 @@ class AccountPolicy extends React.Component {
cmd = [...cmd, "--state-attr"];
if (stateAttrName.length != 0) {
- cmd = [...cmd, stateAttrName[0].label];
+ cmd = [...cmd, stateAttrName[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -366,10 +351,7 @@ class AccountPolicy extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
@@ -641,7 +623,7 @@ class AccountPolicy extends React.Component {
<Row>
<Col sm={10}>
<Form horizontal>
- <FormGroup key="configArea" controlId="configArea">
+ <FormGroup key="configAreaAP" controlId="configAreaAP">
<Col
componentClass={ControlLabel}
sm={3}
diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
index cc9ed22..0c9af6a 100644
--- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
@@ -170,19 +170,13 @@ class AttributeUniqueness extends React.Component {
configEntry["uniqueness-top-entry-oc"] === undefined
? []
: [
- {
- id: configEntry["uniqueness-top-entry-oc"][0],
- label: configEntry["uniqueness-top-entry-oc"][0]
- }
+ 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]
- }
+ configEntry["uniqueness-subtree-entries-oc"][0]
]
});
@@ -192,7 +186,7 @@ class AttributeUniqueness extends React.Component {
for (let value of configEntry["uniqueness-attribute-name"]) {
configAttrNamesList = [
...configAttrNamesList,
- { id: value, label: value }
+ value
];
}
this.setState({ attrNames: configAttrNamesList });
@@ -203,7 +197,7 @@ class AttributeUniqueness extends React.Component {
for (let value of configEntry["uniqueness-subtrees"]) {
configSubtreesList = [
...configSubtreesList,
- { id: value, label: value }
+ value
];
}
this.setState({ subtrees: configSubtreesList });
@@ -260,7 +254,7 @@ class AttributeUniqueness extends React.Component {
cmd = [...cmd, "--attr-name"];
if (attrNames.length != 0) {
for (let value of attrNames) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -273,7 +267,7 @@ class AttributeUniqueness extends React.Component {
cmd = [...cmd, "--subtree"];
if (subtrees.length != 0) {
for (let value of subtrees) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -284,7 +278,7 @@ class AttributeUniqueness extends React.Component {
cmd = [...cmd, "--top-entry-oc"];
if (topEntryOc.length != 0) {
- cmd = [...cmd, topEntryOc[0].label];
+ cmd = [...cmd, topEntryOc[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -293,7 +287,7 @@ class AttributeUniqueness extends React.Component {
cmd = [...cmd, "--subtree-entries-oc"];
if (subtreeEnriesOc.length != 0) {
- cmd = [...cmd, subtreeEnriesOc[0].label];
+ cmd = [...cmd, subtreeEnriesOc[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -398,10 +392,7 @@ class AttributeUniqueness extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
@@ -429,10 +420,7 @@ class AttributeUniqueness extends React.Component {
const ocContent = JSON.parse(content);
let ocs = [];
for (let content of ocContent["items"]) {
- ocs.push({
- id: content.name,
- label: content.name
- });
+ ocs.push(content.name[0]);
}
this.setState({
objectClasses: ocs
diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
index aea83f6..015b74d 100644
--- a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
@@ -214,10 +214,7 @@ class AutoMembership extends React.Component {
let groupingAttr = definitionEntry["automembergroupingattr"][0];
this.setState({
groupingAttrMember: [
- {
- id: groupingAttr.split(":")[0],
- label: groupingAttr.split(":")[0]
- }
+ groupingAttr.split(":")[0]
],
groupingAttrEntry: groupingAttr.split(":")[1]
});
@@ -546,7 +543,7 @@ class AutoMembership extends React.Component {
this.setState({ regexExclusive: [] });
} else {
for (let value of regexEntry["automemberexclusiveregex"]) {
- exclusiveRegexList = [...exclusiveRegexList, { id: value, label: value }];
+ exclusiveRegexList = [...exclusiveRegexList, value];
}
this.setState({ regexExclusive: exclusiveRegexList });
}
@@ -554,7 +551,7 @@ class AutoMembership extends React.Component {
this.setState({ regexInclusive: [] });
} else {
for (let value of regexEntry["automemberinclusiveregex"]) {
- inclusiveRegexList = [...inclusiveRegexList, { id: value, label: value }];
+ inclusiveRegexList = [...inclusiveRegexList, value];
}
this.setState({ regexInclusive: inclusiveRegexList });
}
@@ -606,11 +603,11 @@ class AutoMembership extends React.Component {
automembertargetgroup: [regexTargetGroup],
automemberexclusiveregex:
regexExclusive.length !== 0
- ? regexExclusive.map(regex => regex.label)
+ ? regexExclusive.map(regex => regex)
: [],
automemberinclusiveregex:
regexInclusive.length !== 0
- ? regexInclusive.map(regex => regex.label)
+ ? regexInclusive.map(regex => regex)
: [],
needsadd: true
}
@@ -633,11 +630,11 @@ class AutoMembership extends React.Component {
automembertargetgroup: [regexTargetGroup],
automemberexclusiveregex:
regexExclusive.length !== 0
- ? regexExclusive.map(regex => regex.label)
+ ? regexExclusive.map(regex => regex)
: [],
automemberinclusiveregex:
regexInclusive.length !== 0
- ? regexInclusive.map(regex => regex.label)
+ ? regexInclusive.map(regex => regex)
: [],
needsupdate: true
}
@@ -697,10 +694,7 @@ class AutoMembership extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx b/src/cockpit/389-console/src/lib/plugins/dna.jsx
index 8e05995..df5c9f8 100644
--- a/src/cockpit/389-console/src/lib/plugins/dna.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx
@@ -292,7 +292,7 @@ class DNA extends React.Component {
this.setState({ type: [] });
} else {
for (let value of configEntry["dnatype"]) {
- dnaTypeList = [...dnaTypeList, { id: value, label: value }];
+ dnaTypeList = [...dnaTypeList, value];
}
this.setState({ type: dnaTypeList });
}
@@ -401,7 +401,7 @@ class DNA extends React.Component {
cmd = [...cmd, "--type"];
if (type.length != 0) {
for (let value of type) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -726,10 +726,7 @@ class DNA extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
index eb6e425..b9bd253 100644
--- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
@@ -137,19 +137,13 @@ class LinkedAttributes extends React.Component {
configEntry["linktype"] === undefined
? []
: [
- {
- id: configEntry["linktype"][0],
- label: configEntry["linktype"][0]
- }
+ configEntry["linktype"][0]
],
managedType:
configEntry["managedtype"] === undefined
? []
: [
- {
- id: configEntry["managedtype"][0],
- label: configEntry["managedtype"][0]
- }
+ configEntry["managedtype"][0]
],
linkScope:
configEntry["linkscope"] === undefined
@@ -195,7 +189,7 @@ class LinkedAttributes extends React.Component {
cmd = [...cmd, "--link-type"];
if (linkType.length != 0) {
- cmd = [...cmd, linkType[0].label];
+ cmd = [...cmd, linkType[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -204,7 +198,7 @@ class LinkedAttributes extends React.Component {
cmd = [...cmd, "--managed-type"];
if (managedType.length != 0) {
- cmd = [...cmd, managedType[0].label];
+ cmd = [...cmd, managedType[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -310,10 +304,7 @@ class LinkedAttributes extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
index 3c87169..b403fb2 100644
--- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
@@ -351,19 +351,13 @@ class ManagedEntries extends React.Component {
configEntry["meprdnattr"] === undefined
? []
: [
- {
- id: configEntry["meprdnattr"][0],
- label: configEntry["meprdnattr"][0]
- }
+ configEntry["meprdnattr"][0]
],
templateStaticAttr:
configEntry["mepstaticattr"] === undefined
? []
: [
- {
- id: configEntry["mepstaticattr"][0],
- label: configEntry["mepstaticattr"][0]
- }
+ configEntry["mepstaticattr"][0]
]
});
if (configEntry["mepmappedattr"] === undefined) {
@@ -372,7 +366,7 @@ class ManagedEntries extends React.Component {
for (let value of configEntry["mepmappedattr"]) {
templateMappedAttrObjectList = [
...templateMappedAttrObjectList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -415,7 +409,7 @@ class ManagedEntries extends React.Component {
cmd = [...cmd, "--rdn-attr"];
if (templateRDNAttr.length != 0) {
- cmd = [...cmd, templateRDNAttr[0].label];
+ cmd = [...cmd, templateRDNAttr[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -424,7 +418,7 @@ class ManagedEntries extends React.Component {
cmd = [...cmd, "--static-attr"];
if (templateStaticAttr.length != 0) {
- cmd = [...cmd, templateStaticAttr[0].label];
+ cmd = [...cmd, templateStaticAttr[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -434,7 +428,7 @@ class ManagedEntries extends React.Component {
cmd = [...cmd, "--mapped-attr"];
if (templateMappedAttr.length != 0) {
for (let value of templateMappedAttr) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else {
cmd = [...cmd, "delete"];
@@ -536,10 +530,7 @@ class ManagedEntries extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index dba8fba..391dd3d 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -183,10 +183,7 @@ class MemberOf extends React.Component {
configEntry["memberofautoaddoc"] === undefined
? []
: [
- {
- id: configEntry["memberofautoaddoc"][0],
- label: configEntry["memberofautoaddoc"][0]
- }
+ configEntry["memberofautoaddoc"][0],
],
configAllBackends: !(
configEntry["memberofallbackends"] === undefined ||
@@ -215,7 +212,7 @@ class MemberOf extends React.Component {
for (let value of configEntry["memberofattr"]) {
configAttrObjectList = [
...configAttrObjectList,
- { id: value, label: value }
+ value
];
}
this.setState({ configAttr: configAttrObjectList });
@@ -226,7 +223,7 @@ class MemberOf extends React.Component {
for (let value of configEntry["memberofgroupattr"]) {
configGroupAttrObjectList = [
...configGroupAttrObjectList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -298,7 +295,7 @@ class MemberOf extends React.Component {
cmd = [...cmd, "--autoaddoc"];
if (configAutoAddOC.length != 0) {
- cmd = [...cmd, configAutoAddOC[0].label];
+ cmd = [...cmd, configAutoAddOC[0]];
} else if (action == "add") {
cmd = [...cmd, ""];
} else {
@@ -309,13 +306,13 @@ class MemberOf extends React.Component {
cmd = [...cmd, "--attr"];
if (configAttr.length != 0) {
for (let value of configAttr) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
}
cmd = [...cmd, "--groupattr"];
if (configGroupAttr.length != 0) {
for (let value of configGroupAttr) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
}
@@ -422,10 +419,7 @@ class MemberOf extends React.Component {
pluginRow["memberofautoaddoc"] === undefined
? []
: [
- {
- id: pluginRow["memberofautoaddoc"][0],
- label: pluginRow["memberofautoaddoc"][0]
- }
+ pluginRow["memberofautoaddoc"][0],
],
memberOfAllBackends: !(
pluginRow["memberofallbackends"] === undefined ||
@@ -454,7 +448,7 @@ class MemberOf extends React.Component {
for (let value of pluginRow["memberofattr"]) {
memberOfAttrObjectList = [
...memberOfAttrObjectList,
- { id: value, label: value }
+ value
];
}
this.setState({ memberOfAttr: memberOfAttrObjectList });
@@ -465,7 +459,7 @@ class MemberOf extends React.Component {
for (let value of pluginRow["memberofgroupattr"]) {
memberOfGroupAttrObjectList = [
...memberOfGroupAttrObjectList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -491,10 +485,7 @@ class MemberOf extends React.Component {
const ocContent = JSON.parse(content);
let ocs = [];
for (let content of ocContent["items"]) {
- ocs.push({
- id: content.name,
- label: content.name
- });
+ ocs.push(content.name[0]);
}
this.setState({
objectClasses: ocs
@@ -522,10 +513,7 @@ class MemberOf extends React.Component {
const atContent = JSON.parse(content);
let attrs = [];
for (let content of atContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributeTypes: attrs
@@ -585,7 +573,7 @@ class MemberOf extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--autoaddoc"];
if (memberOfAutoAddOC.length != 0) {
- specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].label];
+ specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0]];
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
}
@@ -594,7 +582,7 @@ class MemberOf extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--attr"];
if (memberOfAttr.length != 0) {
for (let value of memberOfAttr) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -603,7 +591,7 @@ class MemberOf extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--groupattr"];
if (memberOfGroupAttr.length != 0) {
for (let value of memberOfGroupAttr) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -729,12 +717,7 @@ class MemberOf extends React.Component {
}}
selected={configAttr}
newSelectionPrefix="Add a member: "
- options={[
- {
- id: "memberOf",
- label: "memberOf"
- }
- ]}
+ options={["memberOf"]}
placeholder="Type a member attribute..."
/>
</Col>
@@ -927,18 +910,9 @@ class MemberOf extends React.Component {
selected={memberOfAttr}
newSelectionPrefix="Add a member attrbiute: "
options={[
- {
- id: "member",
- label: "member"
- },
- {
- id: "memberCertificate",
- label: "memberCertificate"
- },
- {
- id: "uniqueMember",
- label: "uniqueMember"
- }
+ "member",
+ "memberCertificate",
+ "uniqueMember"
]}
placeholder="Type a member attribute..."
/>
diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
index 4bda5ac..b3ede3f 100644
--- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
@@ -249,7 +249,7 @@ class PassthroughAuthentication extends React.Component {
for (let value of pamConfigEntry["pamexcludesuffix"]) {
pamExcludeSuffixList = [
...pamExcludeSuffixList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -263,7 +263,7 @@ class PassthroughAuthentication extends React.Component {
for (let value of pamConfigEntry["pamincludesuffix"]) {
pamIncludeSuffixList = [
...pamIncludeSuffixList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -275,7 +275,7 @@ class PassthroughAuthentication extends React.Component {
this.setState({ pamIDAttr: [] });
} else {
for (let value of pamConfigEntry["pamidattr"]) {
- pamIDAttrList = [...pamIDAttrList, { id: value, label: value }];
+ pamIDAttrList = [...pamIDAttrList, value];
}
this.setState({ pamIDAttr: pamIDAttrList });
}
@@ -399,7 +399,7 @@ class PassthroughAuthentication extends React.Component {
cmd = [...cmd, "--exclude-suffix"];
if (pamExcludeSuffix.length != 0) {
for (let value of pamExcludeSuffix) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -409,7 +409,7 @@ class PassthroughAuthentication extends React.Component {
cmd = [...cmd, "--include-suffix"];
if (pamIncludeSuffix.length != 0) {
for (let value of pamIncludeSuffix) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -419,7 +419,7 @@ class PassthroughAuthentication extends React.Component {
cmd = [...cmd, "--id-attr"];
if (pamIDAttr.length != 0) {
for (let value of pamIDAttr) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -642,10 +642,7 @@ class PassthroughAuthentication extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
index af83515..700c188 100644
--- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
@@ -140,7 +140,7 @@ class ReferentialIntegrity extends React.Component {
for (let value of pluginRow["referint-membership-attr"]) {
membershipAttrList = [
...membershipAttrList,
- { id: value, label: value }
+ value
];
}
this.setState({
@@ -206,7 +206,7 @@ class ReferentialIntegrity extends React.Component {
cmd = [...cmd, "--membership-attr"];
if (configMembershipAttr.length != 0) {
for (let value of configMembershipAttr) {
- cmd = [...cmd, value.label];
+ cmd = [...cmd, value];
}
} else if (action == "add") {
cmd = [...cmd, ""];
@@ -334,7 +334,7 @@ class ReferentialIntegrity extends React.Component {
this.setState({ membershipAttr: [] });
} else {
for (let value of pluginRow["referint-membership-attr"]) {
- membershipAttrList = [...membershipAttrList, { id: value, label: value }];
+ membershipAttrList = [...membershipAttrList, value];
}
this.setState({ membershipAttr: membershipAttrList });
}
@@ -357,10 +357,7 @@ class ReferentialIntegrity extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
@@ -418,7 +415,7 @@ class ReferentialIntegrity extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--membership-attr"];
if (membershipAttr.length != 0) {
for (let value of membershipAttr) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
index a7f35c7..c1ed953 100644
--- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
@@ -70,10 +70,7 @@ class RetroChangelog extends React.Component {
pluginRow["nsslapd-attribute"] === undefined
? []
: [
- {
- id: pluginRow["nsslapd-attribute"][0],
- label: pluginRow["nsslapd-attribute"][0]
- }
+ pluginRow["nsslapd-attribute"][0]
],
directory:
pluginRow["nsslapd-changelogdir"] === undefined
@@ -107,10 +104,7 @@ class RetroChangelog extends React.Component {
const attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent["items"]) {
- attrs.push({
- id: content.name,
- label: content.name
- });
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs
@@ -142,7 +136,7 @@ class RetroChangelog extends React.Component {
"--is-replicated",
isReplicated ? "TRUE" : "FALSE",
"--attribute",
- attribute.length != 0 ? attribute[0].label : "delete",
+ attribute.length != 0 ? attribute[0] : "delete",
"--directory",
directory || "delete",
"--max-age",
diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
index 9064adf..6c8b1eb 100644
--- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
@@ -66,7 +66,7 @@ class RootDNAccessControl extends React.Component {
this.setState({ allowHost: [] });
} else {
for (let value of pluginRow["rootdn-allow-host"]) {
- allowHostList = [...allowHostList, { id: value, label: value }];
+ allowHostList = [...allowHostList, value];
}
this.setState({ allowHost: allowHostList });
}
@@ -74,7 +74,7 @@ class RootDNAccessControl extends React.Component {
this.setState({ denyHost: [] });
} else {
for (let value of pluginRow["rootdn-deny-host"]) {
- denyHostList = [...denyHostList, { id: value, label: value }];
+ denyHostList = [...denyHostList, value];
}
this.setState({ denyHost: denyHostList });
}
@@ -82,7 +82,7 @@ class RootDNAccessControl extends React.Component {
this.setState({ allowIP: [] });
} else {
for (let value of pluginRow["rootdn-allow-ip"]) {
- allowIPList = [...allowIPList, { id: value, label: value }];
+ allowIPList = [...allowIPList, value];
}
this.setState({ allowIP: allowIPList });
}
@@ -90,7 +90,7 @@ class RootDNAccessControl extends React.Component {
this.setState({ denyIP: [] });
} else {
for (let value of pluginRow["rootdn-deny-ip"]) {
- denyIPList = [...denyIPList, { id: value, label: value }];
+ denyIPList = [...denyIPList, value];
}
this.setState({ denyIP: denyIPList });
}
@@ -127,7 +127,7 @@ class RootDNAccessControl extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--allow-host"];
if (allowHost.length != 0) {
for (let value of allowHost) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -135,7 +135,7 @@ class RootDNAccessControl extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--deny-host"];
if (denyHost.length != 0) {
for (let value of denyHost) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -143,7 +143,7 @@ class RootDNAccessControl extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--allow-ip"];
if (allowIP.length != 0) {
for (let value of allowIP) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -151,7 +151,7 @@ class RootDNAccessControl extends React.Component {
specificPluginCMD = [...specificPluginCMD, "--allow-host"];
if (allowHost.length != 0) {
for (let value of allowHost) {
- specificPluginCMD = [...specificPluginCMD, value.label];
+ specificPluginCMD = [...specificPluginCMD, value];
}
} else {
specificPluginCMD = [...specificPluginCMD, "delete"];
diff --git a/src/cockpit/389-console/src/lib/security/ciphers.jsx b/src/cockpit/389-console/src/lib/security/ciphers.jsx
index c187586..94d8f31 100644
--- a/src/cockpit/389-console/src/lib/security/ciphers.jsx
+++ b/src/cockpit/389-console/src/lib/security/ciphers.jsx
@@ -186,8 +186,7 @@ export class Ciphers extends React.Component {
if (this.state.saving) {
cipherPage =
- <div className="ds-loading-spinner ds-center">
- <p />
+ <div className="ds-loading-spinner ds-center ds-margin-top-lg">
<h4>Saving cipher preferences ...</h4>
<Spinner loading size="md" />
</div>;
@@ -274,10 +273,9 @@ export class Ciphers extends React.Component {
/>
</Col>
</Row>
- <p />
<Button
bsStyle="primary"
- className="ds-margin-top"
+ className="ds-margin-top-lg"
onClick={() => {
this.saveCipherPref();
}}
diff --git a/src/cockpit/389-console/src/lib/server/accessLog.jsx b/src/cockpit/389-console/src/lib/server/accessLog.jsx
new file mode 100644
index 0000000..85c5623
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/accessLog.jsx
@@ -0,0 +1,763 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+
+const accesslog_levels = [
+ 4,
+ 256,
+ 512
+];
+
+const settings_attrs = [
+ 'nsslapd-accesslog',
+ 'nsslapd-accesslog-level',
+ 'nsslapd-accesslog-logbuffering',
+ 'nsslapd-accesslog-logging-enabled',
+ 'accesslevel-4',
+ 'accesslevel-256',
+ 'accesslevel-512',
+];
+
+const rotation_attrs = [
+ 'nsslapd-accesslog-logrotationsync-enabled',
+ 'nsslapd-accesslog-logrotationsynchour',
+ 'nsslapd-accesslog-logrotationsyncmin',
+ 'nsslapd-accesslog-logrotationtime',
+ 'nsslapd-accesslog-logrotationtimeunit',
+ 'nsslapd-accesslog-maxlogsize',
+ 'nsslapd-accesslog-maxlogsperdir',
+];
+
+const exp_attrs = [
+ 'nsslapd-accesslog-logexpirationtime',
+ 'nsslapd-accesslog-logexpirationtimeunit',
+ 'nsslapd-accesslog-logmaxdiskspace',
+ 'nsslapd-accesslog-logminfreediskspace',
+];
+
+export class ServerAccessLog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ attrs: this.props.attrs,
+ };
+
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e, nav_tab) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableBtnName = "";
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ disableBtnName = "saveSettingsDisabled";
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ disableBtnName = "saveRotationDisabled";
+ } else {
+ config_attrs = exp_attrs;
+ disableBtnName = "saveExpDisabled";
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ for (let config_attr of config_attrs) {
+ if (attr == config_attr && this.state['_' + config_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let config_attr of config_attrs) {
+ if (attr != config_attr && this.state['_' + config_attr] != this.state[config_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ [disableBtnName]: disableSaveBtn,
+ });
+ }
+
+ saveConfig(nav_tab) {
+ let level_change = false;
+ let new_level = 0;
+ this.setState({
+ loading: true
+ });
+
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ } else {
+ config_attrs = exp_attrs;
+ }
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of config_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ if (attr.startsWith("accesslevel-")) {
+ level_change = true;
+ continue;
+ }
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ if (level_change) {
+ for (let level of accesslog_levels) {
+ if (this.state['accesslevel-' + level.toString()]) {
+ new_level += level;
+ }
+ }
+ cmd.push("nsslapd-accesslog-level" + "=" + new_level.toString());
+ }
+
+ log_cmd("saveConfig", "Saving access log settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "success",
+ "Successfully updated Access Log settings"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "error",
+ `Error saving Access Log settings - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadConfig() {
+ this.setState({
+ loading: true
+ });
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("reloadConfig", "load Access Log configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let enabled = false;
+ let buffering = false;
+ let level_val = parseInt(attrs['nsslapd-accesslog-level'][0]);
+ let loglevel = {};
+
+ if (attrs['nsslapd-accesslog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+ if (attrs['nsslapd-accesslog-logbuffering'][0] == "on") {
+ buffering = true;
+ }
+ for (let level of accesslog_levels) {
+ if (level & level_val) {
+ loglevel[level.toString()] = true;
+ } else {
+ loglevel[level.toString()] = false;
+ }
+ }
+
+ this.setState(() => (
+ {
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-accesslog': attrs['nsslapd-accesslog'][0],
+ 'nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],
+ 'accesslevel-4': loglevel['4'],
+ 'accesslevel-256': loglevel['256'],
+ 'accesslevel-512': loglevel['512'],
+ 'nsslapd-accesslog-logbuffering': buffering,
+ 'nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],
+ 'nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],
+ 'nsslapd-accesslog-logging-enabled': enabled,
+ 'nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],
+ 'nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],
+ 'nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],
+ 'nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],
+ 'nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],
+ 'nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],
+ 'nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],
+ 'nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],
+ 'nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-accesslog': attrs['nsslapd-accesslog'][0],
+ '_nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],
+ '_accesslevel-4': loglevel['4'],
+ '_accesslevel-256': loglevel['256'],
+ '_accesslevel-512': loglevel['512'],
+ '_nsslapd-accesslog-logbuffering': buffering,
+ '_nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],
+ '_nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],
+ '_nsslapd-accesslog-logging-enabled': enabled,
+ '_nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],
+ '_nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],
+ '_nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],
+ '_nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],
+ '_nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],
+ '_nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],
+ '_nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],
+ '_nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],
+ '_nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading Access Log configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ loading: false,
+ loaded: true,
+ });
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ let enabled = false;
+ let buffering = false;
+ let level_val = parseInt(attrs['nsslapd-accesslog-level'][0]);
+ let loglevel = {};
+
+ if (attrs['nsslapd-accesslog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+ if (attrs['nsslapd-accesslog-logbuffering'][0] == "on") {
+ buffering = true;
+ }
+ for (let level of accesslog_levels) {
+ if (level & level_val) {
+ loglevel[level.toString()] = true;
+ } else {
+ loglevel[level.toString()] = false;
+ }
+ }
+
+ this.setState({
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-accesslog': attrs['nsslapd-accesslog'][0],
+ 'nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],
+ 'accesslevel-4': loglevel['4'],
+ 'accesslevel-256': loglevel['256'],
+ 'accesslevel-512': loglevel['512'],
+ 'nsslapd-accesslog-logbuffering': buffering,
+ 'nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],
+ 'nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],
+ 'nsslapd-accesslog-logging-enabled': enabled,
+ 'nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],
+ 'nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],
+ 'nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],
+ 'nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],
+ 'nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],
+ 'nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],
+ 'nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],
+ 'nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],
+ 'nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-accesslog': attrs['nsslapd-accesslog'][0],
+ '_nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],
+ '_accesslevel-4': loglevel['4'],
+ '_accesslevel-256': loglevel['256'],
+ '_accesslevel-512': loglevel['512'],
+ '_nsslapd-accesslog-logbuffering': buffering,
+ '_nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],
+ '_nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],
+ '_nsslapd-accesslog-logging-enabled': enabled,
+ '_nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],
+ '_nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],
+ '_nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],
+ '_nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],
+ '_nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],
+ '_nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],
+ '_nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],
+ '_nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],
+ '_nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],
+ });
+ }
+
+ render() {
+ let body =
+ <div className="ds-margin-top-lg">
+ <TabContainer id="access-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />
+ </NavItem>
+ </Nav>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form>
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-accesslog-logging-enabled).">
+ <Col sm={3}>
+ <Checkbox
+ id="nsslapd-accesslog-logging-enabled"
+ defaultChecked={this.state['nsslapd-accesslog-logging-enabled']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ >
+ Enable Access Logging
+ </Checkbox>
+ </Col>
+ </Row>
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-accesslog).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Access Log Location
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-accesslog"
+ type="text"
+ value={this.state['nsslapd-accesslog']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Disable access log buffering for faster troubleshooting, but this will impact server performance (nsslapd-accesslog-logbuffering).">
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ id="nsslapd-accesslog-logbuffering"
+ defaultChecked={this.state['nsslapd-accesslog-logbuffering']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ >
+ Access Log Buffering Enabled
+ </Checkbox>
+ </Col>
+ </Row>
+ <table className="table table-striped table-bordered table-hover ds-loglevel-table ds-margin-top-lg" id="accesslog-level-table">
+ <thead>
+ <tr>
+ <th className="ds-table-checkbox" />
+ <th>Logging Level</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="accesslevel-256"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['accesslevel-256']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Default Logging
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="accesslevel-4"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['accesslevel-4']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Internal Operations
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="accesslevel-512"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['accesslevel-512']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Entry Access and Referrals
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <Button
+ disabled={this.state.saveSettingsDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("settings");
+ }}
+ >
+ Save Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={2}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-accesslog-maxlogsperdir).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Number Of Logs
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-maxlogsperdir"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-maxlogsperdir']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Log Size (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-maxlogsize"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-maxlogsize']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-accesslog-logrotationtime).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Create New Log Every...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logrotationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-logrotationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-accesslog-logrotationtimeunit"
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ value={this.state['nsslapd-accesslog-logrotationtimeunit']}
+ >
+ <option>minute</option>
+ <option>hour</option>
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-accesslog-logrotationsynchour).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Hour
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logrotationsynchour"
+ type="number"
+ min="0"
+ max="23"
+ value={this.state['nsslapd-accesslog-logrotationsynchour']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-accesslog-logrotationsyncmin).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Minute
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logrotationsyncmin"
+ type="number"
+ min="0"
+ max="59"
+ value={this.state['nsslapd-accesslog-logrotationsyncmin']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveRotationDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("rotation");
+ }}
+ >
+ Save Rotation Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={3}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-accesslog-logmaxdiskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Total Log Archive Exceeds (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logmaxdiskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-logmaxdiskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-accesslog-logminfreediskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Free Disk Space (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logminfreediskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-logminfreediskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-accesslog-logexpirationtime).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Log File is Older Than...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-accesslog-logexpirationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-accesslog-logexpirationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-accesslog-logexpirationtimeunit"
+ value={this.state['nsslapd-accesslog-logexpirationtimeunit']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ >
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("exp");
+ }}
+ >
+ Save Deletion Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ if (this.state.loading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div id="server-accesslog-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ Access Log Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the Access Log settings"
+ onClick={this.reloadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerAccessLog.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerAccessLog.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/auditLog.jsx b/src/cockpit/389-console/src/lib/server/auditLog.jsx
new file mode 100644
index 0000000..88ba26a
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/auditLog.jsx
@@ -0,0 +1,623 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+
+const settings_attrs = [
+ 'nsslapd-auditlog',
+ 'nsslapd-auditlog-logging-enabled',
+];
+
+const rotation_attrs = [
+ 'nsslapd-auditlog-logrotationsync-enabled',
+ 'nsslapd-auditlog-logrotationsynchour',
+ 'nsslapd-auditlog-logrotationsyncmin',
+ 'nsslapd-auditlog-logrotationtime',
+ 'nsslapd-auditlog-logrotationtimeunit',
+ 'nsslapd-auditlog-maxlogsize',
+ 'nsslapd-auditlog-maxlogsperdir',
+];
+
+const exp_attrs = [
+ 'nsslapd-auditlog-logexpirationtime',
+ 'nsslapd-auditlog-logexpirationtimeunit',
+ 'nsslapd-auditlog-logmaxdiskspace',
+ 'nsslapd-auditlog-logminfreediskspace',
+];
+
+export class ServerAuditLog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ attrs: this.props.attrs,
+ };
+
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e, nav_tab) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableBtnName = "";
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ disableBtnName = "saveSettingsDisabled";
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ disableBtnName = "saveRotationDisabled";
+ } else {
+ config_attrs = exp_attrs;
+ disableBtnName = "saveExpDisabled";
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ for (let config_attr of config_attrs) {
+ if (attr == config_attr && this.state['_' + config_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let config_attr of config_attrs) {
+ if (attr != config_attr && this.state['_' + config_attr] != this.state[config_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ [disableBtnName]: disableSaveBtn,
+ });
+ }
+
+ saveConfig(nav_tab) {
+ this.setState({
+ loading: true
+ });
+
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ } else {
+ config_attrs = exp_attrs;
+ }
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of config_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveConfig", "Saving audit log settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "success",
+ "Successfully updated Audit Log settings"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "error",
+ `Error saving Audit Log settings - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ let enabled = false;
+
+ if (attrs['nsslapd-auditlog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+
+ this.setState({
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-auditlog': attrs['nsslapd-auditlog'][0],
+ 'nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],
+ 'nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],
+ 'nsslapd-auditlog-logging-enabled': enabled,
+ 'nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],
+ 'nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],
+ 'nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],
+ 'nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],
+ 'nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],
+ 'nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],
+ 'nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],
+ 'nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],
+ 'nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-auditlog': attrs['nsslapd-auditlog'][0],
+ '_nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],
+ '_nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],
+ '_nsslapd-auditlog-logging-enabled': enabled,
+ '_nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],
+ '_nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],
+ '_nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],
+ '_nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],
+ '_nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],
+ '_nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],
+ '_nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],
+ '_nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],
+ '_nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],
+ });
+ }
+
+ reloadConfig() {
+ this.setState({
+ loading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("reloadConfig", "load Audit Log configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let enabled = false;
+
+ if (attrs['nsslapd-auditlog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+
+ this.setState(() => (
+ {
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-auditlog': attrs['nsslapd-auditlog'][0],
+ 'nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],
+ 'nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],
+ 'nsslapd-auditlog-logging-enabled': enabled,
+ 'nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],
+ 'nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],
+ 'nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],
+ 'nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],
+ 'nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],
+ 'nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],
+ 'nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],
+ 'nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],
+ 'nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-auditlog': attrs['nsslapd-auditlog'][0],
+ '_nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],
+ '_nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],
+ '_nsslapd-auditlog-logging-enabled': enabled,
+ '_nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],
+ '_nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],
+ '_nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],
+ '_nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],
+ '_nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],
+ '_nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],
+ '_nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],
+ '_nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],
+ '_nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading Audit Log configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ loading: false,
+ loaded: true,
+ });
+ });
+ }
+
+ render() {
+ let body =
+ <div className="ds-margin-top-lg">
+ <TabContainer id="audit-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />
+ </NavItem>
+ </Nav>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form>
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditlog-logging-enabled).">
+ <Col sm={3}>
+ <Checkbox
+ id="nsslapd-auditlog-logging-enabled"
+ defaultChecked={this.state['nsslapd-auditlog-logging-enabled']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ >
+ Enable Audit Logging
+ </Checkbox>
+ </Col>
+ </Row>
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditlog).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Audit Log Location
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-auditlog"
+ type="text"
+ value={this.state['nsslapd-auditlog']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ />
+ </Col>
+ </Row>
+ </div>
+ <Button
+ disabled={this.state.saveSettingsDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("settings");
+ }}
+ >
+ Save Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={2}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-auditlog-maxlogsperdir).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Number Of Logs
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-maxlogsperdir"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-maxlogsperdir']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-auditlog-maxlogsize).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Log Size (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-maxlogsize"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-maxlogsize']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-auditlog-logrotationtime).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Create New Log Every...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logrotationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-logrotationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-auditlog-logrotationtimeunit"
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ value={this.state['nsslapd-auditlog-logrotationtimeunit']}
+ >
+ <option>minute</option>
+ <option>hour</option>
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-auditlog-logrotationsynchour).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Hour
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logrotationsynchour"
+ type="number"
+ min="0"
+ max="23"
+ value={this.state['nsslapd-auditlog-logrotationsynchour']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-auditlog-logrotationsyncmin).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Minute
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logrotationsyncmin"
+ type="number"
+ min="0"
+ max="59"
+ value={this.state['nsslapd-auditlog-logrotationsyncmin']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveRotationDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("rotation");
+ }}
+ >
+ Save Rotation Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={3}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditlog-logmaxdiskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Total Log Archive Exceeds (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logmaxdiskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-logmaxdiskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditlog-logminfreediskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Free Disk Space (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logminfreediskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-logminfreediskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditlog-logexpirationtime).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Log File is Older Than...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditlog-logexpirationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditlog-logexpirationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-auditlog-logexpirationtimeunit"
+ value={this.state['nsslapd-auditlog-logexpirationtimeunit']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ >
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("exp");
+ }}
+ >
+ Save Deletion Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ if (this.state.loading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div id="server-auditlog-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ Audit Log Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the Access Log settings"
+ onClick={this.reloadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerAuditLog.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerAuditLog.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/auditfailLog.jsx b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx
new file mode 100644
index 0000000..e7b88d2
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx
@@ -0,0 +1,621 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+
+const settings_attrs = [
+ 'nsslapd-auditfaillog',
+ 'nsslapd-auditfaillog-logging-enabled',
+];
+
+const rotation_attrs = [
+ 'nsslapd-auditfaillog-logrotationsync-enabled',
+ 'nsslapd-auditfaillog-logrotationsynchour',
+ 'nsslapd-auditfaillog-logrotationsyncmin',
+ 'nsslapd-auditfaillog-logrotationtime',
+ 'nsslapd-auditfaillog-logrotationtimeunit',
+ 'nsslapd-auditfaillog-maxlogsize',
+ 'nsslapd-auditfaillog-maxlogsperdir',
+];
+
+const exp_attrs = [
+ 'nsslapd-auditfaillog-logexpirationtime',
+ 'nsslapd-auditfaillog-logexpirationtimeunit',
+ 'nsslapd-auditfaillog-logmaxdiskspace',
+ 'nsslapd-auditfaillog-logminfreediskspace',
+];
+
+export class ServerAuditFailLog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ attrs: this.props.attrs,
+ };
+
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e, nav_tab) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableBtnName = "";
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ disableBtnName = "saveSettingsDisabled";
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ disableBtnName = "saveRotationDisabled";
+ } else {
+ config_attrs = exp_attrs;
+ disableBtnName = "saveExpDisabled";
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ for (let config_attr of config_attrs) {
+ if (attr == config_attr && this.state['_' + config_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let config_attr of config_attrs) {
+ if (attr != config_attr && this.state['_' + config_attr] != this.state[config_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ [disableBtnName]: disableSaveBtn,
+ });
+ }
+
+ saveConfig(nav_tab) {
+ this.setState({
+ loading: true
+ });
+
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ } else {
+ config_attrs = exp_attrs;
+ }
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of config_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveConfig", "Saving audit log settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "success",
+ "Successfully updated Audit Failure Log settings"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "error",
+ `Error saving Audit Failure Log settings - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadConfig() {
+ this.setState({
+ loading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("loadConfig", "load Audit Failure Log configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let enabled = false;
+
+ if (attrs['nsslapd-auditfaillog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+
+ this.setState(() => (
+ {
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],
+ 'nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],
+ 'nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],
+ 'nsslapd-auditfaillog-logging-enabled': enabled,
+ 'nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],
+ 'nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],
+ 'nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],
+ 'nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],
+ 'nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],
+ 'nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],
+ 'nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],
+ 'nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],
+ 'nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],
+ '_nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],
+ '_nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],
+ '_nsslapd-auditfaillog-logging-enabled': enabled,
+ '_nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],
+ '_nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],
+ '_nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],
+ '_nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],
+ '_nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],
+ '_nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],
+ '_nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],
+ '_nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],
+ '_nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading Audit Failure Log configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ loading: false,
+ });
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ let enabled = false;
+
+ if (attrs['nsslapd-auditfaillog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+
+ this.setState({
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],
+ 'nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],
+ 'nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],
+ 'nsslapd-auditfaillog-logging-enabled': enabled,
+ 'nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],
+ 'nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],
+ 'nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],
+ 'nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],
+ 'nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],
+ 'nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],
+ 'nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],
+ 'nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],
+ 'nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],
+ '_nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],
+ '_nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],
+ '_nsslapd-auditfaillog-logging-enabled': enabled,
+ '_nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],
+ '_nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],
+ '_nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],
+ '_nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],
+ '_nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],
+ '_nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],
+ '_nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],
+ '_nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],
+ '_nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],
+ });
+ }
+
+ render() {
+ let body =
+ <div className="ds-margin-top-lg">
+ <TabContainer id="auditfail-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />
+ </NavItem>
+ </Nav>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form>
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditfaillog-logging-enabled).">
+ <Col sm={3}>
+ <Checkbox
+ id="nsslapd-auditfaillog-logging-enabled"
+ defaultChecked={this.state['nsslapd-auditfaillog-logging-enabled']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ >
+ Enable Audit Failure Logging
+ </Checkbox>
+ </Col>
+ </Row>
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditfaillog).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Audit Failure Log Location
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-auditfaillog"
+ type="text"
+ value={this.state['nsslapd-auditfaillog']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ />
+ </Col>
+ </Row>
+ </div>
+ <Button
+ disabled={this.state.saveSettingsDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("settings");
+ }}
+ >
+ Save Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={2}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-auditfaillog-maxlogsperdir).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Number Of Logs
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-maxlogsperdir"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-maxlogsperdir']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-auditfaillog-maxlogsize).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Log Size (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-maxlogsize"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-maxlogsize']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-auditfaillog-logrotationtime).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Create New Log Every...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logrotationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-logrotationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-auditfaillog-logrotationtimeunit"
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ value={this.state['nsslapd-auditfaillog-logrotationtimeunit']}
+ >
+ <option>minute</option>
+ <option>hour</option>
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-auditfaillog-logrotationsynchour).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Hour
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logrotationsynchour"
+ type="number"
+ min="0"
+ max="23"
+ value={this.state['nsslapd-auditfaillog-logrotationsynchour']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-auditfaillog-logrotationsyncmin).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Minute
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logrotationsyncmin"
+ type="number"
+ min="0"
+ max="59"
+ value={this.state['nsslapd-auditfaillog-logrotationsyncmin']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveRotationDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("rotation");
+ }}
+ >
+ Save Rotation Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={3}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditfaillog-logmaxdiskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Total Log Archive Exceeds (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logmaxdiskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-logmaxdiskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditfaillog-logminfreediskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Free Disk Space (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logminfreediskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-logminfreediskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditfaillog-logexpirationtime).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Log File is Older Than...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-auditfaillog-logexpirationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-auditfaillog-logexpirationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-auditfaillog-logexpirationtimeunit"
+ value={this.state['nsslapd-auditfaillog-logexpirationtimeunit']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ >
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("exp");
+ }}
+ >
+ Save Deletion Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ if (this.state.loading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div id="server-auditfaillog-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ Audit Failure Log Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the Access Log settings"
+ onClick={this.reloadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerAuditFailLog.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerAuditFailLog.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/errorLog.jsx b/src/cockpit/389-console/src/lib/server/errorLog.jsx
new file mode 100644
index 0000000..6416253
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/errorLog.jsx
@@ -0,0 +1,956 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+
+const errorlog_levels = [
+ 1, 2, 4, 8, 16, 32, 64, 128, 2048,
+ 4096, 8192, 32768, 65536, 262144,
+];
+
+const settings_attrs = [
+ 'nsslapd-errorlog',
+ 'nsslapd-errorlog-level',
+ 'nsslapd-errorlog-logging-enabled',
+ 'errorlevel-1',
+ 'errorlevel-2',
+ 'errorlevel-4',
+ 'errorlevel-8',
+ 'errorlevel-16',
+ 'errorlevel-32',
+ 'errorlevel-64',
+ 'errorlevel-128',
+ 'errorlevel-2048',
+ 'errorlevel-4096',
+ 'errorlevel-8192',
+ 'errorlevel-32768',
+ 'errorlevel-65536',
+ 'errorlevel-262144',
+];
+
+const rotation_attrs = [
+ 'nsslapd-errorlog-logrotationsync-enabled',
+ 'nsslapd-errorlog-logrotationsynchour',
+ 'nsslapd-errorlog-logrotationsyncmin',
+ 'nsslapd-errorlog-logrotationtime',
+ 'nsslapd-errorlog-logrotationtimeunit',
+ 'nsslapd-errorlog-maxlogsize',
+ 'nsslapd-errorlog-maxlogsperdir',
+];
+
+const exp_attrs = [
+ 'nsslapd-errorlog-logexpirationtime',
+ 'nsslapd-errorlog-logexpirationtimeunit',
+ 'nsslapd-errorlog-logmaxdiskspace',
+ 'nsslapd-errorlog-logminfreediskspace',
+];
+
+export class ServerErrorLog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ attrs: this.props.attrs,
+ };
+
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e, nav_tab) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableBtnName = "";
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ disableBtnName = "saveSettingsDisabled";
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ disableBtnName = "saveRotationDisabled";
+ } else {
+ config_attrs = exp_attrs;
+ disableBtnName = "saveExpDisabled";
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ for (let config_attr of config_attrs) {
+ if (attr == config_attr && this.state['_' + config_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let config_attr of config_attrs) {
+ if (attr != config_attr && this.state['_' + config_attr] != this.state[config_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ [disableBtnName]: disableSaveBtn,
+ });
+ }
+
+ saveConfig(nav_tab) {
+ let level_change = false;
+ let new_level = 0;
+ this.setState({
+ loading: true
+ });
+
+ let config_attrs = [];
+ if (nav_tab == "settings") {
+ config_attrs = settings_attrs;
+ } else if (nav_tab == "rotation") {
+ config_attrs = rotation_attrs;
+ } else {
+ config_attrs = exp_attrs;
+ }
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of config_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ if (attr.startsWith("errorlevel-")) {
+ level_change = true;
+ continue;
+ }
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ if (level_change) {
+ for (let level of errorlog_levels) {
+ if (this.state['errorlevel-' + level.toString()]) {
+ new_level += level;
+ }
+ }
+ cmd.push("nsslapd-errorlog-level" + "=" + new_level.toString());
+ }
+
+ log_cmd("saveConfig", "Saving error log settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "success",
+ "Successfully updated Error Log settings"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "error",
+ `Error saving Error Log settings - ${errMsg.desc}`
+ );
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ let enabled = false;
+ let level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);
+ let loglevel = {};
+
+ if (attrs['nsslapd-errorlog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+ for (let level of errorlog_levels) {
+ if (level & level_val) {
+ loglevel[level.toString()] = true;
+ } else {
+ loglevel[level.toString()] = false;
+ }
+ }
+
+ this.setState({
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-errorlog': attrs['nsslapd-errorlog'][0],
+ 'nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],
+ 'errorlevel-1': loglevel['1'],
+ 'errorlevel-2': loglevel['2'],
+ 'errorlevel-4': loglevel['4'],
+ 'errorlevel-8': loglevel['8'],
+ 'errorlevel-16': loglevel['16'],
+ 'errorlevel-32': loglevel['32'],
+ 'errorlevel-64': loglevel['64'],
+ 'errorlevel-128': loglevel['128'],
+ 'errorlevel-2048': loglevel['2048'],
+ 'errorlevel-4096': loglevel['4096'],
+ 'errorlevel-8192': loglevel['8192'],
+ 'errorlevel-32768': loglevel['32768'],
+ 'errorlevel-65536': loglevel['65536'],
+ 'errorlevel-262144': loglevel['262144'],
+ 'nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],
+ 'nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],
+ 'nsslapd-errorlog-logging-enabled': enabled,
+ 'nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],
+ 'nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],
+ 'nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],
+ 'nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],
+ 'nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],
+ 'nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],
+ 'nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],
+ 'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
+ 'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-errorlog': attrs['nsslapd-errorlog'][0],
+ '_nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],
+ '_errorlevel-1': loglevel['1'],
+ '_errorlevel-2': loglevel['2'],
+ '_errorlevel-4': loglevel['4'],
+ '_errorlevel-8': loglevel['8'],
+ '_errorlevel-16': loglevel['16'],
+ '_errorlevel-32': loglevel['32'],
+ '_errorlevel-64': loglevel['64'],
+ '_errorlevel-128': loglevel['128'],
+ '_errorlevel-2048': loglevel['2048'],
+ '_errorlevel-4096': loglevel['4096'],
+ '_errorlevel-8192': loglevel['8192'],
+ '_errorlevel-32768': loglevel['32768'],
+ '_errorlevel-65536': loglevel['65536'],
+ '_errorlevel-262144': loglevel['262144'],
+ '_nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],
+ '_nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],
+ '_nsslapd-errorlog-logging-enabled': enabled,
+ '_nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],
+ '_nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],
+ '_nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],
+ '_nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],
+ '_nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],
+ '_nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],
+ '_nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],
+ '_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
+ '_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
+ });
+ }
+
+ reloadConfig() {
+ this.setState({
+ loading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("reloadConfig", "load Error Log configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let enabled = false;
+ let level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);
+ let loglevel = {};
+
+ if (attrs['nsslapd-errorlog-logging-enabled'][0] == "on") {
+ enabled = true;
+ }
+ for (let level of errorlog_levels) {
+ if (level & level_val) {
+ loglevel[level.toString()] = true;
+ } else {
+ loglevel[level.toString()] = false;
+ }
+ }
+
+ this.setState(() => (
+ {
+ loading: false,
+ loaded: true,
+ saveSettingsDisabled: true,
+ saveRotationDisabled: true,
+ saveExpDisabled: true,
+ 'nsslapd-errorlog': attrs['nsslapd-errorlog'][0],
+ 'nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],
+ 'errorlevel-1': loglevel['1'],
+ 'errorlevel-2': loglevel['2'],
+ 'errorlevel-4': loglevel['4'],
+ 'errorlevel-8': loglevel['8'],
+ 'errorlevel-16': loglevel['16'],
+ 'errorlevel-32': loglevel['32'],
+ 'errorlevel-64': loglevel['64'],
+ 'errorlevel-128': loglevel['128'],
+ 'errorlevel-2048': loglevel['2048'],
+ 'errorlevel-4096': loglevel['4096'],
+ 'errorlevel-8192': loglevel['8192'],
+ 'errorlevel-32768': loglevel['32768'],
+ 'errorlevel-65536': loglevel['65536'],
+ 'errorlevel-262144': loglevel['262144'],
+ 'nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],
+ 'nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],
+ 'nsslapd-errorlog-logging-enabled': enabled,
+ 'nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],
+ 'nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],
+ 'nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],
+ 'nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],
+ 'nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],
+ 'nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],
+ 'nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],
+ 'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
+ 'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
+ // Record original values
+ '_nsslapd-errorlog': attrs['nsslapd-errorlog'][0],
+ '_nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],
+ '_errorlevel-1': loglevel['1'],
+ '_errorlevel-2': loglevel['2'],
+ '_errorlevel-4': loglevel['4'],
+ '_errorlevel-8': loglevel['8'],
+ '_errorlevel-16': loglevel['16'],
+ '_errorlevel-32': loglevel['32'],
+ '_errorlevel-64': loglevel['64'],
+ '_errorlevel-128': loglevel['128'],
+ '_errorlevel-2048': loglevel['2048'],
+ '_errorlevel-4096': loglevel['4096'],
+ '_errorlevel-8192': loglevel['8192'],
+ '_errorlevel-32768': loglevel['32768'],
+ '_errorlevel-65536': loglevel['65536'],
+ '_errorlevel-262144': loglevel['262144'],
+ '_nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],
+ '_nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],
+ '_nsslapd-errorlog-logging-enabled': enabled,
+ '_nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],
+ '_nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],
+ '_nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],
+ '_nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],
+ '_nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],
+ '_nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],
+ '_nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],
+ '_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
+ '_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading Error Log configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ loading: false,
+ loaded: true,
+ });
+ });
+ }
+
+ render() {
+ let body =
+ <div className="ds-margin-top-lg">
+ <TabContainer id="error-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />
+ </NavItem>
+ </Nav>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form>
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-errorlog-logging-enabled).">
+ <Col sm={3}>
+ <Checkbox
+ id="nsslapd-errorlog-logging-enabled"
+ defaultChecked={this.state['nsslapd-errorlog-logging-enabled']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ >
+ Enable Error Logging
+ </Checkbox>
+ </Col>
+ </Row>
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Enable access logging (nsslapd-errorlog).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Error Log Location
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-errorlog"
+ type="text"
+ value={this.state['nsslapd-errorlog']}
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ />
+ </Col>
+ </Row>
+ <table className="table table-striped table-bordered table-hover ds-loglevel-table ds-margin-top-lg" id="errorlog-level-table">
+ <thead>
+ <tr>
+ <th className="ds-table-checkbox" />
+ <th>Logging Level</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-1"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-1']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Trace Function Calls
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-2"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-2']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Packet Handling
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-4"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-4']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Heavy Trace Output
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-8"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-8']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Connection Management
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-16"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-16']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Packets Sent & Received
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-32"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-32']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Search Filter Processing
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-64"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-64']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Config File Processing
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-128"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-128']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Access Control List Processing
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-256"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-2048']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Log Entry Parsing
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-4096"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-4096']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Housekeeping
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-8192"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-8192']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Replication
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-32768"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-32768']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Entry Cache
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-65536"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-65536']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Plugin
+ </td>
+ </tr>
+ <tr>
+ <td className="ds-table-checkbox">
+ <input
+ id="errorlevel-262144"
+ onChange={(e) => {
+ this.handleChange(e, "settings");
+ }}
+ checked={this.state['errorlevel-262144']}
+ type="checkbox"
+ />
+ </td>
+ <td className="ds-left-align">
+ Access Control Summary
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <Button
+ disabled={this.state.saveSettingsDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("settings");
+ }}
+ >
+ Save Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={2}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-errorlog-maxlogsperdir).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Number Of Logs
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-maxlogsperdir"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-maxlogsperdir']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Maximum Log Size (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-maxlogsize"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-maxlogsize']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-errorlog-logrotationtime).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Create New Log Every...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logrotationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-logrotationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-errorlog-logrotationtimeunit"
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ value={this.state['nsslapd-errorlog-logrotationtimeunit']}
+ >
+ <option>minute</option>
+ <option>hour</option>
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-errorlog-logrotationsynchour).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Hour
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logrotationsynchour"
+ type="number"
+ min="0"
+ max="23"
+ value={this.state['nsslapd-errorlog-logrotationsynchour']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-errorlog-logrotationsyncmin).">
+ <Col componentClass={ControlLabel} sm={3}>
+ Minute
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logrotationsyncmin"
+ type="number"
+ min="0"
+ max="59"
+ value={this.state['nsslapd-errorlog-logrotationsyncmin']}
+ onChange={(e) => {
+ this.handleChange(e, "rotation");
+ }}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveRotationDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("rotation");
+ }}
+ >
+ Save Rotation Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={3}>
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-errorlog-logmaxdiskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Total Log Archive Exceeds (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logmaxdiskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-logmaxdiskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-errorlog-logminfreediskspace).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Free Disk Space (in MB)
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logminfreediskspace"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-logminfreediskspace']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-errorlog-logexpirationtime).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Log File is Older Than...
+ </Col>
+ <Col sm={2}>
+ <FormControl
+ id="nsslapd-errorlog-logexpirationtime"
+ type="number"
+ min="1"
+ max="2147483647"
+ value={this.state['nsslapd-errorlog-logexpirationtime']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ />
+ </Col>
+ <Col sm={2}>
+ <select
+ className="btn btn-default dropdown"
+ id="nsslapd-errorlog-logexpirationtimeunit"
+ value={this.state['nsslapd-errorlog-logexpirationtimeunit']}
+ onChange={(e) => {
+ this.handleChange(e, "exp");
+ }}
+ >
+ <option>day</option>
+ <option>week</option>
+ <option>month</option>
+ </select>
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.saveExpDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={() => {
+ this.saveConfig("exp");
+ }}
+ >
+ Save Deletion Settings
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ if (this.state.loading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div id="server-errorlog-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ Error Log Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the Access Log settings"
+ onClick={this.reloadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerErrorLog.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerErrorLog.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx b/src/cockpit/389-console/src/lib/server/ldapi.jsx
new file mode 100644
index 0000000..904689f
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/ldapi.jsx
@@ -0,0 +1,331 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Row,
+ Spinner,
+} from "patternfly-react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+
+const ldapi_attrs = [
+ 'nsslapd-ldapimaptoentries',
+ 'nsslapd-ldapifilepath',
+ 'nsslapd-ldapimaprootdn',
+ 'nsslapd-ldapientrysearchbase',
+ 'nsslapd-ldapigidnumbertype',
+ 'nsslapd-ldapiuidnumbertype',
+];
+
+export class ServerLDAPI extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ notifications: [],
+ saveDisabled: true,
+ attrs: this.props.attrs,
+ // settings
+
+ };
+
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let ldapi_attr of ldapi_attrs) {
+ if (attr == ldapi_attr && this.state['_' + ldapi_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let ldapi_attr of ldapi_attrs) {
+ if (attr != ldapi_attr && this.state['_' + ldapi_attr] != this.state[ldapi_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ this.setState({
+ [attr]: value,
+ saveDisabled: disableSaveBtn,
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ let mapToEntries = false;
+
+ if ('nsslapd-ldapimaptoentries' in attrs) {
+ if (attrs['nsslapd-ldapimaptoentries'][0] == "on") {
+ mapToEntries = true;
+ }
+ }
+ this.setState({
+ loading: false,
+ loaded: true,
+ saveDisabled: true,
+ 'nsslapd-ldapimaptoentries': mapToEntries,
+ 'nsslapd-ldapifilepath': attrs['nsslapd-ldapifilepath'][0],
+ 'nsslapd-ldapimaprootdn': attrs['nsslapd-ldapimaprootdn'][0],
+ 'nsslapd-ldapientrysearchbase': attrs['nsslapd-ldapientrysearchbase'][0],
+ 'nsslapd-ldapigidnumbertype': attrs['nsslapd-ldapigidnumbertype'][0],
+ 'nsslapd-ldapiuidnumbertype': attrs['nsslapd-ldapiuidnumbertype'][0],
+ // Record original values
+ '_nsslapd-ldapimaptoentries': mapToEntries,
+ '_nsslapd-ldapimaprootdn': attrs['nsslapd-ldapimaprootdn'][0],
+ '_nsslapd-ldapifilepath': attrs['nsslapd-ldapifilepath'][0],
+ '_nsslapd-ldapientrysearchbase': attrs['nsslapd-ldapientrysearchbase'][0],
+ '_nsslapd-ldapigidnumbertype': attrs['nsslapd-ldapigidnumbertype'][0],
+ '_nsslapd-ldapiuidnumbertype': attrs['nsslapd-ldapiuidnumbertype'][0],
+ });
+ }
+
+ saveConfig() {
+ this.setState({
+ loading: true
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ for (let attr of ldapi_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveConfig", "Saving LDAPI settings", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "success",
+ "Successfully updated LDAPI configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig();
+ this.setState({
+ loading: false
+ });
+ this.addNotification(
+ "error",
+ `Error updating LDAPI configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ render() {
+ let mapUserAttrs = "";
+
+ if (this.state['nsslapd-ldapimaptoentries']) {
+ mapUserAttrs =
+ <div>
+ <Row title="The Directory Server attribute to map system UIDs to user entries (nsslapd-ldapiuidnumbertype)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ LDAPI UID Number Attribute
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-ldapiuidnumbertype"
+ type="text"
+ value={this.state['nsslapd-ldapiuidnumbertype']}
+ onChange={this.handleChange}
+ placeholder="e.g. uidNumber"
+ />
+ </Col>
+ </Row>
+ <Row title="The Directory Server attribute to map system GUIDs to user entries (nsslapd-ldapigidnumbertype)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ LDAPI GID Number Attribute
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-ldapigidnumbertype"
+ type="text"
+ value={this.state['nsslapd-ldapigidnumbertype']}
+ onChange={this.handleChange}
+ placeholder="e.g. gidNumber"
+ />
+ </Col>
+ </Row>
+ <Row title="The subtree to search for user entries to use for autobind. (nsslapd-ldapientrysearchbase)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ LDAPI Entry Search Base
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-ldapientrysearchbase"
+ type="text"
+ value={this.state['nsslapd-ldapientrysearchbase']}
+ onChange={this.handleChange}
+
+ />
+ </Col>
+ </Row>
+ </div>;
+ }
+
+ let body =
+ <div>
+ <Form horizontal>
+ <Row title="The Unix socket file (nsslapd-ldapifilepath). The UI requires this exact path so it is a read-only setting." className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={3}>
+ LDAPI Socket File Path
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-ldapifilepath"
+ type="text"
+ value={this.state['nsslapd-ldapifilepath']}
+ disabled
+ />
+ </Col>
+ </Row>
+ <Row title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn). The UI requires this to be set to the current root DN so it is a read-only setting" className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ DN to map "root" To
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-ldapimaprootdn"
+ type="text"
+ value={this.state['nsslapd-ldapimaprootdn']}
+ disabled
+ />
+ </Col>
+ </Row>
+ <Row
+ title="Map regular system users to Directory Server entries (nsslapd-ldapimaptoentries)."
+ className="ds-margin-top"
+ >
+ <Col componentClass={ControlLabel} sm={3}>
+ <Checkbox
+ checked={this.state['nsslapd-ldapimaptoentries']}
+ id="nsslapd-ldapimaptoentries"
+ onChange={this.handleChange} className="ds-margin-left-sm"
+ >
+ Map System Users to Database Entries
+ </Checkbox>
+ </Col>
+ </Row>
+ {mapUserAttrs}
+ <Button
+ disabled={this.state.saveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={this.saveConfig}
+ >
+ Save Settings
+ </Button>
+ </Form>
+ </div>;
+
+ if (this.state.lading || !this.state.loaded) {
+ body = <Spinner loading size="md" />;
+ }
+
+ return (
+ <div id="server-ldapi-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <Row>
+ <Col sm={5}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ LDAPI & AutoBind Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the LDAPI settings"
+ onClick={this.loadConfig}
+ disabled={this.state.loading}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerLDAPI.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerLDAPI.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/sasl.jsx b/src/cockpit/389-console/src/lib/server/sasl.jsx
new file mode 100644
index 0000000..7dff66f
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/sasl.jsx
@@ -0,0 +1,790 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController, DoubleConfirmModal } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Row,
+ Spinner,
+} from "patternfly-react";
+import { SASLTable } from "./serverTables.jsx";
+import { SASLMappingModal } from "./serverModals.jsx";
+import { Typeahead } from "react-bootstrap-typeahead";
+import "../../css/ds.css";
+
+export class ServerSASL extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ configLoading: false,
+ tableLoading: false,
+ loaded: false,
+ activeKey: 1,
+ errObj: {},
+ notifications: [],
+ saveDisabled: true,
+ supportedMechs: [],
+
+ // Main settings
+ allowedMechs: [],
+ mappingFallback: false,
+ maxBufSize: "",
+ // Mapping modal
+ showMapping: false,
+ saveMappingDisabled: true,
+ testRegexDisabled: true,
+ testBtnDisabled: true,
+ saslMapName: "",
+ saslMapRegex: "",
+ saslTestText: "",
+ saslBase: "",
+ saslFilter: "",
+ saslPriority: "100",
+ saslModalType: "Create",
+ saslErrObj: {},
+ showConfirmDelete: false,
+ modalChecked: false,
+ };
+
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleModalAddChange = this.handleModalAddChange.bind(this);
+ this.handleTestRegex = this.handleTestRegex.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.loadMechs = this.loadMechs.bind(this);
+ this.loadSASLMappings = this.loadSASLMappings.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ this.showCreateMapping = this.showCreateMapping.bind(this);
+ this.showEditMapping = this.showEditMapping.bind(this);
+ this.closeMapping = this.closeMapping.bind(this);
+ this.showConfirmDelete = this.showConfirmDelete.bind(this);
+ this.closeConfirmDelete = this.closeConfirmDelete.bind(this);
+ this.createMapping = this.createMapping.bind(this);
+ this.editMapping = this.editMapping.bind(this);
+ this.deleteMapping = this.deleteMapping.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleTestRegex() {
+ let test_string = this.state.saslTestText;
+ let regex = this.state.saslMapRegex;
+ let cleaned_regex = regex.replace(/\\\(/g, '(').replace(/\\\)/g, ')');
+ let sasl_regex = RegExp(cleaned_regex);
+ if (sasl_regex.test(test_string)) {
+ this.addNotification(
+ "success",
+ "The test string matches the Regular Expression"
+ );
+ } else {
+ this.addNotification(
+ "warning",
+ "The test string does not match the Regular Expression"
+ );
+ }
+ }
+
+ handleChange(e) {
+ let attr = "";
+ let value = "";
+ let isArray = false;
+ let chkBox = false;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ // Could be a typeahead change, check if "e" is an Array
+ if (Array.isArray(e)) {
+ isArray = true;
+ attr = "allowedMechs";
+ value = e;
+ } else {
+ value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ attr = e.target.id;
+ if (e.target.type === 'checkbox') {
+ chkBox = true;
+ }
+ }
+
+ // Check if a setting was changed, if so enable the save button
+ if (attr == 'mappingFallback' && this.state._mappingFallback != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'maxBufSize' && this.state._maxBufSize != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'allowedMechs' && this.state._allowedMechs.join(' ') != value.join(' ')) {
+ if (this.state._allowedMechs.length > value.length) {
+ // The way allow mechanisms work if that once you set it initially
+ // you can't edit it without removing all the current mecahisms. So
+ // if we remove one, just remove them all and make the user start over.
+ // MARK THIS DOES NOT WORK
+ value = [];
+ }
+ disableSaveBtn = false;
+ }
+
+ // Now check for differences in values that we did not touch
+ if (attr != 'mappingFallback' && this.state._mappingFallback != this.state.mappingFallback) {
+ disableSaveBtn = false;
+ } else if (attr != 'maxBufSize' && this.state._maxBufSize != this.state.maxBufSize) {
+ disableSaveBtn = false;
+ } else if (attr != 'allowedMechs' && this.state._allowedMechs.join(' ') != this.state.allowedMechs.join(' ')) {
+ disableSaveBtn = false;
+ }
+
+ if (!isArray && !chkBox && value == "") {
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ saveDisabled: disableSaveBtn,
+ errObj: errObj,
+ });
+ }
+
+ handleModalAddChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableRegexTestBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ // Check if a setting was changed, if so enable the save button
+ if (attr == 'saslMapName' && value != "") {
+ disableSaveBtn = false;
+ } else if (attr == 'saslMapRegex' && value != "") {
+ disableSaveBtn = false;
+ } else if (attr == 'saslBase' && value != "") {
+ disableSaveBtn = false;
+ } else if (attr == 'saslFilter' && value != "") {
+ disableSaveBtn = false;
+ }
+ if (!disableSaveBtn) {
+ // Make sure every other field is set
+ if (attr != 'saslMapName' && this.state.saslMapName == "") {
+ disableSaveBtn = true;
+ }
+ if (attr != 'saslMapRegex' && this.state.saslMapRegex == "") {
+ disableSaveBtn = true;
+ }
+ if (attr != 'saslBase' && this.state.saslBase == "") {
+ disableSaveBtn = true;
+ }
+ if (attr != 'saslFilter' && this.state.saslFilter == "") {
+ disableSaveBtn = true;
+ }
+ }
+
+ // Handle TEst Text filed and buttons
+ if (attr == 'saslTestText' && value != "" && this.state.saslMapRegex != "") {
+ disableRegexTestBtn = false;
+ }
+ if (attr != 'saslTestText' && this.state.saslMapRegex != "" && this.state.saslTestText != "") {
+ disableRegexTestBtn = false;
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ saveMappingDisabled: disableSaveBtn,
+ testBtnDisabled: disableRegexTestBtn,
+ errObj: errObj,
+ });
+ }
+
+ handleModalChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let disableRegexTestBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ // Check if a setting was changed, if so enable the save button
+ if (attr == 'saslMapName' && this.state._saslMapName != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'saslMapRegex' && this.state._saslMapRegex != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'saslBase' && this.state._saslBase != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'saslFilter' && this.state._saslFilter != value) {
+ disableSaveBtn = false;
+ } else if (attr == 'saslPriority' && this.state._saslPriority != value) {
+ disableSaveBtn = false;
+ }
+
+ // Now check for differences in values that we did not touch
+ if (attr != 'saslMapName' && this.state._saslMapName != this.state.saslMapName) {
+ disableSaveBtn = false;
+ } else if (attr != 'saslMapRegex' && this.state._saslMapRegex != this.state.saslMapRegex) {
+ disableSaveBtn = false;
+ } else if (attr != 'saslBase' && this.state._saslBase != this.state.saslBase) {
+ disableSaveBtn = false;
+ } else if (attr != 'saslFilter' && this.state._saslFilter != this.state.saslFilter) {
+ disableSaveBtn = false;
+ } else if (attr != 'saslPriority' && this.state._saslPriority != this.state.saslPriority) {
+ disableSaveBtn = false;
+ }
+
+ // Handle TEst Text filed and buttons
+ if (attr == 'saslTestText' && value != "" && this.state.saslMapRegex != "") {
+ disableRegexTestBtn = false;
+ }
+ if (attr != 'saslTestText' && this.state.saslMapRegex != "" && this.state.saslTestText != "") {
+ disableRegexTestBtn = false;
+ }
+
+ if (value == "" && attr != "saslTestText") {
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ saveMappingDisabled: disableSaveBtn,
+ testBtnDisabled: disableRegexTestBtn,
+ errObj: errObj,
+ });
+ }
+
+ loadConfig() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", 'get'
+ ];
+ log_cmd("loadConfig", "Get SASL settings", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let allowedMechsVal = attrs['nsslapd-allowed-sasl-mechanisms'][0];
+ let allowedMechs = [];
+ let fallback = false;
+
+ if (attrs['nsslapd-sasl-mapping-fallback'][0] == "on") {
+ fallback = true;
+ }
+ if (allowedMechsVal != "") {
+ // Could be space or comma separated
+ if (allowedMechsVal.indexOf(',') > -1) {
+ allowedMechsVal = allowedMechsVal.trim();
+ allowedMechs = allowedMechsVal.split(',');
+ } else {
+ allowedMechs = allowedMechsVal.split();
+ }
+ }
+
+ this.setState({
+ maxBufSize: attrs['nsslapd-sasl-max-buffer-size'][0],
+ allowedMechs: allowedMechs,
+ mappingFallback: fallback,
+ saveDisabled: true,
+ // Store original values
+ _maxBufSize: attrs['nsslapd-sasl-max-buffer-size'][0],
+ _allowedMechs: allowedMechs,
+ _mappingFallback: fallback,
+ }, this.loadMechs());
+ });
+ }
+
+ loadMechs() {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "sasl", 'get-mechs'
+ ];
+ log_cmd("loadMechs", "Get supported SASL mechanisms", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ this.setState({
+ supportedMechs: config.items
+ }, this.loadSASLMappings());
+ });
+ }
+
+ loadSASLMappings() {
+ let cmd = ["dsconf", '-j', this.props.serverId, 'sasl', 'list', '--details'];
+ log_cmd('get_and_set_sasl', 'Get SASL mappings', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let saslMapObj = JSON.parse(content);
+ let mappings = [];
+ for (let mapping of saslMapObj['items']) {
+ if (!mapping['attrs'].hasOwnProperty('nssaslmappriority')) {
+ mapping['attrs'].nssaslmappriority = ['100'];
+ }
+ mappings.push(mapping['attrs']);
+ }
+ this.setState({
+ mappings: mappings,
+ loaded: true,
+ tableLoading: false,
+ configLoading: false,
+ });
+ });
+ }
+
+ showCreateMapping() {
+ this.setState({
+ showMappingModal: true,
+ saveMappingDisabled: true,
+ testRegexDisabled: true,
+ saslModalType: "Create",
+ saslMapName: "",
+ saslMapRegex: "",
+ saslTestText: "",
+ saslBase: "",
+ saslFilter: "",
+ saslPriority: "100",
+ saslErrObj: {},
+ });
+ }
+
+ closeMapping() {
+ this.setState({
+ showMappingModal: false,
+ });
+ }
+
+ showEditMapping(name, regex, base, filter, priority) {
+ this.setState({
+ showMappingModal: true,
+ saveMappingDisabled: true,
+ testRegexDisabled: true,
+ saslModalType: "Edit",
+ saslMapName: name,
+ saslMapRegex: regex,
+ saslTestText: "",
+ saslBase: base,
+ saslFilter: filter,
+ saslPriority: priority,
+ // Note original values
+ _saslMapName: name,
+ _saslMapRegex: regex,
+ _saslTestText: "",
+ _saslBase: base,
+ _saslFilter: filter,
+ _saslPriority: priority,
+ saslErrObj: {},
+ });
+ }
+
+ showConfirmDelete(name) {
+ this.setState({
+ saslMapName: name,
+ modalChecked: false,
+ showConfirmDelete: true,
+ });
+ }
+
+ closeConfirmDelete() {
+ this.setState({
+ modalChecked: false,
+ showConfirmDelete: false,
+ });
+ }
+
+ createMapping() {
+ this.setState({
+ tableLoading: true,
+ });
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId,
+ 'sasl', 'create',
+ '--cn=' + this.state.saslMapName,
+ '--nsSaslMapFilterTemplate=' + this.state.saslFilter,
+ '--nsSaslMapRegexString=' + this.state.saslMapRegex,
+ '--nsSaslMapBaseDNTemplate=' + this.state.saslBase,
+ '--nsSaslMapPriority=' + this.state.saslPriority
+ ];
+
+ log_cmd("createMapping", "Create sasl mapping", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadConfig();
+ this.closeMapping();
+ this.addNotification(
+ "success",
+ "Successfully create new SASL Mapping"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig();
+ this.addNotification(
+ "error",
+ `Error creating new SASL Mapping - ${errMsg.desc}`
+ );
+ });
+ }
+
+ editMapping(name) {
+ // Start spinning
+ let new_mappings = this.state.mappings;
+ for (let saslMap of new_mappings) {
+ if (saslMap.cn[0] == name) {
+ saslMap.nssaslmapregexstring = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapregexstring[0]} loading size="sm" />];
+ saslMap.nssaslmapbasedntemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapbasedntemplate[0]} loading size="sm" />];
+ saslMap.nssaslmapfiltertemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapfiltertemplate[0]} loading size="sm" />];
+ saslMap.nssaslmappriority = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmappriority[0]} loading size="sm" />];
+ }
+ }
+
+ this.setState({
+ mappings: new_mappings
+ });
+
+ // Delete and create
+ let delete_cmd = [
+ 'dsconf', '-j', this.props.serverId,
+ 'sasl', 'delete', this.state._saslMapName
+ ];
+ let create_cmd = [
+ 'dsconf', '-j', this.props.serverId,
+ 'sasl', 'create',
+ '--cn=' + this.state.saslMapName,
+ '--nsSaslMapFilterTemplate=' + this.state.saslFilter,
+ '--nsSaslMapRegexString=' + this.state.saslMapRegex,
+ '--nsSaslMapBaseDNTemplate=' + this.state.saslBase,
+ '--nsSaslMapPriority=' + this.state.saslPriority
+ ];
+
+ log_cmd("editMapping", "deleting sasl mapping", delete_cmd);
+ cockpit
+ .spawn(delete_cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ log_cmd("editMapping", "Create new sasl mapping", create_cmd);
+ cockpit
+ .spawn(create_cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.closeMapping();
+ this.loadConfig();
+ this.addNotification(
+ "success",
+ "Successfully updated SASL Mapping"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.closeMapping();
+ this.loadConfig();
+ this.addNotification(
+ "error",
+ `Error updating SASL Mapping - ${errMsg.desc}`
+ );
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig();
+ this.closeMapping();
+ this.addNotification(
+ "error",
+ `Error replacing SASL Mapping - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deleteMapping() {
+ // Start spinning
+ let new_mappings = this.state.mappings;
+ for (let saslMap of new_mappings) {
+ if (saslMap.cn[0] == this.state.saslMapName) {
+ saslMap.nssaslmapregexstring = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapregexstring[0]} loading size="sm" />];
+ saslMap.nssaslmapbasedntemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapbasedntemplate[0]} loading size="sm" />];
+ saslMap.nssaslmapfiltertemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapfiltertemplate[0]} loading size="sm" />];
+ saslMap.nssaslmappriority = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmappriority[0]} loading size="sm" />];
+ }
+ }
+ this.setState({
+ mappings: new_mappings
+ });
+
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId,
+ 'sasl', 'delete', this.state.saslMapName
+ ];
+ log_cmd("deleteMapping", "Delete sasl mapping", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.closeConfirmDelete();
+ this.loadConfig();
+ this.addNotification(
+ "success",
+ "Successfully deleted SASL Mapping"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig();
+ this.closeConfirmDelete();
+ this.addNotification(
+ "error",
+ `Error deleting SASL Mapping - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveConfig() {
+ // Start spinning
+ this.setState({
+ configLoading: true,
+ });
+
+ // Build up the command list
+ let cmd = [
+ 'dsconf', '-j', this.props.serverId, 'config', 'replace'
+ ];
+
+ let mech_str_new = this.state.allowedMechs.join(' ');
+ let mech_str_orig = this.state._allowedMechs.join(' ');
+ if (mech_str_orig != mech_str_new) {
+ cmd.push("nsslapd-allowed-sasl-mechanisms=" + mech_str_new);
+ }
+ if (this.state._mappingFallback != this.state.mappingFallback) {
+ let value = "off";
+ if (this.state.mappingFallback) {
+ value = "on";
+ }
+ cmd.push("nsslapd-sasl-mapping-fallback=" + value);
+ }
+ if (this.state._maxBufSize != this.state.maxBufSize) {
+ cmd.push("nsslapd-sasl-max-buffer-size=" + this.state.maxBufSize);
+ }
+
+ log_cmd("saveConfig", "Applying SASL config change", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadConfig();
+ this.addNotification(
+ "success",
+ "Successfully updated SASL configuration. These " +
+ "changes require the server to be restarted to take effect."
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig();
+ this.addNotification(
+ "error",
+ `Error updating SASL configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ render() {
+ let configSpinner = "";
+ let tableSpinner = " ";
+ let body = "";
+ if (this.state.tableLoading) {
+ tableSpinner = <Spinner loading size="sm" />;
+ }
+ if (this.state.configLoading) {
+ configSpinner = <Spinner loading size="md" />;
+ }
+
+ if (!this.state.loaded) {
+ body =
+ <div className="ds-loading-spinner ds-margin-top ds-center">
+ <h4>Loading SASL configuration ...</h4>
+ <Spinner className="ds-margin-top" loading size="md" />
+ </div>;
+ } else {
+ body =
+ <div className="ds-margin-left-sm">
+ <Row>
+ <Col sm={3} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg">
+ SASL Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh SASL configuration"
+ onClick={() => {
+ this.loadConfig();
+ }}
+ />
+ </ControlLabel>
+ </Col>
+ <Col sm={1} className="ds-margin-top-lg">
+ {configSpinner}
+ </Col>
+ </Row>
+ <hr />
+ <Form>
+ <Row title="The maximum SASL buffer size in bytes (nsslapd-sasl-max-buffer-size)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={3}>
+ Max SASL Buffer Size
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="maxBufSize"
+ type="number"
+ min="-1"
+ max="2147483647"
+ value={this.state.maxBufSize}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row
+ title="A list of SASL mechanisms the server will only accept (nsslapd-allowed-sasl-mechanisms). The default is all mechanisms are allowed."
+ className="ds-margin-top"
+ >
+ <Col componentClass={ControlLabel} sm={3}>
+ Allowed SASL Mechanisms
+ </Col>
+ <Col sm={4}>
+ <Typeahead
+ id="allowedMechs"
+ onChange={value => {
+ this.handleChange(value);
+ }}
+ multiple
+ options={this.state.supportedMechs}
+ selected={this.state.allowedMechs}
+ placeholder="Type SASL mechanism to allow"
+ ref={(typeahead) => { this.typeahead = typeahead }}
+ />
+ </Col>
+ </Row>
+ <Row
+ title="Check all sasl mappings until one succeeds or they all fail (nsslapd-sasl-mapping-fallback)."
+ className="ds-margin-top"
+ >
+ <Checkbox
+ checked={this.state.mappingFallback}
+ id="mappingFallback"
+ onChange={this.handleChange} className="ds-margin-left-sm"
+ >
+ Allow SASL Mapping Fallback
+ </Checkbox>
+ </Row>
+ </Form>
+ <Button
+ disabled={this.state.saveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={this.saveConfig}
+ >
+ Save Settings
+ </Button>
+ <hr />
+ <Row>
+ <h4 className="ds-center ds-logo-style">
+ <div className="ds-inline">
+ <ControlLabel>
+ <b>SASL Mappings</b>
+ </ControlLabel>
+ </div>
+ <div className="ds-left-indent ds-inline">
+ <ControlLabel>
+ {tableSpinner}
+ </ControlLabel>
+ </div>
+ </h4>
+ </Row>
+ <SASLTable
+ rows={this.state.mappings}
+ editMapping={this.showEditMapping}
+ deleteMapping={this.showConfirmDelete}
+ className="ds-margin-top"
+ />
+ <Button
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={this.showCreateMapping}
+ >
+ Create New Mapping
+ </Button>
+ </div>;
+ }
+
+ return (
+ <div id="server-sasl-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ {body}
+ <SASLMappingModal
+ showModal={this.state.showMappingModal}
+ testBtnDisabled={this.state.testBtnDisabled}
+ saveDisabled={this.state.saveMappingDisabled}
+ closeHandler={this.closeMapping}
+ handleChange={this.state.saslModalType == "Create" ? this.handleModalAddChange : this.handleModalChange}
+ handleTestRegex={this.handleTestRegex}
+ saveHandler={this.state.saslModalType == "Create" ? this.createMapping : this.editMapping}
+ error={this.state.saslErrObj}
+ type={this.state.saslModalType}
+ name={this.state.saslMapName}
+ regex={this.state.saslMapRegex}
+ testText={this.state.saslTestText}
+ base={this.state.saslBase}
+ filter={this.state.saslFilter}
+ priority={this.state.saslPriority}
+ spinning={this.state.tableLoading}
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmDelete}
+ closeHandler={this.closeConfirmDelete}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deleteMapping}
+ item={this.state.saslMapName}
+ checked={this.state.modalChecked}
+ spinning={this.state.tableLoading}
+ mTitle="Delete SASL Mapping"
+ mMsg="Are you sure you want to delete this SASL mapping?"
+ mSpinningMsg="Deleting SASL Mapping ..."
+ mBtnName="Delete Mapping"
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/cockpit/389-console/src/lib/server/serverModals.jsx b/src/cockpit/389-console/src/lib/server/serverModals.jsx
new file mode 100644
index 0000000..ea7bfe9
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/serverModals.jsx
@@ -0,0 +1,224 @@
+import React from "react";
+import {
+ Button,
+ // Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Modal,
+ Row,
+ Spinner,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class SASLMappingModal extends React.Component {
+ render() {
+ let title = this.props.type;
+ let btnText = "Create";
+ let spinning = "";
+ if (title != "Create") {
+ btnText = "Save";
+ }
+ if (this.props.spinning) {
+ spinning = <Spinner className="ds-margin-top-lg" loading size="md" />;
+ }
+
+ return (
+ <Modal show={this.props.showModal} onHide={this.props.closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.props.closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {title} SASL Mapping
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal>
+ <Row
+ className="ds-margin-top"
+ title="SASL Mapping entry name"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ SASL Mapping Name
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslMapName"
+ type="text"
+ onChange={this.props.handleChange}
+ className={this.props.error.saslMapName ? "ds-input-bad" : ""}
+ defaultValue={this.props.name}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="SASL mapping Regular Expression"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ SASL Mapping Regex
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslMapRegex"
+ type="text"
+ onChange={this.props.handleChange}
+ className={this.props.error.saslMapRegex ? "ds-input-bad" : ""}
+ defaultValue={this.props.regex}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="Test Regular Expression"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ <font size="2">* Test Regex</font>
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslTestText"
+ type="text"
+ onChange={this.props.handleChange}
+ defaultValue={this.props.testText}
+ placeholder="Enter text to test regex"
+ />
+ </Col>
+ <Col sm={1}>
+ <Button
+ disabled={this.props.testBtnDisabled}
+ bsStyle="primary"
+ onClick={this.props.handleTestRegex}
+ >
+ Test It
+ </Button>
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="The search base or a specific entry DN to match against the constructed DN"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ SASL Mapping Base
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslBase"
+ type="text"
+ onChange={this.props.handleChange}
+ className={this.props.error.saslBase ? "ds-input-bad" : ""}
+ defaultValue={this.props.base}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="SASL mapping search filter"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ SASL Mapping Filter
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslFilter"
+ type="text"
+ onChange={this.props.handleChange}
+ className={this.props.error.saslFilter ? "ds-input-bad" : ""}
+ defaultValue={this.props.filter}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="Set the mapping priority for which mappins should be tried first"
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ SASL Mapping Priority
+ </Col>
+ <Col sm={5}>
+ <FormControl
+ id="saslPriority"
+ type="number"
+ min="1"
+ max="100"
+ value={this.props.priority}
+ onChange={this.props.handleChange}
+ className={this.props.error.saslPriority ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ </Form>
+ {spinning}
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.props.closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ disabled={this.props.saveDisabled}
+ onClick={() => {
+ this.props.saveHandler(this.props.name);
+ }}
+ >
+ {btnText} Mapping
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Types and defaults
+
+SASLMappingModal.propTypes = {
+ showModal: PropTypes.bool,
+ testBtnDisabled: PropTypes.bool,
+ saveDisabled: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleTestRegex: PropTypes.func,
+ saveHandler: PropTypes.func,
+ error: PropTypes.object,
+ name: PropTypes.string,
+ regex: PropTypes.string,
+ testText: PropTypes.string,
+ base: PropTypes.string,
+ filter: PropTypes.string,
+ priority: PropTypes.string,
+ spinning: PropTypes.bool,
+};
+
+SASLMappingModal.defaultProps = {
+ showModal: false,
+ testBtnDisabled: true,
+ saveDisabled: true,
+ closeHandler: noop,
+ handleChange: noop,
+ handleTestRegex: noop,
+ saveHandler: noop,
+ error: {},
+ name: "",
+ regex: "",
+ testText: "",
+ base: "",
+ filter: "",
+ priority: "",
+ spinning: PropTypes.bool,
+};
diff --git a/src/cockpit/389-console/src/lib/server/serverTables.jsx b/src/cockpit/389-console/src/lib/server/serverTables.jsx
new file mode 100644
index 0000000..d4f0782
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/serverTables.jsx
@@ -0,0 +1,247 @@
+import React from "react";
+import {
+ DropdownButton,
+ MenuItem,
+ actionHeaderCellFormatter,
+ sortableHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import { DSTable, DSShortTable } from "../dsTable.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class SASLTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "cn",
+ columns: [
+ {
+ property: "cn",
+ header: {
+ label: "Mapping Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nssaslmapregexstring",
+ header: {
+ label: "Regular Expression",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nssaslmapbasedntemplate",
+ header: {
+ label: "Search Base DN",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nssaslmapfiltertemplate",
+ header: {
+ label: "Search Filter",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "nssaslmappriority",
+ header: {
+ label: "Priority",
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 5,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 5
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.cn[0]}>
+ <DropdownButton id={rowData.cn[0]}
+ className="ds-action-button"
+ bsStyle="primary" title="Actions">
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.editMapping(
+ rowData.cn[0],
+ rowData.nssaslmapregexstring[0],
+ rowData.nssaslmapbasedntemplate[0],
+ rowData.nssaslmapfiltertemplate[0],
+ rowData.nssaslmappriority[0]
+ );
+ }}
+ >
+ Edit Mapping
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="2" onClick={() => {
+ this.props.deleteMapping(rowData.cn[0]);
+ }}
+ >
+ Delete Mapping
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ],
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ } // Constructor
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "SASL Mappings",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let SASLTable;
+ if (this.props.rows.length == 0) {
+ SASLTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Mappings"}]}
+ />;
+ } else {
+ SASLTable =
+ <DSTable
+ noSearchBar
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+ return (
+ <div>
+ {SASLTable}
+ </div>
+ );
+ }
+}
+
+SASLTable.propTypes = {
+ rows: PropTypes.array,
+ editMapping: PropTypes.func,
+ deleteMapping: PropTypes.func
+};
+
+SASLTable.defaultProps = {
+ rows: [],
+ editMapping: noop,
+ deleteMapping: noop
+};
diff --git a/src/cockpit/389-console/src/lib/server/settings.jsx b/src/cockpit/389-console/src/lib/server/settings.jsx
new file mode 100644
index 0000000..8cc4c48
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/settings.jsx
@@ -0,0 +1,1348 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Nav,
+ NavItem,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+const general_attrs = [
+ 'nsslapd-port',
+ 'nsslapd-secureport',
+ 'nsslapd-localhost',
+ 'nsslapd-listenhost',
+ 'nsslapd-bakdir',
+ 'nsslapd-ldifdir',
+ 'nsslapd-schemadir',
+ 'nsslapd-certdir'
+];
+
+const rootdn_attrs = [
+ 'nsslapd-rootpw',
+ 'nsslapd-rootpwstoragescheme',
+];
+
+const disk_attrs = [
+ 'nsslapd-disk-monitoring',
+ 'nsslapd-disk-monitoring-logging-critical',
+ 'nsslapd-disk-monitoring-threshold',
+ 'nsslapd-disk-monitoring-grace-period',
+];
+
+const adv_attrs = [
+ 'nsslapd-allow-anonymous-access',
+ 'nsslapd-entryusn-global',
+ 'nsslapd-ignore-time-skew',
+ 'nsslapd-readonly',
+ 'nsslapd-anonlimitsdn',
+ 'nsslapd-schemacheck',
+ 'nsslapd-syntaxcheck',
+ 'nsslapd-plugin-logging',
+ 'nsslapd-syntaxlogging',
+ 'nsslapd-plugin-binddn-tracking',
+ 'nsslapd-attribute-name-exceptions',
+ 'nsslapd-dn-validate-strict',
+];
+
+export class ServerSettings extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: true,
+ activeKey: 1,
+ notifications: [],
+ attrs: this.props.attrs,
+ // Setting lists
+ configSaveDisabled: true,
+ configReloading: false,
+ errObjConfig: {},
+ rootDNReloading: false,
+ rootDNSaveDisabled: true,
+ errObjRootDN: {},
+ diskMonReloading: false,
+ diskMonSaveDisabled: true,
+ errObjDiskMon: {},
+ advSaveDisabled: true,
+ advReloading: false,
+ errObjAdv: {},
+ };
+
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleConfigChange = this.handleConfigChange.bind(this);
+ this.handleRootDNChange = this.handleRootDNChange.bind(this);
+ this.handleDiskMonChange = this.handleDiskMonChange.bind(this);
+ this.handleAdvChange = this.handleAdvChange.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.saveRootDN = this.saveRootDN.bind(this);
+ this.reloadRootDN = this.reloadRootDN.bind(this);
+ this.saveDiskMonitoring = this.saveDiskMonitoring.bind(this);
+ this.reloadDiskMonitoring = this.reloadDiskMonitoring.bind(this);
+ this.saveAdvanced = this.saveAdvanced.bind(this);
+ this.reloadAdvanced = this.reloadAdvanced.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleConfigChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObjConfig;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let general_attr of general_attrs) {
+ if (attr == general_attr && this.state['_' + general_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let general_attr of general_attrs) {
+ if (attr != general_attr && this.state['_' + general_attr] != this.state[general_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ if (attr != 'nsslapd-listenhost' && value == "") {
+ // Only listenhost is allowed to be blank
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ configSaveDisabled: disableSaveBtn,
+ errObjConfig: errObj,
+ });
+ }
+
+ handleRootDNChange(e) {
+ let value = e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObjRootDN;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let rootdn_attr of rootdn_attrs) {
+ if (attr == rootdn_attr && this.state['_' + rootdn_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let rootdn_attr of rootdn_attrs) {
+ if (attr != rootdn_attr && this.state['_' + rootdn_attr] != this.state[rootdn_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Handle validating passwords are in sync
+ if (attr == 'nsslapd-rootpw') {
+ if (value != this.state._confirmRootpw) {
+ disableSaveBtn = true;
+ errObj['nsslapd-rootpw'] = true;
+ } else {
+ errObj['nsslapdrootpw'] = false;
+ }
+ }
+ if (attr == 'confirmRootpw') {
+ if (value != this.state['_nsslapd-rootpw']) {
+ disableSaveBtn = true;
+ errObj['confirmRootpw'] = true;
+ } else {
+ errObj['confirmRootpw'] = false;
+ }
+ }
+
+ if (value == "") {
+ disableSaveBtn = true;
+ valueErr = true;
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ rootDNSaveDisabled: disableSaveBtn,
+ errObjRootDN: errObj
+ });
+ }
+
+ handleDiskMonChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObjDiskMon;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let disk_attr of disk_attrs) {
+ if (attr == disk_attr && this.state['_' + disk_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let disk_attr of disk_attrs) {
+ if (attr != disk_attr && this.state['_' + disk_attr] != this.state[disk_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ if (value == "" && e.target.type !== 'checkbox') {
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ diskMonSaveDisabled: disableSaveBtn,
+ errObjDiskMon: errObj
+ });
+ }
+
+ handleAdvChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObjAdv;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let adv_attr of adv_attrs) {
+ if (attr == adv_attr && this.state['_' + adv_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let adv_attr of adv_attrs) {
+ if (attr != adv_attr && this.state['_' + adv_attr] != this.state[adv_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Handle special cases for anon limit dn
+ if (attr == 'nsslapd-anonlimitsdn' && !valid_dn(value)) {
+ errObj[attr] = true;
+ }
+ if (value == "" && attr != 'nsslapd-anonlimitsdn' && e.target.type !== 'checkbox') {
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ advSaveDisabled: disableSaveBtn,
+ errObjAdv: errObj,
+ });
+ }
+
+ loadConfig() {
+ let attrs = this.state.attrs;
+ // Handle the checkbox values
+ let diskMonitoring = false;
+ let diskLogCritical = false;
+ let schemaCheck = false;
+ let syntaxCheck = false;
+ let pluginLogging = false;
+ let syntaxLogging = false;
+ let bindDNTracking = false;
+ let nameExceptions = false;
+ let dnValidate = false;
+ let usnGlobal = false;
+ let ignoreSkew = false;
+ let readOnly = false;
+ let listenhost = "";
+
+ if (attrs['nsslapd-entryusn-global'][0] == "on") {
+ usnGlobal = true;
+ }
+ if (attrs['nsslapd-ignore-time-skew'][0] == "on") {
+ ignoreSkew = true;
+ }
+ if (attrs['nsslapd-readonly'][0] == "on") {
+ readOnly = true;
+ }
+ if (attrs['nsslapd-disk-monitoring'][0] == "on") {
+ diskMonitoring = true;
+ }
+ if (attrs['nsslapd-disk-monitoring-logging-critical'][0] == "on") {
+ diskLogCritical = true;
+ }
+ if (attrs['nsslapd-schemacheck'][0] == "on") {
+ schemaCheck = true;
+ }
+ if (attrs['nsslapd-syntaxcheck'][0] == "on") {
+ syntaxCheck = true;
+ }
+ if (attrs['nsslapd-plugin-logging'][0] == "on") {
+ pluginLogging = true;
+ }
+ if (attrs['nsslapd-syntaxlogging'][0] == "on") {
+ syntaxLogging = true;
+ }
+ if (attrs['nsslapd-plugin-binddn-tracking'][0] == "on") {
+ bindDNTracking = true;
+ }
+ if (attrs['nsslapd-attribute-name-exceptions'][0] == "on") {
+ nameExceptions = true;
+ }
+ if (attrs['nsslapd-dn-validate-strict'][0] == "on") {
+ dnValidate = true;
+ }
+ if ('nsslapd-listenhost' in attrs) {
+ listenhost = attrs['nsslapd-listenhost'][0];
+ }
+
+ this.setState({
+ loaded: true,
+ loading: false,
+ // Settings
+ 'nsslapd-port': attrs['nsslapd-port'][0],
+ 'nsslapd-secureport': attrs['nsslapd-secureport'][0],
+ 'nsslapd-localhost': attrs['nsslapd-localhost'][0],
+ 'nsslapd-listenhost': listenhost,
+ 'nsslapd-bakdir': attrs['nsslapd-bakdir'][0],
+ 'nsslapd-ldifdir': attrs['nsslapd-ldifdir'][0],
+ 'nsslapd-schemadir': attrs['nsslapd-schemadir'][0],
+ 'nsslapd-certdir': attrs['nsslapd-certdir'][0],
+ 'nsslapd-rootdn': attrs['nsslapd-rootdn'][0],
+ 'nsslapd-rootpw': attrs['nsslapd-rootpw'][0],
+ confirmRootpw: attrs['nsslapd-rootpw'][0],
+ 'nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
+ 'nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ 'nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
+ 'nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
+ 'nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
+ 'nsslapd-disk-monitoring': diskMonitoring,
+ 'nsslapd-disk-monitoring-logging-critical': diskLogCritical,
+ 'nsslapd-schemacheck': schemaCheck,
+ 'nsslapd-syntaxcheck': syntaxCheck,
+ 'nsslapd-plugin-logging': pluginLogging,
+ 'nsslapd-syntaxlogging': syntaxLogging,
+ 'nsslapd-plugin-binddn-tracking': bindDNTracking,
+ 'nsslapd-attribute-name-exceptions': nameExceptions,
+ 'nsslapd-dn-validate-strict': dnValidate,
+ 'nsslapd-entryusn-global': usnGlobal,
+ 'nsslapd-ignore-time-skew': ignoreSkew,
+ 'nsslapd-readonly': readOnly,
+ // Record original values
+ '_nsslapd-port': attrs['nsslapd-port'][0],
+ '_nsslapd-secureport': attrs['nsslapd-secureport'][0],
+ '_nsslapd-localhost': attrs['nsslapd-localhost'][0],
+ '_nsslapd-listenhost': listenhost,
+ '_nsslapd-bakdir': attrs['nsslapd-bakdir'][0],
+ '_nsslapd-ldifdir': attrs['nsslapd-ldifdir'][0],
+ '_nsslapd-schemadir': attrs['nsslapd-schemadir'][0],
+ '_nsslapd-certdir': attrs['nsslapd-certdir'][0],
+ '_nsslapd-rootdn': attrs['nsslapd-rootdn'][0],
+ '_nsslapd-rootpw': attrs['nsslapd-rootpw'][0],
+ _confirmRootpw: attrs['nsslapd-rootpw'][0],
+ '_nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
+ '_nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ '_nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
+ '_nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
+ '_nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
+ '_nsslapd-disk-monitoring': diskMonitoring,
+ '_nsslapd-disk-monitoring-logging-critical': diskLogCritical,
+ '_nsslapd-schemacheck': schemaCheck,
+ '_nsslapd-syntaxcheck': syntaxCheck,
+ '_nsslapd-plugin-logging': pluginLogging,
+ '_nsslapd-syntaxlogging': syntaxLogging,
+ '_nsslapd-plugin-binddn-tracking': bindDNTracking,
+ '_nsslapd-attribute-name-exceptions': nameExceptions,
+ '_nsslapd-dn-validate-strict': dnValidate,
+ '_nsslapd-entryusn-global': usnGlobal,
+ '_nsslapd-ignore-time-skew': ignoreSkew,
+ '_nsslapd-readonly': readOnly,
+ }, this.props.enableTree);
+ }
+
+ saveRootDN() {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'replace'
+ ];
+
+ for (let attr of rootdn_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ cmd.push(attr + "=" + this.state[attr]);
+ }
+ }
+
+ log_cmd("saveRootDN", "Saving changes to root DN", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadRootDN();
+ this.addNotification(
+ "success",
+ "Successfully updated Directory Manager configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadRootDN();
+ this.addNotification(
+ "error",
+ `Error updating Directory Manager configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadRootDN() {
+ this.setState({
+ rootDNReloading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get"
+ ];
+ log_cmd("reloadConfig", "Reload Directory Manager configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ this.setState(() => (
+ {
+ rootDNReloading: false,
+ 'nsslapd-rootdn': attrs['nsslapd-rootdn'][0],
+ 'nsslapd-rootpw': attrs['nsslapd-rootpw'][0],
+ confirmRootpw: attrs['nsslapd-rootpw'][0],
+ 'nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
+ // Record original values
+ '_nsslapd-rootdn': attrs['nsslapd-rootdn'][0],
+ '_nsslapd-rootpw': attrs['nsslapd-rootpw'][0],
+ _confirmRootpw: attrs['nsslapd-rootpw'][0],
+ '_nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],
+ rootDNSaveDisabled: true
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ rootDNReloading: false,
+ });
+ this.addNotification(
+ "error",
+ `Error reloading Directory Manager configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveDiskMonitoring() {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'replace'
+ ];
+
+ for (let attr of disk_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveRootDN", "Saving changes to Disk Monitoring", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadDiskMonitoring();
+ this.addNotification(
+ "success",
+ "Successfully updated Disk Monitoring configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadDiskMonitoring();
+ this.addNotification(
+ "error",
+ `Error updating Disk Monitoring configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadDiskMonitoring() {
+ this.setState({
+ diskMonReloading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get"
+ ];
+ log_cmd("reloadDiskMonitoring", "Reload Disk Monitoring configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let diskMonitoring = false;
+ let diskLogCritical = false;
+
+ if (attrs['nsslapd-disk-monitoring'][0] == "on") {
+ diskMonitoring = true;
+ }
+ if (attrs['nsslapd-disk-monitoring-logging-critical'][0] == "on") {
+ diskLogCritical = true;
+ }
+ this.setState(() => (
+ {
+ diskMonReloading: false,
+ 'nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
+ 'nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
+ 'nsslapd-disk-monitoring': diskMonitoring,
+ 'nsslapd-disk-monitoring-logging-critical': diskLogCritical,
+ // Record original values
+ '_nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
+ '_nsslapd-disk-monitoring-grace-period': attrs['nsslapd-disk-monitoring-grace-period'][0],
+ '_nsslapd-disk-monitoring': diskMonitoring,
+ '_nsslapd-disk-monitoring-logging-critical': diskLogCritical,
+ diskMonSaveDisabled: true
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ diskMonReloading: false,
+ });
+ this.addNotification(
+ "error",
+ `Error reloading Disk Monitoring configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveAdvanced() {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'replace'
+ ];
+ for (let attr of adv_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveAdvanced", "Saving Advanced configuration", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.reloadAdvanced();
+ this.addNotification(
+ "success",
+ "Successfully updated Advanced configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadAdvanced();
+ this.addNotification(
+ "error",
+ `Error updating Advanced configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadAdvanced() {
+ this.setState({
+ advReloading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get"
+ ];
+ log_cmd("reloadAdvanced", "Reload Advanced configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ // Handle the checkbox values
+ let schemaCheck = false;
+ let syntaxCheck = false;
+ let pluginLogging = false;
+ let syntaxLogging = false;
+ let bindDNTracking = false;
+ let nameExceptions = false;
+ let dnValidate = false;
+ let usnGlobal = false;
+ let ignoreSkew = false;
+ let readOnly = false;
+
+ if (attrs['nsslapd-entryusn-global'][0] == "on") {
+ usnGlobal = true;
+ }
+ if (attrs['nsslapd-ignore-time-skew'][0] == "on") {
+ ignoreSkew = true;
+ }
+ if (attrs['nsslapd-readonly'][0] == "on") {
+ readOnly = true;
+ }
+ if (attrs['nsslapd-schemacheck'][0] == "on") {
+ schemaCheck = true;
+ }
+ if (attrs['nsslapd-syntaxcheck'][0] == "on") {
+ syntaxCheck = true;
+ }
+ if (attrs['nsslapd-plugin-logging'][0] == "on") {
+ pluginLogging = true;
+ }
+ if (attrs['nsslapd-syntaxlogging'][0] == "on") {
+ syntaxLogging = true;
+ }
+ if (attrs['nsslapd-plugin-binddn-tracking'][0] == "on") {
+ bindDNTracking = true;
+ }
+ if (attrs['nsslapd-attribute-name-exceptions'][0] == "on") {
+ nameExceptions = true;
+ }
+ if (attrs['nsslapd-dn-validate-strict'][0] == "on") {
+ dnValidate = true;
+ }
+
+ this.setState(() => (
+ {
+ 'nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ 'nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
+ 'nsslapd-schemacheck': schemaCheck,
+ 'nsslapd-syntaxcheck': syntaxCheck,
+ 'nsslapd-plugin-logging': pluginLogging,
+ 'nsslapd-syntaxLogging': syntaxLogging,
+ 'nsslapd-plugin-binddn-tracking': bindDNTracking,
+ 'nsslapd-attribute-name-exceptions': nameExceptions,
+ 'nsslapd-dn-validate-strict': dnValidate,
+ 'nsslapd-entryusn-global': usnGlobal,
+ 'nsslapd-ignore-time-skew': ignoreSkew,
+ 'nsslapd-readonly': readOnly,
+ // Record original values
+ '_nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],
+ '_nsslapd-allow-anonymous-access': attrs['nsslapd-allow-anonymous-access'][0],
+ '_nsslapd-schemacheck': schemaCheck,
+ '_nsslapd-syntaxcheck': syntaxCheck,
+ '_nsslapd-plugin-logging': pluginLogging,
+ '_nsslapd-syntaxLogging': syntaxLogging,
+ '_nsslapd-plugin-binddn-tracking': bindDNTracking,
+ '_nsslapd-attribute-name-exceptions': nameExceptions,
+ '_nsslapd-dn-validate-strict': dnValidate,
+ '_nsslapd-entryusn-global': usnGlobal,
+ '_nsslapd-ignore-time-skew': ignoreSkew,
+ '_nsslapd-readonly': readOnly,
+ advReloading: false,
+ advSaveDisabled: true,
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading Advanced configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ advReloading: false,
+ });
+ });
+ }
+
+ saveConfig() {
+ // Build up the command list
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'replace'
+ ];
+
+ for (let attr of general_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ cmd.push(attr + "=" + this.state[attr]);
+ }
+ }
+
+ log_cmd("saveConfig", "Applying server config change", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ // Continue with the next mod
+ this.reloadConfig();
+ this.addNotification(
+ "success",
+ "Successfully updated server configuration. These " +
+ "changes require the server to be restarted to take effect."
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.reloadConfig();
+ this.addNotification(
+ "error",
+ `Error updating server configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ reloadConfig() {
+ this.setState({
+ configReloading: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get"
+ ];
+ log_cmd("reloadConfig", "Reload server configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ let listenhost = "";
+
+ if ('nsslapd-listenhost' in attrs) {
+ listenhost = attrs['nsslapd-listenhost'][0];
+ }
+ this.setState(() => (
+ {
+ configReloading: false,
+ configSaveDisabled: true,
+ 'nsslapd-port': attrs['nsslapd-port'][0],
+ 'nsslapd-secureport': attrs['nsslapd-secureport'][0],
+ 'nsslapd-localhost': attrs['nsslapd-localhost'][0],
+ 'nsslapd-listenhost': listenhost,
+ 'nsslapd-bakdir': attrs['nsslapd-bakdir'][0],
+ 'nsslapd-ldifdir': attrs['nsslapd-ldifdir'][0],
+ 'nsslapd-schemadir': attrs['nsslapd-schemadir'][0],
+ 'nsslapd-certdir': attrs['nsslapd-certdir'][0],
+ // Record original values
+ '_nsslapd-port': attrs['nsslapd-port'][0],
+ '_nsslapd-secureport': attrs['nsslapd-secureport'][0],
+ '_nsslapd-localhost': attrs['nsslapd-localhost'][0],
+ '_nsslapd-listenhost': listenhost,
+ '_nsslapd-bakdir': attrs['nsslapd-bakdir'][0],
+ '_nsslapd-ldifdir': attrs['nsslapd-ldifdir'][0],
+ '_nsslapd-schemadir': attrs['nsslapd-schemadir'][0],
+ '_nsslapd-certdir': attrs['nsslapd-certdir'][0],
+ })
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error reloading server configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ configReloading: false,
+ });
+ });
+ }
+
+ render() {
+ let body = "";
+ let reloadSpinner = "";
+ let diskMonitor = "";
+
+ if (this.state['nsslapd-disk-monitoring']) {
+ diskMonitor =
+ <Form horizontal className="ds-margin-top">
+ <Row
+ className="ds-margin-top"
+ title="The available disk space, in bytes, that will trigger the shutdown process. Default is 2mb. Once below half of the threshold then we enter the shutdown mode. (nsslapd-disk-monitoring-threshold)"
+ >
+ <Col componentClass={ControlLabel} sm={4}>
+ Disk Monitoring Threshold
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-disk-monitoring-threshold"
+ type="text"
+ value={this.state['nsslapd-disk-monitoring-threshold']}
+ onChange={this.handleDiskMonChange}
+ className={this.state.errObjDiskMon.diskThreshold ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="How many minutes to wait to allow an admin to clean up disk space before shutting slapd down. The default is 60 minutes. (nsslapd-disk-monitoring-grace-period)."
+ >
+ <Col componentClass={ControlLabel} sm={4}>
+ Disk Monitoring Grace Period
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-disk-monitoring-grace-period"
+ type="text"
+ value={this.state['nsslapd-disk-monitoring-grace-period']}
+ onChange={this.handleDiskMonChange}
+ className={this.state.errObjDiskMon.diskGracePeriod ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="When disk space gets critically low do not remove logs to free up disk space (nsslapd-disk-monitoring-logging-critical)."
+ >
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Logs
+ </Col>
+ <Col sm={4}>
+ <Checkbox
+ id="nsslapd-disk-monitoring-logging-critical"
+ defaultChecked={this.state['nsslapd-disk-monitoring-logging-critical']}
+ onChange={this.handleDiskMonChange}
+ >
+ Preserve Logs Even If Disk Space Gets Low
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>;
+ }
+
+ if (this.state.configReloading || this.state.rootDNReloading ||
+ this.state.diskMonReloading || this.state.advReloading) {
+ reloadSpinner = <Spinner loading size="md" />;
+ }
+
+ if (this.state.loading) {
+ body =
+ <div className="ds-loading-spinner ds-margin-top ds-center">
+ <h4>Loading Server Settings ...</h4>
+ <Spinner className="ds-margin-top" loading size="md" />
+ </div>;
+ } else {
+ body =
+ <div>
+ <Row>
+ <Col sm={4}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg ds-margin-left-sm">
+ Server Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh configuration settings"
+ onClick={this.reloadConfig}
+ />
+ </ControlLabel>
+ </Col>
+ <Col sm={8} className="ds-margin-top-lg">
+ {reloadSpinner}
+ </Col>
+ </Row>
+ <div className={this.state.loading ? 'ds-fadeout' : 'ds-fadein ds-margin-left'}>
+ <TabContainer id="server-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div className="ds-margin-top">
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'General Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Directory Manager'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Disk Monitoring'}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: 'Advanced Settings'}} />
+ </NavItem>
+ </Nav>
+ <TabContent className="ds-margin-top-lg">
+ <TabPane eventKey={1}>
+ <Form className="ds-margin-top-lg" horizontal>
+ <Row title="The server's local hostname (nsslapd-localhost)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Hostname
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-localhost"
+ type="text"
+ value={this.state['nsslapd-localhost']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-localhost'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The server's port number (nsslapd-port)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ LDAP Port
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-port"
+ type="number"
+ min="0"
+ max="65535"
+ value={this.state['nsslapd-port']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-port'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The server's secure port number (nsslapd-port)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ LDAPS Port
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-secureport"
+ type="number"
+ min="1"
+ max="65535"
+ value={this.state['nsslapd-secureport']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-secureport'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row
+ title="This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address). Requires restart. (nsslapd-listenhost)."
+ className="ds-margin-top"
+ >
+ <Col componentClass={ControlLabel} sm={4}>
+ Listen Host Address
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-listenhost"
+ type="text"
+ value={this.state['nsslapd-listenhost']}
+ onChange={this.handleConfigChange}
+ />
+ </Col>
+ </Row>
+ <Row title="The location where database backups are stored (nsslapd-bakdir)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Backup Directory
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-bakdir"
+ type="text"
+ value={this.state['nsslapd-bakdir']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-bakdir'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The location where the server's LDIF files are located (nsslapd-ldifdir)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ LDIF File Directory
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-ldifdir"
+ type="text"
+ value={this.state['nsslapd-ldifdir']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-ldifdir'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The location for the servers custom schema files. (nsslapd-schemadir)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Schema Directory
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-schemadir"
+ type="text"
+ value={this.state['nsslapd-schemadir']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-schemadir'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The location of the server's certificates (nsslapd-certdir)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ TLS Certificate Directory
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsslapd-certdir"
+ type="text"
+ value={this.state['nsslapd-certdir']}
+ onChange={this.handleConfigChange}
+ className={this.state.errObjConfig['nsslapd-certdir'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.configSaveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={this.saveConfig}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <Form className="ds-margin-top-lg" horizontal>
+ <Row title="The DN of the unrestricted directory manager (nsslapd-rootdn)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Directory Manager DN
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ disabled
+ type="text"
+ value={this.state['nsslapd-rootdn']}
+ />
+ </Col>
+ </Row>
+ <Row title="The Directory Manager password (nsslapd-rootpw)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Directory Manager Password
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-rootpw"
+ type="password"
+ value={this.state['nsslapd-rootpw']}
+ onChange={this.handleRootDNChange}
+ className={this.state.errObjRootDN['nsslapd-rootpw'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The Directory Manager password (nsslapd-rootpw)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="confirmRootpw"
+ type="password"
+ value={this.state.confirmRootpw}
+ onChange={this.handleRootDNChange}
+ className={this.state.errObjRootDN.confirmRootpw ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="Set the Directory Manager password storage scheme (nsslapd-rootpwstoragescheme)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Password Storage Scheme
+ </Col>
+ <Col sm={4}>
+ <select
+ className="btn btn-default dropdown" id="nsslapd-rootpwstoragescheme"
+ onChange={this.handleRootDNChange} value={this.state['nsslapd-rootpwstoragescheme']}>
+ <option>PBKDF2_SHA256</option>
+ <option>SSHA512</option>
+ <option>SSHA384</option>
+ <option>SSHA256</option>
+ <option>SSHA</option>
+ <option>MD5</option>
+ <option>SMD5</option>
+ <option>CRYPT-MD5</option>
+ <option>CRYPT-SHA512</option>
+ <option>CRYPT-SHA256</option>
+ <option>CRYPT</option>
+ <option>CLEAR</option>
+ </select>
+ </Col>
+ </Row>
+ <Button
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ disabled={this.state.rootDNSaveDisabled}
+ onClick={this.saveRootDN}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+
+ <TabPane eventKey={3}>
+ <Form className="ds-margin-left ds-margin-top-lg">
+ <Row title="Enable disk space monitoring (nsslapd-disk-monitoring)." className="ds-margin-top">
+ <Checkbox
+ id="nsslapd-disk-monitoring"
+ checked={this.state['nsslapd-disk-monitoring']}
+ onChange={this.handleDiskMonChange}
+ >
+ Enable Disk Space Monitoring
+ </Checkbox>
+ </Row>
+ </Form>
+ {diskMonitor}
+ <Button
+ disabled={this.state.diskMonSaveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-med"
+ onClick={this.saveDiskMonitoring}
+ >
+ Save
+ </Button>
+ </TabPane>
+
+ <TabPane eventKey={4}>
+ <Form className="ds-margin-top ds-margin-left" horizontal>
+ <Row className="ds-margin-top-lg">
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-schemacheck"
+ defaultChecked={this.state['nsslapd-schemacheck']}
+ onChange={this.handleAdvChange}
+ title="Enable schema checking (nsslapd-schemacheck)."
+ >
+ Enable Schema Checking
+ </Checkbox>
+ </Col>
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-syntaxcheck"
+ defaultChecked={this.state['nsslapd-syntaxcheck']}
+ onChange={this.handleAdvChange}
+ title="Enable attribute syntax checking (nsslapd-syntaxcheck)."
+ >
+ Enable Attribute Syntax Checking
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-plugin-logging"
+ defaultChecked={this.state['nsslapd-plugin-logging']}
+ onChange={this.handleAdvChange}
+ title="Enable plugins to log access and audit events. (nsslapd-plugin-logging)."
+ >
+ Enable Plugin Logging
+ </Checkbox>
+ </Col>
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-syntaxlogging"
+ defaultChecked={this.state['nsslapd-syntaxlogging']}
+ onChange={this.handleAdvChange}
+ title="Enable syntax logging (nsslapd-syntaxlogging)."
+ >
+ Enable Attribute Syntax Logging
+ </Checkbox>
+ </Col>
+
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-plugin-binddn-tracking"
+ defaultChecked={this.state['nsslapd-plugin-binddn-tracking']}
+ onChange={this.handleAdvChange}
+ title="Enabling this feature will write new operational attributes to the modified entry: internalModifiersname & internalCreatorsname. These new attributes contain the plugin DN, while modifiersname will be the original binding entry that triggered the update. (nsslapd-plugin-binddn-tracking)."
+ >
+ Enable Plugin Bind DN Tracking
+ </Checkbox>
+ </Col>
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-attribute-name-exceptions"
+ defaultChecked={this.state['nsslapd-attribute-name-exceptions']}
+ onChange={this.handleAdvChange}
+ title="Allows non-standard characters in attribute names to be used for backwards compatibility with older servers (nsslapd-attribute-name-exceptions)."
+ >
+ Allow Attribute Naming Exceptions
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-dn-validate-strict"
+ defaultChecked={this.state['nsslapd-dn-validate-strict']}
+ onChange={this.handleAdvChange}
+ title="Enables strict syntax validation for DNs, according to section 3 in RFC 4514 (nsslapd-dn-validate-strict)."
+ >
+ Strict DN Syntax Validation
+ </Checkbox>
+ </Col>
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-entryusn-global"
+ defaultChecked={this.state['nsslapd-entryusn-global']}
+ onChange={this.handleAdvChange}
+ title="For USN plugin - maintain unique USNs across all back end databases (nsslapd-entryusn-global)."
+ >
+ Maintain Unique USNs Across All Backends
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-ignore-time-skew"
+ defaultChecked={this.state['nsslapd-ignore-time-skew']}
+ onChange={this.handleAdvChange}
+ title="Ignore replication time skew when acquiring a replica to start a replciation session (nsslapd-ignore-time-skew)."
+ >
+ Ignore CSN Time Skew
+ </Checkbox>
+ </Col>
+ <Col sm={5}>
+ <Checkbox
+ id="nsslapd-readonly"
+ defaultChecked={this.state['nsslapd-readonly']}
+ onChange={this.handleAdvChange}
+ title="Make entire server read-only (nsslapd-readonly)"
+ >
+ Server Read-Only
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>
+ <Form className="ds-margin-left">
+ <Row
+ className="ds-margin-top-xlg"
+ title="Allow anonymous binds to the server (nsslapd-allow-anonymous-access)."
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ Allow Anonymous Access
+ </Col>
+ <Col sm={4}>
+ <select
+ className="btn btn-default dropdown" id="nsslapd-allow-anonymous-access"
+ onChange={this.handleAdvChange} value={this.state['nsslapd-allow-anonymous-access']}
+ >
+ <option>on</option>
+ <option>off</option>
+ <option title="Allows anonymous search and read access to search the root DSE itself, but restricts access to all other directory entries. ">rootdse</option>
+ </select>
+ </Col>
+ </Row>
+ <Row
+ className="ds-margin-top"
+ title="The DN of a template entry containing the resource limits to apply to anonymous connections (nsslapd-anonlimitsdn)."
+ >
+ <Col componentClass={ControlLabel} sm={5}>
+ Anonymous Resource Limits DN
+ </Col>
+ <Col sm={4}>
+ <FormControl
+ id="nsslapd-anonlimitsdn"
+ type="text"
+ value={this.state['nsslapd-anonlimitsdn']}
+ onChange={this.handleAdvChange}
+ className={this.state.errObjAdv.anonLimitsDN ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Button
+ disabled={this.state.advSaveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg"
+ onClick={this.saveAdvanced}
+ >
+ Save
+ </Button>
+ </Form>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>
+ </div>;
+ }
+
+ return (
+ <div id="server-settings-page">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerSettings.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerSettings.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/lib/server/tuning.jsx b/src/cockpit/389-console/src/lib/server/tuning.jsx
new file mode 100644
index 0000000..f688897
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/server/tuning.jsx
@@ -0,0 +1,556 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "../notifications.jsx";
+import CustomCollapse from "../customCollapse.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Button,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Checkbox,
+ Row,
+ Spinner,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+const tuning_attrs = [
+ 'nsslapd-ndn-cache-enabled',
+ 'nsslapd-ignore-virtual-attrs',
+ 'nsslapd-connection-nocanon',
+ 'nsslapd-enable-turbo-mode',
+ 'nsslapd-threadnumber',
+ 'nsslapd-maxdescriptors',
+ 'nsslapd-timelimit',
+ 'nsslapd-sizelimit',
+ 'nsslapd-pagedsizelimit',
+ 'nsslapd-idletimeout',
+ 'nsslapd-ioblocktimeout',
+ 'nsslapd-outbound-ldap-io-timeout',
+ 'nsslapd-maxbersize',
+ 'nsslapd-maxsasliosize',
+ 'nsslapd-listen-backlog-size',
+ 'nsslapd-max-filter-nest-level',
+ 'nsslapd-ndn-cache-max-size',
+];
+
+export class ServerTuning extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ loaded: false,
+ activeKey: 1,
+ notifications: [],
+ saveDisabled: true,
+ errObj: {},
+ attrs: this.props.attrs,
+ };
+
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.loadConfig = this.loadConfig.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ componentWillMount() {
+ // Loading config
+ if (!this.state.loaded) {
+ this.loadConfig();
+ }
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ handleChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let attr = e.target.id;
+ let disableSaveBtn = true;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ // Check if a setting was changed, if so enable the save button
+ for (let tuning_attr of tuning_attrs) {
+ if (attr == tuning_attr && this.state['_' + tuning_attr] != value) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ // Now check for differences in values that we did not touch
+ for (let tuning_attr of tuning_attrs) {
+ if (attr != tuning_attr && this.state['_' + tuning_attr] != this.state[tuning_attr]) {
+ disableSaveBtn = false;
+ break;
+ }
+ }
+
+ if (value == "" && e.target.type !== 'checkbox') {
+ valueErr = true;
+ disableSaveBtn = true;
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ saveDisabled: disableSaveBtn,
+ errObj: errObj,
+ });
+ }
+
+ loadConfig(reloading) {
+ if (reloading) {
+ this.setState({
+ loading: true
+ });
+ }
+
+ let attrs = this.state.attrs;
+ let ndnEnabled = false;
+ let ignoreVirtAttrs = false;
+ let connNoCannon = false;
+ let turboMode = false;
+
+ if (attrs['nsslapd-ndn-cache-enabled'][0] == "on") {
+ ndnEnabled = true;
+ }
+ if (attrs['nsslapd-ignore-virtual-attrs'][0] == "on") {
+ ignoreVirtAttrs = true;
+ }
+ if (attrs['nsslapd-connection-nocanon'][0] == "on") {
+ connNoCannon = true;
+ }
+ if (attrs['nsslapd-enable-turbo-mode'][0] == "on") {
+ turboMode = true;
+ }
+ this.setState({
+ loaded: true,
+ loading: false,
+ // Settings
+ 'nsslapd-ndn-cache-enabled': ndnEnabled,
+ 'nsslapd-ignore-virtual-attrs': ignoreVirtAttrs,
+ 'nsslapd-connection-nocanon': connNoCannon,
+ 'nsslapd-enable-turbo-mode': turboMode,
+ 'nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
+ 'nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
+ 'nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
+ 'nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
+ 'nsslapd-pagedsizelimit': attrs['nsslapd-pagedsizelimit'][0],
+ 'nsslapd-idletimeout': attrs['nsslapd-idletimeout'][0],
+ 'nsslapd-ioblocktimeout': attrs['nsslapd-ioblocktimeout'][0],
+ 'nsslapd-outbound-ldap-io-timeout': attrs['nsslapd-outbound-ldap-io-timeout'][0],
+ 'nsslapd-maxbersize': attrs['nsslapd-maxbersize'][0],
+ 'nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
+ 'nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
+ 'nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
+ 'nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
+ // Record original values
+ '_nsslapd-ndn-cache-enabled': ndnEnabled,
+ '_nsslapd-ignore-virtual-attrs': ignoreVirtAttrs,
+ '_nsslapd-connection-nocanon': connNoCannon,
+ '_nsslapd-enable-turbo-mode': turboMode,
+ '_nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
+ '_nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
+ '_nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
+ '_nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
+ '_nsslapd-pagedsizelimit': attrs['nsslapd-pagedsizelimit'][0],
+ '_nsslapd-idletimeout': attrs['nsslapd-idletimeout'][0],
+ '_nsslapd-ioblocktimeout': attrs['nsslapd-ioblocktimeout'][0],
+ '_nsslapd-outbound-ldap-io-timeout': attrs['nsslapd-outbound-ldap-io-timeout'][0],
+ '_nsslapd-maxbersize': attrs['nsslapd-maxbersize'][0],
+ '_nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
+ '_nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
+ '_nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
+ '_nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
+ });
+ }
+
+ saveConfig() {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'config', 'replace'
+ ];
+
+ for (let attr of tuning_attrs) {
+ if (this.state['_' + attr] != this.state[attr]) {
+ let val = this.state[attr];
+ if (typeof val === "boolean") {
+ if (val) {
+ val = "on";
+ } else {
+ val = "off";
+ }
+ }
+ cmd.push(attr + "=" + val);
+ }
+ }
+
+ log_cmd("saveConfig", "Saving Tuning configuration", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.loadConfig(1);
+ this.addNotification(
+ "success",
+ "Successfully updated Advanced configuration"
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.loadConfig(1);
+ this.addNotification(
+ "error",
+ `Error updating Advanced configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ render () {
+ let reloadSpinner = "";
+ let body = "";
+
+ if (this.state.loading) {
+ reloadSpinner = <Spinner loading size="md" />;
+ }
+ if (!this.state.loaded) {
+ body =
+ <div className="ds-loading-spinner ds-margin-top ds-center">
+ <h4>Loading tuning configuration ...</h4>
+ <Spinner className="ds-margin-top" loading size="md" />
+ </div>;
+ } else {
+ body =
+ <div>
+ <Row>
+ <Col sm={4}>
+ <ControlLabel className="ds-suffix-header ds-margin-top-lg ds-margin-left-sm">
+ Tuning & Limits
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh tuning settings"
+ onClick={() => {
+ this.loadConfig(1);
+ }}
+ />
+ </ControlLabel>
+ </Col>
+ <Col sm={8} className="ds-margin-top-lg">
+ {reloadSpinner}
+ </Col>
+ </Row>
+ <hr />
+ <Form horizontal>
+ <Row title="The number of worker threads that handle database operations (nsslapd-threadnumber)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Number Of Worker Threads
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-threadnumber"
+ type="number"
+ min="-1"
+ max="1048576"
+ value={this.state['nsslapd-threadnumber']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-threadnumber'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The maximum number of file descriptors the server will use (nsslapd-maxdescriptors)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Maximum File Descriptors
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-maxdescriptors"
+ type="number"
+ min="1024"
+ max="1048576"
+ value={this.state['nsslapd-maxdescriptors']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-maxdescriptors'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The maximum number of seconds allocated for a search request (nsslapd-timelimit)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Search Time Limit
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-timelimit"
+ type="number"
+ min="-1"
+ max="2147483647"
+ value={this.state['nsslapd-timelimit']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-timelimit'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The maximum number of entries to return from a search operation (nsslapd-sizelimit)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Search Size Limit
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-sizelimit"
+ type="number"
+ min="-1"
+ max="2147483647"
+ value={this.state['nsslapd-sizelimit']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-sizelimit'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="The maximum number of entries to return from a paged search operation (nsslapd-pagedsizelimit)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Paged Search Size Limit
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-pagedsizelimit"
+ type="number"
+ min="-1"
+ max="2147483647"
+ value={this.state['nsslapd-pagedsizelimit']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-pagedsizelimit'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="Sets the amount of time in seconds after which an idle LDAP client connection is closed by the server (nsslapd-idletimeout)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Idle Connection Timeout
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-idletimeout"
+ type="number"
+ min="0"
+ max="2147483647"
+ value={this.state['nsslapd-idletimeout']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-idletimeout'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ <Row title="Sets the amount of time in milliseconds after which the connection to a stalled LDAP client is closed (nsslapd-ioblocktimeout)." className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ I/O Block Timeout
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ id="nsslapd-ioblocktimeout"
+ type="number"
+ min="0"
+ max="2147483647"
+ value={this.state['nsslapd-ioblocktimeout']}
+ onChange={this.handleChange}
+ className={this.state.errObj['nsslapd-ioblocktimeout'] ? "ds-input-bad" : ""}
+ />
+ </Col>
+ </Row>
+ </Form>
+ <CustomCollapse className="ds-margin-left-sm ds-margin-right">
+ <div className="ds-margin-top">
+ <Form horizontal>
+ <Row className="ds-margin-top" title="Sets the I/O wait time for all outbound LDAP connections (nsslapd-outbound-ldap-io-timeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Outbound IO Timeout
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="number"
+ min="0"
+ max="2147483647"
+ id="nsslapd-outbound-ldap-io-timeout"
+ value={this.state['nsslapd-outbound-ldap-io-timeout']}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum size in bytes allowed for an incoming message (nsslapd-maxbersize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Maximum BER Size
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="number"
+ min="1"
+ max="2147483647"
+ id="nsslapd-maxbersize"
+ value={this.state['nsslapd-maxbersize']}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum allowed SASL IO packet size that the server will accept (nsslapd-maxsasliosize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Maximum SASL IO Size
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="number"
+ min="-1"
+ max="2147483647"
+ id="nsslapd-maxsasliosize"
+ value={this.state['nsslapd-maxsasliosize']}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum length for how long the connection queue for the socket can grow before refusing connections (nsslapd-listen-backlog-size).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Listen Backlog Size
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="number"
+ min="64"
+ id="nsslapd-listen-backlog-size"
+ value={this.state['nsslapd-listen-backlog-size']}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Sets how deep a nested search filter is analysed (nsslapd-max-filter-nest-level).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Maximum Nested Filter Level
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="number"
+ min="0"
+ id="nsslapd-max-filter-nest-level"
+ value={this.state['nsslapd-max-filter-nest-level']}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-xlg">
+ <Col componentClass={ControlLabel} sm={4} title="Enable the normalized DN cache. Each thread has its own cache (nsslapd-ndn-cache-enabled).">
+ <Checkbox
+ checked={this.state['nsslapd-ndn-cache-enabled']}
+ id="nsslapd-ndn-cache-enabled"
+ onChange={this.handleChange}
+ >
+ Enable Normalized DN Cache
+ </Checkbox>
+ </Col>
+ <Col sm={4}>
+ <div className="ds-inline">
+ <FormControl
+ id="nsslapd-ndn-cache-max-size"
+ type="number"
+ min="1048576"
+ max="2147483647"
+ className="ds-input-right"
+ value={this.state['nsslapd-ndn-cache-max-size']}
+ onChange={this.handleChange}
+ title="Per thread NDN cache size in bytes (nsslapd-ndn-cache-max-size)."
+ />
+ </div>
+ <div className="ds-inline ds-left-margin ds-lower-field">
+ <font size="2">bytes</font>
+ </div>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={4} componentClass={ControlLabel} title="Disable DNS reverse entries for outgoing connections (nsslapd-connection-nocanon).">
+ <Checkbox
+ id="nsslapd-connection-nocanon"
+ defaultChecked={this.state['nsslapd-connection-nocanon']}
+ onChange={this.handleChange}
+ >
+ Disable Reverse DNS Lookups
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={4} componentClass={ControlLabel} title="Sets the worker threads to continuously read a connection without passing it back to the polling mechanism. (nsslapd-enable-turbo-mode).">
+ <Checkbox
+ id="nsslapd-enable-turbo-mode"
+ defaultChecked={this.state['nsslapd-enable-turbo-mode']}
+ onChange={this.handleChange}
+ >
+ Enable Connection Turbo Mode
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={4} componentClass={ControlLabel} title="Disable the virtual attribute lookup in a search entry (nsslapd-ignore-virtual-attrs).">
+ <Checkbox
+ id="nsslapd-ignore-virtual-attrs"
+ defaultChecked={this.state['nsslapd-ignore-virtual-attrs']}
+ onChange={this.handleChange}
+ >
+ Disable Virtual Attribute Lookups
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>
+ </div>
+ </CustomCollapse>
+ <Button
+ disabled={this.state.saveDisabled}
+ bsStyle="primary"
+ className="ds-margin-top-lg ds-margin-left"
+ onClick={this.saveConfig}
+ >
+ Save
+ </Button>
+ </div>;
+ }
+
+ return (
+ <div id="tuning-content">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ {body}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+ServerTuning.propTypes = {
+ serverId: PropTypes.string,
+ attrs: PropTypes.object,
+};
+
+ServerTuning.defaultProps = {
+ serverId: "",
+ attrs: {},
+};
diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx
index 7663129..755e1d0 100644
--- a/src/cockpit/389-console/src/replication.jsx
+++ b/src/cockpit/389-console/src/replication.jsx
@@ -979,7 +979,7 @@ export class Replication extends React.Component {
let attrContent = JSON.parse(content);
let attrs = [];
for (let content of attrContent['items']) {
- attrs.push(content.name);
+ attrs.push(content.name[0]);
}
this.setState({
attributes: attrs,
diff --git a/src/cockpit/389-console/src/security.jsx b/src/cockpit/389-console/src/security.jsx
index 2e61f52..062be2e 100644
--- a/src/cockpit/389-console/src/security.jsx
+++ b/src/cockpit/389-console/src/security.jsx
@@ -2,24 +2,26 @@ import cockpit from "cockpit";
import React from "react";
import Switch from "react-switch";
import { NotificationController, ConfirmPopup } from "./lib/notifications.jsx";
-import { log_cmd, valid_port } from "./lib/tools.jsx";
+import { log_cmd } from "./lib/tools.jsx";
import { Typeahead } from "react-bootstrap-typeahead";
import { CertificateManagement } from "./lib/security/certificateManagement.jsx";
import { SecurityEnableModal } from "./lib/security/securityModals.jsx";
import { Ciphers } from "./lib/security/ciphers.jsx";
import {
+ Button,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
Nav,
NavItem,
+ Row,
+ Spinner,
TabContainer,
TabContent,
TabPane,
- Col,
- Row,
- ControlLabel,
- Button,
- Checkbox,
- Icon,
- Spinner
} from "patternfly-react";
import PropTypes from "prop-types";
import "./css/ds.css";
@@ -46,7 +48,6 @@ export class Security extends React.Component {
securityEnabled: false,
requireSecureBinds: false,
secureListenhost: false,
- securePort: '636',
clientAuth: false,
checkHostname: false,
validateCert: '',
@@ -54,11 +55,11 @@ export class Security extends React.Component {
sslVersionMax: '',
allowWeakCipher: false,
nssslpersonalityssl: '',
+ nstlsallowclientrenegotiation: true,
// Original config Settings
_securityEnabled: false,
_requireSecureBinds: false,
_secureListenhost: false,
- _securePort: '636',
_clientAuth: false,
_checkHostname: false,
_validateCert: '',
@@ -66,6 +67,7 @@ export class Security extends React.Component {
_sslVersionMax: '',
_allowWeakCipher: false,
_nssslpersonalityssl: '',
+ _nstlsallowclientrenegotiation: true,
};
this.handleChange = this.handleChange.bind(this);
@@ -116,6 +118,10 @@ export class Security extends React.Component {
}
}
+ componentDidMount () {
+ this.props.enableTree();
+ }
+
loadSupportedCiphers () {
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
@@ -283,7 +289,13 @@ export class Security extends React.Component {
let validateCert = "warn";
let cipherPref = "default";
let allowWeak = false;
+ let renegot = true;
+ if ('nstlsallowclientrenegotiation' in config.items) {
+ if (config.items['nstlsallowclientrenegotiation'] == "off") {
+ renegot = false;
+ }
+ }
if ('nsslapd-security' in attrs) {
if (attrs['nsslapd-security'].toLowerCase() == "on") {
secEnabled = true;
@@ -320,7 +332,6 @@ export class Security extends React.Component {
securityEnabled: secEnabled,
requireSecureBinds: secReqSecBinds,
secureListenhost: attrs['nsslapd-securelistenhost'],
- securePort: attrs['nsslapd-secureport'],
clientAuth: clientAuth,
checkHostname: attrs['nsslapd-ssl-check-hostname'],
validateCert: validateCert,
@@ -328,10 +339,11 @@ export class Security extends React.Component {
sslVersionMax: attrs['sslversionmax'],
allowWeakCipher: allowWeak,
cipherPref: cipherPref,
+ nstlsallowclientrenegotiation: renegot,
+ _nstlsallowclientrenegotiation: renegot,
_securityEnabled: secEnabled,
_requireSecureBinds: secReqSecBinds,
_secureListenhost: attrs['nsslapd-securelistenhost'],
- _securePort: attrs['nsslapd-secureport'],
_clientAuth: clientAuth,
_checkHostname: attrs['nsslapd-ssl-check-hostname'],
_validateCert: validateCert,
@@ -513,18 +525,6 @@ export class Security extends React.Component {
if (this.state._clientAuth != this.state.clientAuth) {
cmd.push("--tls-client-auth=" + this.state.clientAuth);
}
- if (this.state._securePort != this.state.securePort) {
- if (!valid_port(this.state.securePort)) {
- this.addNotification(
- "error",
- `The Secure Port is invalid, it must be a number between 1 and 65535`
- );
- // Reset page
- this.loadSecurityConfig();
- return;
- }
- cmd.push("--secure-port=" + this.state.securePort);
- }
if (this.state._secureListenhost != this.state.secureListenhost) {
cmd.push("--listen-host=" + this.state.secureListenhost);
}
@@ -550,6 +550,14 @@ export class Security extends React.Component {
cmd.push("--require-secure-authentication=" + val);
}
+ if (this.state._nstlsallowclientrenegotiation != this.state.nstlsallowclientrenegotiation) {
+ let val = "off";
+ if (this.state.nstlsallowclientrenegotiation) {
+ val = "on";
+ }
+ cmd.push("--tls-client-renegotiation=" + val);
+ }
+
if (cmd.length > 5) {
log_cmd("saveSecurityConfig", "Applying security config change", cmd);
let msg = "Successfully updated security configuration. You must restart the Directory Server for these changes to take effect.";
@@ -626,26 +634,23 @@ export class Security extends React.Component {
let configPage = "";
if (this.state.securityEnabled) {
configPage =
- <div>
- <Row className="ds-margin-top" title="The server's secure port number (nsslapd-secureport).">
- <Col componentClass={ControlLabel} sm={3}>
- Server Secure Port
- </Col>
- <Col sm={4}>
- <input id="securePort" className="ds-input-auto" onChange={this.handleChange} type="text" value={this.state.securePort} />
- </Col>
- </Row>
+ <Form horizontal>
<Row className="ds-margin-top" title="This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address). This parameter specifically sets what interface to use for TLS traffic. Requires restart. (nsslapd-securelistenhost).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Secure Listen Host
</Col>
<Col sm={4}>
- <input id="secureListenhost" className="ds-input-auto" type="text" onChange={this.handleChange} value={this.state.secureListenhost} />
+ <FormControl
+ id="secureListenhost"
+ type="text"
+ value={this.state.secureListenhost}
+ onChange={this.handleChange}
+ />
</Col>
</Row>
<Row className="ds-margin-top" title="The name, or nickname, of the server certificate inthe NSS datgabase the server should use (nsSSLPersonalitySSL).">
- <Col sm={3}>
- <ControlLabel>Server Certificate Name</ControlLabel>
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Certificate Name
</Col>
<Col sm={4}>
<Typeahead
@@ -660,7 +665,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="The minimum SSL/TLS version the server will accept (sslversionmin).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Minimum TLS Version
</Col>
<Col sm={4}>
@@ -674,7 +679,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="The maximum SSL/TLS version the server will accept (sslversionmax).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Maximum TLS Version
</Col>
<Col sm={4}>
@@ -688,7 +693,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="Sets how the Directory Server enforces TLS client authentication (nsSSLClientAuth).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Client Authentication
</Col>
<Col sm={4}>
@@ -700,7 +705,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="Validate server's certificate expiration date (nsslapd-validate-cert).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Validate Certificate
</Col>
<Col sm={4}>
@@ -711,9 +716,8 @@ export class Security extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row>
- <Col sm={5}>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
<Checkbox
id="requireSecureBinds"
defaultChecked={this.state.requireSecureBinds}
@@ -725,7 +729,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={5}>
+ <Col componentClass={ControlLabel} sm={4}>
<Checkbox
id="checkHostname"
defaultChecked={this.state.checkHostname}
@@ -737,7 +741,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={5}>
+ <Col componentClass={ControlLabel} sm={4}>
<Checkbox
id="allowWeakCipher"
defaultChecked={this.state.allowWeakCipher}
@@ -748,17 +752,28 @@ export class Security extends React.Component {
</Checkbox>
</Col>
</Row>
- <p />
+ <Row>
+ <Col componentClass={ControlLabel} sm={4}>
+ <Checkbox
+ id="nstlsallowclientrenegotiation"
+ defaultChecked={this.state.nstlsallowclientrenegotiation}
+ onChange={this.handleChange}
+ title="Allow client-initiated renegotiation (nsTLSAllowClientRenegotiation)."
+ >
+ Allow Client Renegotiation
+ </Checkbox>
+ </Col>
+ </Row>
<Button
bsStyle="primary"
- className="ds-margin-top-med"
+ className="ds-margin-top-lg"
onClick={() => {
this.saveSecurityConfig();
}}
>
Save Configuration
</Button>
- </div>;
+ </Form>;
}
securityPage =
@@ -767,6 +782,18 @@ export class Security extends React.Component {
notifications={this.state.notifications}
removeNotificationAction={this.removeNotification}
/>
+ <Row>
+ <Col sm={11}>
+ <ControlLabel className="ds-suffix-header">
+ Security Settings
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh configuration settings"
+ onClick={this.loadSecurityConfig}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+
<div className="ds-tab-table">
<TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
<div>
@@ -785,23 +812,17 @@ export class Security extends React.Component {
<TabPane eventKey={1}>
<div className="ds-margin-top-xlg ds-indent">
<Row>
- <Col componentClass={ControlLabel} sm={2}>
- Security Enabled
- </Col>
- <Col sm={1}>
+ <Col sm={11}>
+ <ControlLabel>
+ Security Enabled
+ </ControlLabel>
<Switch
- className="ds-switch"
+ className="ds-switch ds-margin-left-sm ds-lower-field"
onChange={this.handleSwitchChange}
checked={this.state.securityEnabled}
height={20}
/>
</Col>
- <Col>
- <Icon className="ds-left-margin ds-refresh"
- type="fa" name="refresh" title="Refresh security settings"
- onClick={this.loadSecurityConfig}
- />
- </Col>
</Row>
<hr />
{configPage}
diff --git a/src/cockpit/389-console/src/server.jsx b/src/cockpit/389-console/src/server.jsx
new file mode 100644
index 0000000..7ad9fea
--- /dev/null
+++ b/src/cockpit/389-console/src/server.jsx
@@ -0,0 +1,345 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "./lib/notifications.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+import {
+ TreeView,
+ Spinner,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { ServerSettings } from "./lib/server/settings.jsx";
+import { ServerTuning } from "./lib/server/tuning.jsx";
+import { ServerSASL } from "./lib/server/sasl.jsx";
+import { ServerLDAPI } from "./lib/server/ldapi.jsx";
+import { ServerAccessLog } from "./lib/server/accessLog.jsx";
+import { ServerAuditLog } from "./lib/server/auditLog.jsx";
+import { ServerAuditFailLog } from "./lib/server/auditfailLog.jsx";
+import { ServerErrorLog } from "./lib/server/errorLog.jsx";
+import { Security } from "./security.jsx";
+
+const treeViewContainerStyles = {
+ width: '295px',
+};
+
+export class Server extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ nodes: [],
+ node_name: "",
+ node_text: "",
+ attrs: [],
+ loaded: false,
+ disableTree: false,
+ };
+
+ this.addNotification = this.addNotification.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.loadTree = this.loadTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
+ this.selectNode = this.selectNode.bind(this);
+ }
+
+ componentWillMount() {
+ this.loadConfig();
+ }
+
+ enableTree() {
+ this.setState({
+ disableTree: false,
+ });
+ }
+
+ loadConfig () {
+ let cmd = [
+ "dsconf", "-j", this.props.serverId, "config", "get"
+ ];
+ log_cmd("loadConfig", "Load server configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ let attrs = config.attrs;
+ this.setState({
+ loaded: true,
+ attrs: attrs
+ }, this.loadTree());
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.setState({
+ loaded: true,
+ });
+ this.addNotification(
+ "error",
+ `Error loading server configuration - ${errMsg.desc}`
+ );
+ });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ loadTree() {
+ let basicData = [
+ {
+ text: "Server Settings",
+ selectable: true,
+ selected: true,
+ icon: "pficon-settings",
+ state: {"expanded": true},
+ id: "settings-config",
+ nodes: []
+ },
+ {
+ text: "Tuning & Limits",
+ selectable: true,
+ icon: "fa fa-tachometer",
+ id: "tuning-config",
+ nodes: []
+ },
+ {
+ text: "Security",
+ selectable: true,
+ icon: "pficon-locked",
+ id: "security-config",
+ nodes: []
+ },
+ {
+ text: "SASL Settings & Mappings",
+ selectable: true,
+ icon: "glyphicon glyphicon-map-marker",
+ id: "sasl-config",
+ nodes: []
+ },
+ {
+ text: "LDAPI & Autobind",
+ selectable: true,
+ icon: "glyphicon glyphicon-flash",
+ id: "ldapi-config",
+ nodes: []
+ },
+ {
+ text: "Logging",
+ icon: "pficon-catalog",
+ selectable: false,
+ id: "logging-config",
+ state: {"expanded": true},
+ nodes: [
+ {
+ text: "Access Log",
+ icon: "glyphicon glyphicon-book",
+ selectable: true,
+ id: "access-log-config",
+ type: "log",
+ },
+ {
+ text: "Audit Log",
+ icon: "glyphicon glyphicon-book",
+ selectable: true,
+ id: "audit-log-config",
+ type: "log",
+ },
+ {
+ text: "Audit Failure Log",
+ icon: "glyphicon glyphicon-book",
+ selectable: true,
+ id: "auditfail-log-config",
+ type: "log",
+ },
+ {
+ text: "Errors Log",
+ icon: "glyphicon glyphicon-book",
+ selectable: true,
+ id: "error-log-config",
+ type: "log",
+ },
+ ]
+ },
+ ];
+ this.setState({
+ nodes: basicData,
+ node_name: this.state.node_name,
+ });
+ }
+
+ selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+ this.setState({
+ disableTree: true, // Disable the tree to allow node to be fully loaded
+ });
+
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ bename: "",
+ };
+ });
+ }
+
+ nodeSelector(nodes, targetNode) {
+ return nodes.map(node => {
+ if (node.nodes) {
+ return {
+ ...node,
+ nodes: this.nodeSelector(node.nodes, targetNode),
+ selected: node.id === targetNode.id ? !node.selected : false
+ };
+ } else if (node.id === targetNode.id) {
+ return { ...node, selected: !node.selected };
+ } else if (node.id !== targetNode.id && node.selected) {
+ return { ...node, selected: false };
+ } else {
+ return node;
+ }
+ });
+ }
+
+ render() {
+ const { nodes } = this.state;
+ let serverPage =
+ <div className="ds-loading-spinner ds-center">
+ <p />
+ <h4>Loading server configuration ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+
+ let server_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+
+ if (this.state.loaded) {
+ if (this.state.node_name == "settings-config" || this.state.node_name == "") {
+ server_element =
+ <ServerSettings
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "tuning-config") {
+ server_element =
+ <ServerTuning
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "sasl-config") {
+ server_element =
+ <ServerSASL
+ serverId={this.props.serverId}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "security-config") {
+ server_element =
+ <Security
+ serverId={this.props.serverId}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "ldapi-config") {
+ server_element =
+ <ServerLDAPI
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "access-log-config") {
+ server_element =
+ <ServerAccessLog
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "audit-log-config") {
+ server_element =
+ <ServerAuditLog
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "auditfail-log-config") {
+ server_element =
+ <ServerAuditFailLog
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ } else if (this.state.node_name == "error-log-config") {
+ server_element =
+ <ServerErrorLog
+ serverId={this.props.serverId}
+ attrs={this.state.attrs}
+ enableTree={this.enableTree}
+ />;
+ }
+
+ serverPage =
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <div className="ds-container">
+ <div>
+ <div className="ds-tree">
+ <div className={disabled} id="server-tree"
+ style={treeViewContainerStyles}>
+ <TreeView
+ nodes={nodes}
+ highlightOnHover
+ highlightOnSelect
+ selectNode={this.selectNode}
+ key={this.state.node_text}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="ds-tree-content">
+ {server_element}
+ </div>
+ </div>
+ </div>;
+ }
+
+ return (
+ <div>
+ {serverPage}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+Server.propTypes = {
+ serverId: PropTypes.string
+};
+
+Server.defaultProps = {
+ serverId: ""
+};
diff --git a/src/cockpit/389-console/src/servers.html b/src/cockpit/389-console/src/servers.html
deleted file mode 100644
index 5df11cd..0000000
--- a/src/cockpit/389-console/src/servers.html
+++ /dev/null
@@ -1,1269 +0,0 @@
-
-<div id="server-content-buttons">
-
- <!--
- General Config Settings
- -->
- <div id="server-config" class="all-pages ds-margin-left">
- <h3 class="ds-config-header">Server Configuration Settings</h3>
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-localhost" class="ds-config-label" title="The server's local hostname (nsslapd-localhost).">Server Hostname</label><input
- type="text" class="ds-input" id="nsslapd-localhost" size="40"/>
- </div>
- <div>
- <label for="nsslapd-port" class="ds-config-label" title="The server's port number (nsslapd-port).">Server Port</label><input
- type="text" class="ds-input" id="nsslapd-port" size="40"/>
- </div>
- <div>
- <label for="nsslapd-listenhost" class="ds-config-label" title=
- "This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address). Requires restart. (nsslapd-listenhost).">Listen Host Address</label><input
- type="text" class="ds-input" id="nsslapd-listenhost" size="40"/>
- </div>
- <div>
- <label for="nsslapd-bakdir" class="ds-config-label" title="The location where database backups are stored (nsslapd-bakdir).">Database Backup Directory</label><input
- type="text" class="ds-input" id="nsslapd-bakdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-ldifdir" class="ds-config-label" title="The location where the server's LDIF files are located (nsslapd-ldifdir).">LDIF File Directory</label><input
- type="text" class="ds-input" id="nsslapd-ldifdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-schemadir" class="ds-config-label" title="The location for the servers custom schema files. (nsslapd-schemadir)">Schema Directory</label><input
- type="text" class="ds-input" id="nsslapd-schemadir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-certdir" class="ds-config-label" title="The location of the server's certificates (nsslapd-certdir).">Certificate Directory</label><input
- type="text" class="ds-input" id="nsslapd-certdir" size="40"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-disk-monitoring"><label
- for="nsslapd-disk-monitoring" class="ds-label" title="Enable disk space monitoring (nsslapd-disk-monitoring).">Enable Disk Space Monitoring</label>
- </div>
- <div>
- <label for="nsslapd-disk-monitoring-threshold" class="ds-config-diskmon-label disk-monitoring" title=
- "The available disk space, in bytes, that will trigger the shutdown process. Default is 2mb. Once below half of the threshold then we enter the shutdown mode. (nsslapd-disk-monitoring-threshold)">
- Disk Monitoring Threshold</label><input
- class="ds-input disk-monitoring disk-monitoring" type="text" id="nsslapd-disk-monitoring-threshold" size="10"/>
- </div>
- <div>
- <label for="nsslapd-disk-monitoring-grace-period" class="ds-config-diskmon-label disk-monitoring" title=
- "How many minutes to wait to allow an admin to clean up disk space before shutting slapd down. The default is 60 minutes. (nsslapd-disk-monitoring-grace-period).">
- Disk Monitoring Grace Period </label><input
- class="ds-input disk-monitoring" type="text" id="nsslapd-disk-monitoring-grace-period" size="10"/>
- </div>
- <div>
- <input type="checkbox" class="ds-config-diskmon-checkbox disk-monitoring" id="nsslapd-disk-monitoring-logging-critical"><label
- for="nsslapd-disk-monitoring-logging-critical" class="ds-label disk-monitoring" title="When disk space gets critically low do not remove logs to free up disk space ().">Preserve Logs</label>
- </div>
- </div>
- </div>
-
- <button class="accordion ds-accordion" id="config-accordion" type="button">► Show Advanced Settings</button>
- <div class="ds-accordion-panel ds-indent">
- <div class="ds-container ds-indent">
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-schemacheck" checked><label
- for="nsslapd-schemacheck" class="ds-label" title="Enable schema checking (nsslapd-schemacheck)."> Enable Schema Checking</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-syntaxcheck" checked><label
- for="nsslapd-syntaxcheck" class="ds-label" title="Enable attribute syntax checking (nsslapd-syntaxcheck)."> Enable Attribute syntax checking</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-syntaxlogging" checked><label
- for="nsslapd-syntaxlogging" class="ds-label" title="Enable syntax logging (nsslapd-syntaxlogging)."> Enable Attribute Syntax Logging</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-plugin-logging" checked><label
- for="nsslapd-plugin-logging" class="ds-label" title="Enable plugins to log access and audit events. (nsslapd-plugin-logging)."> Enable Plugin Logging</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-plugin-binddn-tracking" checked><label
- for="nsslapd-plugin-binddn-tracking" class="ds-label" title=
- "Enabling this feature will write new operational attributes to the modified entry: internalModifiersname & internalCreatorsname. These new attributes contain the plugin DN, while modifiersname will be the original binding entry that triggered the update. (nsslapd-plugin-binddn-tracking)."> Enable Plugin Bind DN Tracking </label>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-attribute-name-exceptions"><label
- for="nsslapd-attribute-name-exceptions" class="ds-label" title="Allows non-standard characters in attribute names to be used for backwards compatibility with older servers"> Allow Attribute Naming Exceptions </label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-dn-validate-strict"><label
- for="nsslapd-dn-validate-strict" class="ds-label" title="Enables strict syntax validation for DNs, according to section 3 in RFC 4514 (nsslapd-dn-validate-strict)."> Enable Strict DN Syntax Validation</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-entryusn-global"><label
- for="nsslapd-entryusn-global" class="ds-label" title="For USN plugin - maintain unique USNs across all back end databases (nsslapd-entryusn-global)."> Enable Unique USNs Across All Backends</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ignore-time-skew"><label
- for="nsslapd-ignore-time-skew" class="ds-label" title="Ignore time skew when generating CSNs"> Ignore CSN Time Skew</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-readonly"><label
- for="nsslapd-readonly" class="ds-label" title="Make entire server read-only (nsslapd-readonly)"> Server Read-Only</label>
- </div>
-
- </div>
- </div>
- <div>
- <label for="nsslapd-allow-anonymous-access" class="ds-config-label" title="Allow anonymous binds to the server (nsslapd-allow-anonymous-access)."> Allow Anonymous Access</label><select
- class="btn btn-default dropdown" id="nsslapd-allow-anonymous-access">
- <option>on</option>
- <option>off</option>
- <option title="Allows anonymous search and read access to search the root DSE itself, but restricts access to all other directory entries. ">rootdse</option>
- </select>
- </div>
- <div>
- <label for="nsslapd-anonlimitsdn" class="ds-config-label" title=
- "The DN of a template entry containing the resource limits to apply to anonymous connections (nsslapd-anonlimitsdn).">Anonymous Resource Limits DN</label><input
- class="ds-input" type="text" id="nsslapd-anonlimitsdn" size="40"/>
- </div>
-
- <hr>
- <div class="ds-inline">
- <form>
- <div>
- <label for="nsslapd-rootdn" class="ds-config-label" title="The DN of the unrestricted directory manager (nsslapd-rootdn).">Directory Manager DN</label><input
- class="ds-input" type="text" disabled id="nsslapd-rootdn" value="cn=Directory Manager" size="40"/>
- </div>
- <div>
- <label for="nsslapd-rootpw" class="ds-config-label" title="The Directory Manager password (nsslapd-rootpw).">Directory Manager Password</label><input
- class="ds-input" type="password" id="nsslapd-rootpw" size="40"/>
- </div>
- <div>
- <label for="nsslapd-rootpw-confirm" class="ds-config-label" title="Confirm directory manager password.">Confirm Password</label><input
- class="ds-input" type="password" id="nsslapd-rootpw-confirm" size="40"/>
- </div>
- <div>
- <label for="nsslapd-rootpwstoragescheme" class="ds-config-label" title="Set the Directory Manager password storage scheme (nsslapd-rootpwstoragescheme).">Password Storage Scheme</label><select
- class="btn btn-default dropdown" id="nsslapd-rootpwstoragescheme">
- <option>PBKDF2_SHA256</option>
- <option>SSHA512</option>
- <option>SSHA384</option>
- <option>SSHA256</option>
- <option>SSHA</option>
- <option>MD5</option>
- <option>SMD5</option>
- <option>CRYPT-MD5</option>
- <option>CRYPT-SHA512</option>
- <option>CRYPT-SHA256</option>
- <option>CRYPT</option>
- <option>CLEAR</option>
- </select>
- </div>
- </form>
- </div>
- </div>
- <p></p>
-
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save Configuration</button>
- </div>
- </div>
-
-
- <!--
- SASL Settings
- -->
- <div id="server-sasl" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">SASL Settings</h3>
- <div class="ds-inline">
- <div>
- <label for="nsslapd-sasl-max-buffer-size" class="ds-config-label" title="The maximum SASL buffer size in bytes (nsslapd-sasl-max-buffer-size).">Max SASL Buffer Size</label><input
- class="ds-input" type="text" id="nsslapd-sasl-max-buffer-size" size="40"/>
- </div>
- <div>
- <label for="nsslapd-allowed-sasl-mechanisms" class="ds-config-label" title="A list of SASL mechanisms the server will only accept (nsslapd-allowed-sasl-mechanisms).">Allowed SASL Mechanisms</label><input
- class="ds-input" type="text" id="nsslapd-allowed-sasl-mechanisms" size="40"/>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-sasl-mapping-fallback" checked><label
- for="nsslapd-sasl-mapping-fallback" class="ds-label" title="Check all sasl mappings until one succeeds or they all fail (nsslapd-sasl-mapping-fallback)."> Allow SASL Mapping Fallback</label>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
-
- <h4 class="ds-config-header"><br>SASL Mappings</h4>
- <div class="ds-indent">
- <table id="sasl-table" class="display ds-sasl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Mapping Name</th>
- <th>Regular Expression</th>
- <th>Search Base DN</th>
- <th>Search Filter</th>
- <th title="Requires 'nsslapd-sasl-mapping-fallback' to “on”">Priority</th>
- <th>Action</th>
- </thead>
- <tbody id="sasl-mappings">
- </tbody>
- </table>
- <button class="btn btn-default" data-toggle="modal" data-target="#sasl-map-form" id="create-sasl-map-btn">Create SASL Mapping</button>
- </div>
- </div>
-
-
- <!--
- Password Policy Settings -> for REACT JS this should be tabbed navigation
- -->
- <div id="global-password-policy" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Global Password Policy Settings</h3>
- <div class="ds-container">
- <div class="ds-split">
- <div class="ds-inline">
- <h4>General Settings</h4>
- <hr class="ds-hr">
- <div>
- <label for="passwordstoragescheme" class="ds-spacing-sm" title="Set the password storage scheme (passwordstoragescheme).">Password Storage Scheme</label><select
- class="btn btn-default dropdown" id="passwordstoragescheme">
- <option>PBKDF2_SHA256</option>
- <option>SSHA512</option>
- <option>SSHA384</option>
- <option>SSHA256</option>
- <option>SSHA</option>
- <option>NS-MTA-MD5</option>
- <option>MD5</option>
- <option>SMD5</option>
- <option>CRYPT-MD5</option>
- <option>CRYPT-SHA512</option>
- <option>CRYPT-SHA256</option>
- <option>CRYPT</option>
- <option>CLEAR</option>
- </select>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-pwpolicy-local" checked><label
- for="nsslapd-pwpolicy-local" class="ds-label" title="Allow subtree/user defined password policies (nsslapd-pwpolicy-local)."> Allow Local Password Policies</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-pwpolicy-inherit-global" checked><label
- for="nsslapd-pwpolicy-inherit-global" class="ds-label" title=
- "If a local password policy does not defined any syntax rules then inherit the local policy syntax (nsslapd-pwpolicy-inherit-global)."> Local Policy Inherits Global Policy</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-allow-hashed-passwords"><label
- for="nsslapd-allow-hashed-passwords" class="ds-label" title="Allow anyone to add a prehashed password (nsslapd-allow-hashed-passwords)."> Allow Adding Pre-Hashed Passwords</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="passwordisglobalpolicy" checked><label
- for="passwordisglobalpolicy" class="ds-label" title="Allow password policy state attributes to replicate (passwordIsGlobalPolicy)."> Replicate Password Policy State Attributes</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="passwordtrackupdatetime" checked><label
- for="passwordtrackupdatetime" class="ds-label" title=
- "Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime)."> Track Password Update Time</label>
- </div>
- <div>
- <p></p>
- <label for="passwordAdminDN" title="The DN for a password administrator or administrator group (passwordAdminDN)">Password Administrator</label><input
- class="ds-input" type="text" id="passwordAdminDN" size="40"/>
- </div>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-split">
- <div class="ds-inline">
- <h4>User Password Settings</h4>
- <hr class="ds-hr">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="passwordchange" checked><label
- for="passwordchange" class="ds-label" title="Allow user's to change their passwords (passwordChange)."> Allow Users To Change Their Passwords</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="passwordmustchange" checked><label
- for="passwordmustchange" class="ds-label" title="User must change its password after its been reset by an administrator (passwordMustChange).">User Must Change Password After Reset</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="passwordhistory" checked><label
- for="passwordhistory" class="ds-label" title="Maintain a password history (passwordHistory).">Keep Password History</label>
- </div>
- <div>
- <input class="ds-history-input" type="text" id="passwordinhistory" size="2"/>Passwords In History
- <label for="passwordminage" class="ds-minage-label" title="Indicates the number of seconds that must pass before a user can change their password. (passwordMinAge)">Allow Password Changes (in seconds)</label><input
- class="ds-input" type="text" id="passwordminage" size="10"/>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-container">
- <div class="ds-split">
- <h4><br>Password Expiration Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox" id="passwordexp" checked><label
- for="passwordexp" class="ds-label" title="Enable a password expiration policy (passwordExp).">Enable Password Expiration</label>
- <div class="ds-inline">
- <div class="ds-expired-div" id="expiration-attrs">
- <div>
- <label for="passwordmaxage" class="ds-expire-label" title="The server's local hostname (passwordMaxAge).">Password Expiration Time (in seconds)</label><input
- class="ds-input" type="text" id="passwordmaxage" size="6"/>
- </div>
- <div>
- <label for="passwordgracelimit" class="ds-expire-label" title="The server's local hostname (passwordGraceLimit).">Allowed Logins After Password Expires</label><input
- class="ds-input" type="text" id="passwordgracelimit" size="6"/>
- </div>
- <div>
- <label for="passwordwarning" class="ds-expire-label" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">Send Password Expiring Warning (in seconds)</label><input
- class="ds-input" type="text" id="passwordwarning" size="6"/>
- </div>
- <div>
- <input type="checkbox" class="ds-send-expiring-checkbox" id="passwordsendexpiringtime"><label
- for="passwordsendexpiringtime" class="ds-label" title="Always return a password expiring control when requested (passwordSendExpiringTime).">Always Send <i>Password Expiring</i> Control</label>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-split">
- <h4><br>Account Lockout Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox" id="passwordlockout" checked><label
- for="passwordlockout" class="ds-label" title="Enable account lockout (passwordLockout).">Enable Account Lockout</label>
- <div class="ds-expired-div" id="lockout-attrs">
- <label for="passwordmaxfailure" class="ds-expire-label" title=
- "The maximum number of failed logins before account gets locked (passwordMaxFailure).">Number of Failed Logins to Lockout Account</label><input
- class="ds-input" type="text" id="passwordmaxfailure" size="5"/>
- <label for="passwordresetfailurecount" class="ds-expire-label" title=
- "The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">Time Before Failure Count Reset </label><input
- class="ds-input" type="text" id="passwordresetfailurecount" size="5"/>
- <div>
- <label title="Lock out the account forever (passwordUnlock)."><input
- class="ds-radio" type="radio" id="passwordunlock" value="passwordunlock" name="account-lockout" checked="checked"> Lockout Account Forever</label>
- <label title="The number of seconds before account gets unlocked (passwordLockoutDuration)."><input
- class="ds-radio" type="radio" name="account-lockout" value="passwordlockoutduration"> Time Until Account Unlocked <input
- class="ds-input" type="text" id="passwordlockoutduration" size="5"/></label>
- </div>
- </div>
- <p></p>
- </div>
- </div>
-
- <div class="ds-inline">
- <h4><br>Password Syntax Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox" id="passwordchecksyntax" checked><label
- for="passwordchecksyntax" class="ds-label" title="Enable account lockout (passwordCheckSyntax).">Check Password Syntax</label>
- <div class="ds-container ds-expired-div" id="syntax-attrs">
- <div class="ds-inline">
- <div>
- <label for="passwordminlength" class="ds-expire-label" title=
- "The minimum number of characters in the password (passwordMinLength).">Password Minimum Length </label><input
- class="ds-input" type="text" id="passwordminlength" size="5"/>
- </div>
- <div>
- <label for="passwordmindigits" class="ds-expire-label" title=
- "Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">Minimum Digit Characters </label><input
- class="ds-input" type="text" id="passwordmindigits" size="5"/>
- </div>
- <div>
- <label for="passwordminalphas" class="ds-expire-label" title=
- "Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">Minimum Alpha Characters </label><input
- class="ds-input" type="text" id="passwordminalphas" size="5"/>
- </div>
- <div>
- <label for="passwordminuppers" class="ds-expire-label" title=
- "Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">Minimum Uppercase Characters </label><input
- class="ds-input" type="text" id="passwordminuppers" size="5"/>
- </div>
- <div>
- <label for="passwordminlowers" class="ds-expire-label" title=
- "Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">Minimum Lowercase Characters </label><input
- class="ds-input" type="text" id="passwordminlowers" size="5"/>
- </div>
- <div>
- <label for="passwordminspecials" class="ds-expire-label" title=
- "Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">Minimum Special Characters </label><input
- class="ds-input" type="text" id="passwordminspecials" size="5"/>
- </div>
- <div>
- <label for="passwordmin8bit" class="ds-expire-label" title=
- "Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">Minimum 8-bit Characters </label><input
- class="ds-input" type="text" id="passwordmin8bit" size="5"/>
- </div>
- <div>
- <label for="passwordmincategories" class="ds-expire-label" title=
- "The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">Minimum Required Character Categories </label><input
- class="ds-input" type="text" id="passwordmincategories" size="5"/>
- </div>
- <div>
- <label for="passwordmintokenlength" class="ds-expire-label" title=
- "The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">Minimum Token Length </label><input
- class="ds-input" type="text" id="passwordmintokenlength" size="5"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <label for="passwordmaxrepeats" class="ds-expire-label" title=
- "The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters </label><input
- class="ds-input" type="text" id="passwordmaxrepeats" size="5"/>
- </div>
- <div>
- <label for="passwordmaxsequence" class="ds-expire-label" title=
- "The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">Maximum Character Sequences </label><input
- class="ds-input" type="text" id="passwordmaxsequence" size="5"/>
- </div>
- <div>
- <label for="passwordmaxseqsets" class="ds-expire-label" title=
- "The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">Maximum Character Sequence Sets </label><input
- class="ds-input" type="text" id="passwordmaxseqsets" size="5"/>
- </div>
- <div>
- <label for="passwordmaxclasschars" class="ds-expire-label" title=
- "The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">Maximum Consecutive Chars Per Char Class </label><input
- class="ds-input" type="text" id="passwordmaxclasschars" size="5"/>
- </div>
- <div>
- <label for="passwordpalindrome" class="ds-expire-label" title=
- "Reject a password if it is a palindrome (passwordPalindrome).">Reject Passwords that Are Palindromes </label><input
- class="ds-margin-top" type="checkbox" id="passwordpalindrome"/>
- </div>
- <div>
- <label for="passworddictcheck" class="ds-expire-label" title=
- "Check the password against the system's CrackLib dictionary (passwordDictCheck).">Check Password Contains Dictionary Word </label><input
- class="ds-margin-top" type="checkbox" id="passworddictcheck"/>
- </div>
- </div>
- </div>
- <div class="ds-margin-left-sm ds-margin-top">
- <div>
- <label for="passwordbadwords" title=
- "A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords).">Reject Passwords That Contain These Words </label><input
- class="ds-input-auto" type="text" id="passwordbadwords"/>
- </div>
- <div class="ds-margin-top">
- <label for="passworduserattributes" title=
- "A space-separated list of entry attributes to compare to the new password (passwordUserAttributes).">Entry Attributes To Compare </label><input
- class="ds-input-auto" type="text" id="passworduserattributes"/>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
- </div>
-
- <!-- -------------------------------------
- Local Password Policies
- ---------------------------------------- -->
- <div id="local-password-policy" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Local Password Policies </h3>
- <label for="local-pwp-suffix">Database Suffix</Label> <select
- class="btn btn-default dropdown" id="local-pwp-suffix">
- </select>
- <div class="ds-page-content">
- <table id="passwd-policy-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Policy Target Entry</th>
- <th>Policy Type</th>
- <th></th>
- </tr>
- </thead>
- <tbody id="local-pwpolicy-tbody">
- </tbody>
- </table>
- <button class="btn btn-primary" data-toggle="modal" data-target="#local-pwp-form" id="create-local-pwp-btn">Create Local Password Policy</button>
- <p></p>
- </div>
- </div>
-
-
- <!--
- Logging Settings
- -->
-
- <!-- Access logging -->
- <div id="server-access-log" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Access Log Settings</h3>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-accesslog-logging-enabled"><label
- for="nsslapd-accesslog-logging-enabled" class="ds-label" title="Enable access logging (nsslapd-accesslog-logging-enabled)."> Enable Access Logging</label>
- <div class="ds-expired-div" id="accesslog-attrs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-accesslog" class="ds-config-label" title="The access log location and name (nsslapd-accesslog).">Access Log Location</label><input
- class="ds-input" type="text" id="nsslapd-accesslog" size="40"/>
- </div>
- <div>
- <input type="checkbox" class="ds-server-checkbox" id="nsslapd-accesslog-logbuffering"><label
- for="nsslapd-accesslog-logbuffering" class="ds-label" title="Disable access log buffering for faster troubleshooting (nsslapd-accesslog-logbuffering)."> Disable Access Log Buffering</label>
- </div>
- </div>
- <p></p>
- <h4 class="ds-sub-header">Rotation Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-accesslog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-accesslog-maxlogsperdir).">Maximum Number Of Logs</label><input
- class="ds-input" type="text" id="nsslapd-accesslog-maxlogsperdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-accesslog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-accesslog-maxlogsize).">Maximum Log Size (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-accesslog-maxlogsize" size="40"/>
- </div>
- <div>
- <label for="nsslapd-accesslog-logrotationtime" class="ds-config-sub-label" title="Access log rotation time settings (nsslapd-accesslog-logrotationtime).">Create New Log Every...</label><input
- class="ds-input" type="text" id="nsslapd-accesslog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-accesslog-logrotationtimeunit">
- <option>minute</option>
- <option>hour</option>
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-accesslog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"
- title="Minute" id="nsslapd-accesslog-logrotationsyncmin" size="1"/>
- </div>
- </div>
- <p></p>
- <h4 class="ds-sub-header">Deletion Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-accesslog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-accesslog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-accesslog-logmaxdiskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-accesslog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-accesslog-logminfreediskspace).">Free Disk Space (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-accesslog-logminfreediskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-accesslog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-accesslog-logexpirationtime).">Log File is Older Than... </label><input
- class="ds-input" type="text" id="nsslapd-accesslog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-accesslog-logexpirationtimeunit">
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select>
- </div>
- </div>
- <p></p>
- <h4 class="ds-sub-header">Access Logging Levels</h4>
- <hr class="ds-hr-logs">
-
-
- <table class="table table-striped table-bordered table-hover ds-loglevel-table" id="accesslog-level-table">
- <thead>
- <tr>
- <th class="ds-table-checkbox"></th>
- <th>Logging Level</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-accesslog-table" id="accesslog-256" type="checkbox"></td>
- <td>Default Logging</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-accesslog-table" id="accesslog-4"type="checkbox"></td>
- <td>Internal Operations</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-accesslog-table" id="accesslog-512" type="checkbox"></td>
- <td>Entry Access and Referrals</td>
- </tr>
- <tbody>
- </table>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
- <div id="server-audit-log" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Audit Log Settings</h3>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-auditlog-logging-enabled"><label
- for="nsslapd-auditlog-logging-enabled" class="ds-label" title="Enable audit logging (nsslapd-auditlog-logging-enabled)."> Enable Audit Logging</label>
- <div class="ds-expired-div" id="auditlog-attrs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-auditlog" class="ds-config-label" title="The audit log location and name (nsslapd-auditlog).">Audit Log Location</label><input
- class="ds-input" type="text" id="nsslapd-auditlog" size="40"/>
- </div>
- </div>
- <p></p>
- <h4 class="ds-sub-header">Rotation Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-auditlog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-auditlog-maxlogsperdir).">Maximum Number Of Logs</label><input
- class="ds-input" type="text" id="nsslapd-auditlog-maxlogsperdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditlog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-auditlog-maxlogsize).">Maximum Log Size (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditlog-maxlogsize" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditlog-logrotationtime" class="ds-config-sub-label" title="Audit log rotation time settings (nsslapd-auditlog-logrotationtime).">Create New Log Every...</label><input
- class="ds-input" type="text" id="nsslapd-auditlog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditlog-logrotationtimeunit">
- <option>minute</option>
- <option>hour</option>
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-auditlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"
- title="Minute" id="nsslapd-auditlog-logrotationsyncmin" size="1"/>
- </div>
- </div>
- <p></p>
- <h4 class="ds-sub-header">Deletion Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-auditlog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditlog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditlog-logmaxdiskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditlog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditlog-logminfreediskspace).">Free Disk Space (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditlog-logminfreediskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditlog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditlog-logexpirationtime).">Log File is Older Than... </label><input
- class="ds-input" type="text" id="nsslapd-auditlog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditlog-logexpirationtimeunit">
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select>
- </div>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
- <!-- Auditfail logging -->
- <div id="server-auditfail-log" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Audit Failure Log Settings</h3>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-auditfaillog-logging-enabled"><label
- for="nsslapd-auditfaillog-logging-enabled" class="ds-label" title="Enable audit failure logging (nsslapd-auditfaillog-logging-enabled)."> Enable Audit Failure Logging</label>
- <div class="ds-expired-div" id="auditfaillog-attrs">
- <label for="nsslapd-auditfaillog" class="ds-config-label" title="The audit failure log location and name (nsslapd-auditfaillog).">Audit Failure Log Location</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog" size="40"/>
- <p></p>
- <h4 class="ds-sub-header">Rotation Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-auditfaillog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-auditfaillog-maxlogsperdir).">Maximum Number Of Logs</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-maxlogsperdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditfaillog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-auditfaillog-maxlogsize).">Maximum Log Size (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-maxlogsize" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditfaillog-logrotationtime" class="ds-config-sub-label" title="Audit failure log rotation time settings (nsslapd-auditlog-logrotationtime).">Create New Log Every...</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditfaillog-logrotationtimeunit">
- <option>minute</option>
- <option>hour</option>
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-auditfaillog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"
- title="Minute" id="nsslapd-auditfaillog-logrotationsyncmin" size="1"/>
- </div>
- </div>
- <h4 class="ds-sub-header">Deletion Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-auditfaillog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditfaillog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-logmaxdiskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditfaillog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditfaillog-logminfreediskspace).">Free Disk Space (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-logminfreediskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-auditfaillog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditfaillog-logexpirationtime).">Log File is Older Than... </label><input
- class="ds-input" type="text" id="nsslapd-auditfaillog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditfaillog-logexpirationtimeunit">
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select>
- </div>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
- <!-- Error logging -->
- <div id="server-errors-log" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Error Log Settings</h3>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-errorlog-logging-enabled" checked><label
- for="nsslapd-errorlog-logging-enabled" class="ds-label" title="Enable error logging (nsslapd-errorlog-logging-enabled)."> Enable Error Logging</label>
- <div class="ds-expired-div" id="errorlog-attrs">
- <label for="nsslapd-errorlog" class="ds-config-label" title="The errors log location and name (nsslapd-errorlog).">Errors Log Location</label><input
- class="ds-input" type="text" id="nsslapd-errorlog" size="40"/>
- <h4 class="ds-sub-header">Rotation Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-errorlog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-errorlog-maxlogsperdir).">Maximum Number Of Logs</label><input
- class="ds-input" type="text" id="nsslapd-errorlog-maxlogsperdir" size="40"/>
- </div>
- <div>
- <label for="nsslapd-errorlog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">Maximum Log Size (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-errorlog-maxlogsize" size="40"/>
- </div>
- <div>
- <label for="nsslapd-errorlog-logrotationtime" class="ds-config-sub-label" title="Errors log rotation time settings (nsslapd-errorlog-logrotationtime).">Create New Log Every...</label><input
- class="ds-input" type="text" id="nsslapd-errorlog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-errorlog-logrotationtimeunit">
- <option>minute</option>
- <option>hour</option>
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-errorlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"
- title="Minute" id="nsslapd-errorlog-logrotationsyncmin" size="1"/>
- </div>
- </div>
-
- <h4 class="ds-sub-header">Deletion Policy</h4>
- <hr class="ds-hr-logs">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-errorlog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-errorlog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-errorlog-logmaxdiskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-errorlog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-errorlog-logminfreediskspace).">Free Disk Space (in MB)</label><input
- class="ds-input" type="text" id="nsslapd-errorlog-logminfreediskspace" size="40"/>
- </div>
- <div>
- <label for="nsslapd-errorlog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-errorlog-logexpirationtime).">Log File is Older Than... </label><input
- class="ds-input" type="text" id="nsslapd-errorlog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-errorlog-logexpirationtimeunit">
- <option>day</option>
- <option>week</option>
- <option>month</option>
- </select>
- </div>
- </div>
-
- <h4 class="ds-sub-header">Error Logging Levels</h4>
- <hr class="ds-hr-logs">
- <table class="table table-striped table-bordered table-hover ds-loglevel-table" id="errorlog-level-table">
- <thead>
- <tr>
- <th class="ds-table-checkbox"></th>
- <th>Logging Level</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-1" type="checkbox"></td>
- <td>Trace Function Calls</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-2" type="checkbox"></td>
- <td>Packet Handling</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-4" type="checkbox"></td>
- <td>Heavy Trace Output</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-8" type="checkbox"></td>
- <td>Connection Management</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-16" type="checkbox"></td>
- <td>Packets Sent/Received</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-32" type="checkbox"></td>
- <td>Search Filter Processing</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-64" type="checkbox"></td>
- <td>Config File Processing</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-128" type="checkbox"></td>
- <td>Access Control List Processing</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-2048" type="checkbox"></td>
- <td>Log Entry Parsing</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-4096" type="checkbox"></td>
- <td>Housekeeping</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-8192" type="checkbox"></td>
- <td>Replication</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-32768" type="checkbox"></td>
- <td>Entry Cache</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-65536" type="checkbox"></td>
- <td>Plug-ins</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-262144" type="checkbox"></td>
- <td>Access Control Summary</td>
- </tr>
- <tr>
- <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-1048576" type="checkbox"></td>
- <td>Nunc-Stans Connection Logging</td>
- </tr>
- </tbody>
- </table>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
- <!--
- Tuning
- -->
- <div id="server-tuning" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Server Tuning & Limits</h3>
- <div class="ds-container">
- <!-- Attribute list -->
- <div class="ds-inline">
- <div>
- <label for="nsslapd-threadnumber" class="ds-config-label" title="The number of worker threads (nsslapd-threadnumber).">Number Of Worker Threads</label><input
- class="ds-input" type="text" id="nsslapd-threadnumber" size="15"/>
- </div>
- <div>
- <label for="nsslapd-maxdescriptors" class="ds-config-label" title="The maximum number of file descriptors the server will use (nsslapd-maxdescriptors).">Maximum File Descriptors</label><input
- class="ds-input" type="text" id="nsslapd-maxdescriptors" size="15"/>
- </div>
- <div>
- <label for="nsslapd-timelimit" class="ds-config-label" title="The maximum number of seconds allocated for a search request (nsslapd-timelimit).">Search Time Limit</label><input
- class="ds-input" type="text" id="nsslapd-timelimit" size="15"/>
- </div>
- <div>
- <label for="nsslapd-sizelimit" class="ds-config-label" title="The maximum number of entries to return from a search operation (nsslapd-sizelimit).">Search Size Limit</label><input
- class="ds-input" type="text" id="nsslapd-sizelimit" size="15"/>
- </div>
- <div>
- <label for="nsslapd-pagedsizelimit" class="ds-config-label" title="The maximum number of entries to return from a paged search operation (nsslapd-pagedsizelimit).">Paged Search Size Limit</label><input
- class="ds-input" type="text" id="nsslapd-pagedsizelimit" size="15"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <label for="nsslapd-idletimeout" class="ds-config-label" title=
- "Sets the amount of time in seconds after which an idle LDAP client connection is closed by the server (nsslapd-idletimeout).">Idle Connection Timeout</label><input
- class="ds-input" type="text" id="nsslapd-idletimeout" size="15"/>
- </div>
- <div>
- <label for="nsslapd-ioblocktimeout" class="ds-config-label" title=
- "Sets the amount of time in milliseconds after which the connection to a stalled LDAP client is closed (nsslapd-ioblocktimeout).">I/O Block Timeout</label><input
- class="ds-input" type="text" id="nsslapd-ioblocktimeout" size="15"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ndn-cache-enabled" checked><label
- for="nsslapd-ndn-cache-enabled" class="ds-label" title=
- "Enable the normalized DN cache (nsslapd-ndn-cache-enabled)."> Enable Normalized DN Cache</label>
- </div>
- <div>
- <label for="nsslapd-ndn-cache-max-size" class="ds-config-sub-label" title="The size of the normalized DN cache in bytes (nsslapd-ndn-cache-max-size).">Normalized DN Cache Size</label><input
- class="ds-input" type="text" id="nsslapd-ndn-cache-max-size" size="15"/>
- </div>
- </div>
- </div>
- <p></p>
-
- <button class="accordion ds-accordion" id="tuning-config-accordion" type="button">► Show Advanced Settings </button>
- <div class="ds-accordion-panel">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-outbound-ldap-io-timeout" class="ds-config-label" title=
- "Sets the I/O wait time for all outbound LDAP connections (nsslapd-outbound-ldap-io-timeout).">Outbound IO Timeout</label><input
- class="ds-input" type="text" id="nsslapd-outbound-ldap-io-timeout" size="15"/>
- </div>
- <div>
- <label for="nsslapd-maxbersize" class="ds-config-label" title="The maximum size in bytes allowed for an incoming message (nsslapd-maxbersize).">Maximum BER Size</label><input
- class="ds-input" type="text" id="nsslapd-maxbersize" size="15"/>
- </div>
- <div>
- <label for="nsslapd-maxsasliosize" class="ds-config-label" title="The maximum allowed SASL IO packet size that the server will accept (nsslapd-maxsasliosize).">
- Maximum SASL IO Size</label><input class="ds-input" type="text" id="nsslapd-maxsasliosize" size="15"/>
- </div>
- <div>
- <label for="nsslapd-listen-backlog-size" class="ds-config-label" title=
- "The maximum length for how long the connection queue for the socket can grow before refusing connections (nsslapd-listen-backlog-size).">Listen Backlog Size</label><input
- class="ds-input" type="text" id="nsslapd-listen-backlog-size" size="15"/>
- </div>
- <div>
- <label for="nsslapd-max-filter-nest-level" class="ds-config-label" title=
- "Sets how deep a nested search filter is analysed (nsslapd-max-filter-nest-level).">Maximum Nested Filter Level</label><input
- class="ds-input" type="text" id="nsslapd-max-filter-nest-level" size="15"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline ds-margin-top">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ignore-virtual-attrs" checked><label
- for="nsslapd-ignore-virtual-attrs" class="ds-label" title=
- "Disable the virtual attribute lookup in a search entry (nsslapd-ignore-virtual-attrs)."> Disable Virtual Attribute Lookups</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-connection-nocanon" checked><label
- for="nsslapd-connection-nocanon" class="ds-label" title=
- "Disable DNS reverse entries for outgoing connections (nsslapd-connection-nocanon)."> Disable Reverse DNS Lookups</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-enable-turbo-mode" checked><label
- for="nsslapd-enable-turbo-mode" class="ds-label" title=
- "Sets the worker threads to continuously read a connection without passing it back to the polling mechanism. (nsslapd-enable-turbo-mode)."> Enable Connection Turbo Mode</label>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
-
- <!--
- LDAPI & Autobind Settings
- -->
- <div id="server-ldapi" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">LDAPI & Autobind Settings</h3>
- <div class="ldapi-attrs ds-inline" hidden>
- <div>
- <label for="nsslapd-ldapifilepath" class="ds-config-label" title="The Unix socket file (nsslapd-ldapifilepath). The UI requires this exact path so it is a read-only setting.">LDAPI Socket File Path</label><input
- class="ds-input" type="text" id="nsslapd-ldapifilepath" size="35" disabled/>
- </div>
- <div class="ds-inline">
- <div class="autobind-attrs">
- <div>
- <label for="nsslapd-ldapimaprootdn" class="ds-config-label" title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn). The UI requires this to be set to the current root DN so it is a read-only setting">DN to map "root" To</label><input
- class="ds-input" type="text" id="nsslapd-ldapimaprootdn" disabled size="35"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ldapimaptoentries"><label
- for="nsslapd-ldapimaptoentries" class="ds-label" title="Map regular system users to Directory Server entries (nsslapd-ldapimaptoentries).">Map System User to Database Entry</label>
- </div>
- </div>
- <div class="autobind-entry-attrs" hidden>
- <div>
- <label for="nsslapd-ldapiuidnumbertype" class="ds-config-indent-sm-label" title=
- "The Directory Server attribute to map system UIDs to user entries (nsslapd-ldapiuidnumbertype).">LDAPI UID Number Type</label><input
- class="ds-input" type="text" id="nsslapd-ldapiuidnumbertype" placeholder="e.g. uidNumber" size="35"/>
- </div>
- <div>
- <label for="nsslapd-ldapigidnumbertype" class="ds-config-indent-sm-label" title=
- "The Directory Server attribute to map system GUIDs to user entries (nsslapd-ldapigidnumbertype).">LDAPI UID Number Type</label><input
- class="ds-input" type="text" id="nsslapd-ldapigidnumbertype" placeholder="e.g. gidNumber" size="35"/>
- </div>
- <div>
- <label for="nsslapd-ldapientrysearchbase" class="ds-config-indent-sm-label" title=
- "The subtree to search for user entries to use for autobind. (nsslapd-ldapientrysearchbase).">LDAPI Entry Search Base</label><input
- class="ds-input" type="text" id="nsslapd-ldapientrysearchbase" size="35"/>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
-
-
- <!-- Modals/Popups/Wizards -->
-
-
- <!-- Create SASL Mapping -->
- <div class="modal fade" id="sasl-map-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="sasl-header" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title" id="sasl-header">Create SASL Mapping</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <p class="ds-modal-error"></p>
- <div>
- <label for="sasl-map-name" class="ds-config-label" title="SASL mapping name">
- SASL Mapping Name</label><input class="ds-input sasl-input" type="text" id="sasl-map-name"/>
- </div>
- <div>
- <label for="sasl-map-regex" class="ds-config-label" title="SASL mapping regular expression">
- SASL Mapping Regex</label><input class="ds-input sasl-input" type="text" id="sasl-map-regex"/><label
- for="test-sasl-regex" class="ds-left-margin">Test Regex</label><input type="checkbox"
- class="ds-left-margin ds-margin-top" id="test-sasl-regex">
- </div>
- <div id="sasl-test-div" hidden>
- <div class="ds-inline">
- <div>
- <label for="sasl-test-regex" class="ds-config-label" title="SASL mapping name">
- </label><input class="ds-input sasl-test-input" placeholder="Enter text to test" type="text" id="sasl-test-regex-string"/><button
- type="button" class="btn btn-default ds-trailing-btn" id="sasl-test-regex-btn">Test It</button>
- <hr>
- </div>
- </div>
- </div>
- <div>
- <label for="sasl-map-base" class="ds-config-label" title=
- "The search base or a specific entry DN to match against the constructed DN">
- SASL Mapping Base</label><input class="ds-input sasl-input" type="text" id="sasl-map-base"/>
- </div>
- <div>
- <label for="sasl-map-filter" class="ds-config-label" title="SASL mapping filter">
- SASL Mapping Filter</label><input class="ds-input sasl-input" type="text" id="sasl-map-filter"/>
- </div>
- <div>
- <label for="sasl-map-priority" class="ds-config-label" title=
- "SASL mapping priority. 1 is highest priority, and 100 is lowest">
- SASL Mapping Priority</label><input class="ds-input sasl-input" type="text" id="sasl-map-priority"/>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="sasl-map-save">Save</button>
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Create Local Password Policy -->
- <div class="modal fade" id="local-pwp-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="local-pwp-header" aria-hidden="true">
- <div class="modal-dialog">
- <div class="ds-modal-wide modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title" id="local-pwp-header">Create Local Password Policy</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label title="Create a subtree level password policy" for="subtree-pwp-radio"><input
- class="ds-radio pwp-role" type="radio" id="subtree-pwp-radio" name="pwp-role" value="subtree" checked="checked"> Subtree Password Policy</label>
- </div>
- <div>
- <label for="user-pwp-radio" title="Create a user level password policy"><input
- class="ds-radio pwp-role" type="radio" id="user-pwp-radio" name="pwp-role" value="user"> User Password Policy</label>
- </div>
- <div>
- <div>
- <label for="local-entry-dn" class="ds-config-label ds-pwp-input" title="The entry to apply a local policy to.">Entry for Password Policy</label>
- </div>
- <div class="ds-inline">
- <input class="ds-input ds-pwp-input" type="text" id="local-entry-dn" size="70" required />
- </div>
- <hr>
- <div>
-
- </div>
- </div>
- <div class="ds-container">
- <div class="ds-split">
- <h4>General Settings</h4>
- <hr class="ds-hr">
- <div class="ds-inline">
- <div>
- <label for="local-passwordstoragescheme" class="ds-config-label-lrg" title="Set the password storage scheme (passwordstoragescheme).">Password Storage Scheme</label><select
- class="btn btn-default dropdown" id="local-passwordstoragescheme">
- <option>PBKDF2_SHA256</option>
- <option>SSHA512</option>
- <option>SSHA384</option>
- <option>SSHA256</option>
- <option>SSHA</option>
- <option>NS-MTA-MD5</option>
- <option>MD5</option>
- <option>SMD5</option>
- <option>CRYPT-MD5</option>
- <option>CRYPT-SHA512</option>
- <option>CRYPT-SHA256</option>
- <option>CRYPT</option>
- <option>CLEAR</option>
- </select>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordtrackupdatetime"><label
- for="local-passwordtrackupdatetime" class="ds-label" title=
- "Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime)."> Track Password Update Time</label>
- </div>
- <div>
- <label for="local-passwordadmindn" title="The DN for a password administrator or administrator group (passwordAdminDN)">Password Administrator</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordadmindn" size="40"/>
- </div>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-split">
- <h4>User Password Settings</h4>
- <hr class="ds-hr">
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordchange"><label
- for="local-passwordchange" class="ds-label" title="Allow user's to change their passwords (passwordChange)."> Allow Users To Change Their Passwords</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordmustchange"><label
- for="local-passwordmustchange" class="ds-label" title="User must change its password after its been reset by an administrator (passwordMustChange).">User Must Change Password After Reset</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordhistory"><label
- for="local-passwordhistory" class="ds-label" title="Maintain a password history (passwordHistory).">Keep Password History</label>
- </div>
- <div>
- <input class="ds-history-input ds-pwp-input" type="text" id="local-passwordinhistory" size="2"/>Passwords In History
- <label for="local-passwordminage" class="ds-minage-label" title="Indicates the number of seconds that must pass before a user can change their password. (passwordMinAge)">Allow Password Changes (in seconds)</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordminage" size="10"/>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-container">
- <div class="ds-split">
- <h4><br>Password Expiration Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordexp"><label
- for="local-passwordexp" class="ds-label" title="Enable a password expiration policy (passwordExp).">Enable Password Expiration</label>
- <div class="ds-expired-div" id="local-expiration-attrs">
- <label for="local-passwordmaxage" class="ds-expire-label" title="The server's local hostname (passwordMaxAge).">Password Expiration Time (in seconds)</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordmaxage" size="5"/>
- <label for="local-passwordgracelimit" class="ds-expire-label" title="The server's local hostname (passwordGraceLimit).">Allowed Logins After Password Expires</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordgracelimit" size="5"/>
- <label for="local-passwordwarning" class="ds-expire-label" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">Send Password Expiring Warning (in seconds)</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordwarning" size="5"/>
- <input type="checkbox" class="ds-send-expiring-checkbox ds-pwp-checkbox" id="local-passwordsendexpiringtime"><label
- for="local-passwordsendexpiringtime" class="ds-label" title=
- "Always return a password expiring control when requested (passwordSendExpiringTime).">Always Send <i>Password Expiring Control</i></label>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-split">
- <h4><br>Account Lockout Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordlockout"><label
- for="local-passwordlockout" class="ds-label" title="Enable account lockout (passwordLockout).">Enable Account Lockout</label>
- <div class="ds-expired-div" id="local-lockout-attrs">
- <label for="local-passwordmaxfailure" class="ds-expire-label" title=
- "The maximum number of failed logins before account gets locked (passwordMaxFailure).">Number of Failed Logins to Lockout Account</label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordmaxfailure" size="5"/>
- <label for="local-passwordresetfailurecount" class="ds-expire-label" title=
- "The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">Time Before Failure Count Reset </label><input
- class="ds-input ds-pwp-input" type="text" id="local-passwordresetfailurecount" size="5"/>
- <div>
- <label title="Lock out the account forever (passwordUnlock)." for="local-passwordunlock" ><input
- class="ds-radio" type="radio" id="local-passwordunlock" value="passwordunlock" name="account-lockout" checked="checked"> Lockout Account Forever</label>
- <label title="The number of seconds before account gets unlocked (passwordLockoutDuration)." for="local-passwordlockoutduration"><input
- class="ds-radio" type="radio" name="account-lockout" value="passwordlockoutduration"> Time Until Account Unlocked <input
- class="ds-input ds-pwp-input" type="text" id="local-passwordlockoutduration" size="5"/></label>
- </div>
- </div>
- <p></p>
- </div>
- </div>
-
- <h4><br>Password Syntax Settings</h4>
- <hr class="ds-hr">
- <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordchecksyntax"><label
- for="local-passwordchecksyntax" class="ds-label" title="Enable account lockout (passwordCheckSyntax).">Check Password Syntax</label>
- <div class="ds-container ds-expired-div" id="local-syntax-attrs">
- <div class="ds-split">
- <label for="local-passwordminlength" class="ds-expire-label" title=
- "The minimum number of characters in the password (passwordMinLength).">Password Minimum Length </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminlength" size="5"/>
- <label for="local-passwordmindigits" class="ds-expire-label" title=
- "Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">Minimum Digit Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmindigits" size="5"/>
- <label for="local-passwordminalphas" class="ds-expire-label" title=
- "Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">Minimum Alpha Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminalphas" size="5"/>
- <label for="local-passwordminuppers" class="ds-expire-label" title=
- "Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">Minimum Uppercase Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminuppers" size="5"/>
- <label for="local-passwordminlowers" class="ds-expire-label" title=
- "Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">Minimum Lowercase Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminlowers" size="5"/>
- <label for="local-passwordminspecials" class="ds-expire-label" title=
- "Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">Minimum Special Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminspecials" size="5"/>
- <label for="local-passwordmin8bit" class="ds-expire-label" title=
- "Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">Minimum 8-bit Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmin8bit" size="5"/>
- <label for="local-passwordmincategories" class="ds-expire-label" title=
- "The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">Minimum Required Character Categories </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmincategories" size="5"/>
- <label for="local-passwordmintokenlength" class="ds-expire-label" title=
- "The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">Minimum Token Length </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmintokenlength" size="5"/>
- </div>
- <div class="ds-split">
- <label for="local-passwordmaxrepeats" class="ds-expire-label" title=
- "The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxrepeats"/>
- <label for="local-passwordmaxsequence" class="ds-expire-label" title=
- "The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">Maximum Character Sequences </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxsequence"/>
- <label for="local-passwordmaxseqsets" class="ds-expire-label" title=
- "The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">Maximum Character Sequence Sets </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxseqsets"/>
- <label for="local-passwordmaxclasschars" class="ds-expire-label" title=
- "The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">Maximum Consecutive Chars Per Char Class </label><input
- class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxclasschars"/>
- <label for="local-passwordpalindrome" class="ds-expire-label" title=
- "Reject a password if it is a palindrome (passwordPalindrome).">Reject Passwords that Are Palindromes </label><input
- class="ds-margin-top ds-pwp-checkbox" type="checkbox" id="local-passwordpalindrome"/>
- <label for="local-passworddictcheck" class="ds-expire-label" title=
- "Check the password against the system's CrackLib dictionary (passwordDictCheck).">Check Password Contains Dictionary Word </label><input
- class="ds-margin-top ds-pwp-checkbox" type="checkbox" id="local-passworddictcheck"/>
- </div>
- </div>
- <div class="ds-margin-left-sm ds-margin-top">
- <div>
- <label for="passwordbadwords" title=
- "A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords).">Reject Passwords That Contain These Words </label><input
- class="ds-input-auto" type="text" id="local-passwordbadwords"/>
- </div>
- <div class="ds-margin-top">
- <label for="passworduserattributes" title=
- "A space-separated list of entry attributes to compare to the new password (passwordUserAttributes).">Entry Attributes To Compare </label><input
- class="ds-input-auto" type="text" id="local-passworduserattributes"/>
- </div>
- </div>
- </form>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="local-pwp-save">Save</button>
- </div>
- </div>
- </div>
-
-</div>
diff --git a/src/cockpit/389-console/src/servers.js b/src/cockpit/389-console/src/servers.js
deleted file mode 100644
index 9423a5d..0000000
--- a/src/cockpit/389-console/src/servers.js
+++ /dev/null
@@ -1,1808 +0,0 @@
-
-var sasl_action_html =
- '<div class="dropdown">' +
- '<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">' +
- 'Choose Action...' +
- '<span class="caret"></span>' +
- '</button>' +
- '<ul class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="dropdownMenu1">' +
- '<li role=""><a role="menuitem" class="sasl-edit-btn" tabindex="2" href="#">Edit Mapping</a></li>' +
- '<li role=""><a role="menuitem" class="sasl-del-btn" tabindex="1" href="#">Delete Mapping</a></li>' +
- '</ul>' +
- '</div>';
-
-var local_pwp_html =
- '<div class="dropdown" >' +
- '<button class="btn btn-default dropdown-toggle" type="button" id="menu1" data-toggle="dropdown">Choose Action...' +
- '<span class="caret"></span></button>' +
- '<ul id="test-drop" class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="menu1">' +
- '<li role="policy-role"><a role="pwpolicy" tabindex="0" class="edit-local-pwp" href="#">View/Edit Policy</a></li>' +
- '<li role="policy-role"><a role="pwpolicy" tabindex="-1" class="delete-local-pwp" href="#">Delete Policy</a></li>' +
- '</ul>' +
- '</div>';
-
-var create_full_template =
- "[general]\n" +
- "config_version = 2\n" +
- "defaults = 999999999\n" +
- "full_machine_name = FQDN\n" +
- "selinux = True\n" +
- "strict_host_checking = True\n" +
- "systemd = True\n" +
- "[slapd]\n" +
- "backup_dir = /var/lib/dirsrv/slapd-{instance_name}/bak\n" +
- "bin_dir = /usr/bin\n" +
- "cert_dir = /etc/dirsrv/slapd-{instance_name}\n" +
- "config_dir = /etc/dirsrv/slapd-{instance_name}\n" +
- "data_dir = /usr/share\n" +
- "db_dir = /var/lib/dirsrv/slapd-{instance_name}/db\n" +
- "user = dirsrv\n" +
- "group = dirsrv\n" +
- "initconfig_dir = /etc/sysconfig\n" +
- "inst_dir = /usr/lib64/dirsrv/slapd-{instance_name}\n" +
- "instance_name = localhost\n" +
- "ldif_dir = /var/lib/dirsrv/slapd-{instance_name}/ldif\n" +
- "lib_dir = /usr/lib64\n" +
- "local_state_dir = /var\n" +
- "lock_dir = /var/lock/dirsrv/slapd-{instance_name}\n" +
- "log_dir = /var/log/dirsrv/slapd-{instance_name}\n" +
- "port = PORT\n" +
- "prefix = /usr\n" +
- "root_dn = ROOTDN\n" +
- "root_password = ROOTPW\n" +
- "run_dir = /var/run/dirsrv\n" +
- "sbin_dir = /usr/sbin\n" +
- "schema_dir = /etc/dirsrv/slapd-{instance_name}/schema\n" +
- "secure_port = SECURE_PORT\n" +
- "self_sign_cert = True\n" +
- "sysconf_dir = /etc\n" +
- "tmp_dir = /tmp\n";
-
-var create_inf_template =
- "[general]\n" +
- "config_version = 2\n" +
- "full_machine_name = FQDN\n\n" +
- "[slapd]\n" +
- "user = dirsrv\n" +
- "group = dirsrv\n" +
- "instance_name = INST_NAME\n" +
- "port = PORT\n" +
- "root_dn = ROOTDN\n" +
- "root_password = ROOTPW\n" +
- "secure_port = SECURE_PORT\n" +
- "self_sign_cert = SELF_SIGN\n";
-
-
-var sasl_table;
-var pwp_table;
-
-// log levels
-var accesslog_levels = [4, 256, 512]
-var errorlog_levels = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 2048,
- 4096, 8192, 16384, 32768, 65536, 262144, 1048576];
-
-function load_server_config() {
- var mark = document.getElementById("server-config-title");
- mark.innerHTML = "Configuration for server: <b>" + server_id + "</b>";
-}
-
-function clear_sasl_map_form () {
- $(".ds-modal-error").hide();
- $(".sasl-input").val("");
- $(".sasl-input").css("border-color", "initial");
-}
-
-function clear_local_pwp_form () {
- $(".ds-pwp-input").val("");
- $(".ds-pwp-checkbox").prop('checked', false);
- $("#local-passwordstoragescheme").prop('selectedIndex',0);
- $("#subtree-pwp-radio").attr('disabled', false);
- $("#user-pwp-radio").attr('disabled', false);
- $("#local-entry-dn").attr('disabled', false);
- $("#local-pwp-header").html("<b>Create Local Password Policy</b>");
-}
-
-function clear_inst_input() {
- // Reset the color of the fields
- $(".ds-inst-input").css("border-color", "initial");
-}
-
-function clear_inst_form() {
- $(".ds-modal-error").hide();
- $("#create-inst-serverid").val("");
- $("#create-inst-port").val("389");
- $("#create-inst-secureport").val("636");
- $("#create-inst-rootdn").val("cn=Directory Manager");
- $("#rootdn-pw").val("");
- $("#rootdn-pw-confirm").val("");
- $("#backend-suffix").val("dc=example,dc=com");
- $("#backend-name").val("userRoot");
- $("#create-sample-entries").prop('checked', false);
- $("#create-inst-tls").prop('checked', true);
- $(".ds-inst-input").css("border-color", "initial");
-}
-
-/*
- * Validate the val and add it to the argument list for "dsconf"
- *
- * arg_list - array of options for dsconf
- * valtype - value type: "num" or "dn"
- * val - the new value
- * def_val - set this default is there is no new value("")
- * edit - if we are editing a value, we accept ("") and do not ignore it
- * attr - the dict key(its also the element ID)
- * arg - the CLI argument (--pwdlen)
- * msg - error message to display when things go wrong
- *
- * Return false on validation failure
- */
-function add_validate_arg (arg_list, valtype, val, def_val, edit, attr, arg, msg) {
- if ( val != "" || (edit && localpwp_values[attr] !== undefined && val != localpwp_values[attr])) {
- if (val == "") {
- val = def_val;
- }
- if ( valtype == "num" && !valid_num(val) ) {
- popup_msg("Error", "\"" + msg + "\" value \"" + val + "\" is not a number");
- return false;
- } else if (valtype == "dn" && !valid_dn(val) && val != "" ) {
- popup_msg("Error", "\"" + msg + "\" value \"" + val + "\" is not a DN (distinguished name)");
- return false;
- }
- arg_list.push(arg + '=' + val);
- return true;
- }
- // No change, no error
- return true;
-}
-
-function get_and_set_config () {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','config', 'get'];
- console.log("Loading server configuration.");
- log_cmd('get_and_set_config', 'Get server configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var obj = JSON.parse(data);
- // Reset tables before populating them
- $(".ds-accesslog-table").prop('checked', false);
- $(".ds-errorlog-table").prop('checked', false);
- config_values = {};
- update_progress();
-
- for (var attr in obj['attrs']) {
- var val = obj['attrs'][attr][0];
- attr = attr.toLowerCase();
- if( $('#' + attr).length ) {
- // We have an element that matches, set the html and store the original value
- $("#" + attr).val(val); // Always set value, then check if its something else
- if (val == "on") {
- $("#" + attr).prop('checked', true);
- $("#" + attr).trigger('change');
- } else if (val == "off") {
- $("#" + attr).prop('checked', false);
- $("#" + attr).trigger('change');
- }
- config_values[attr] = val;
-
- // Handle password confirm inputs
- if (attr == "nsslapd-rootpw"){
- $("#nsslapd-rootpw-confirm").val(val);
- }
- }
-
- // Do the log level tables
- if (attr == "nsslapd-accesslog-level") {
- config_values[attr] = val;
- var level_val = parseInt(val);
- for ( var level in accesslog_levels ) {
- if (level_val & accesslog_levels[level]) {
- $("#accesslog-" + accesslog_levels[level].toString()).prop('checked', true);
- }
- }
- } else if (attr == "nsslapd-errorlog-level") {
- config_values[attr] = val;
- var level_val = parseInt(val);
- for ( var level in errorlog_levels ) {
- if (level_val & errorlog_levels[level]) {
- $("#errorlog-" + errorlog_levels[level].toString()).prop('checked', true);
- }
- }
- }
- }
- console.log("Finished loading server configuration.");
- config_loaded = 1;
- check_inst_alive();
- }).fail(function(data) {
- popup_err("Error", "Failed to get config\n" + data.message);
- check_inst_alive(1);
- });
-}
-
-function update_suffix_dropdowns () {
- var dropdowns = ['local-pwp-suffix', 'select_repl_suffix', 'select-repl-cfg-suffix',
- 'select-repl-agmt-suffix', 'select-repl-winsync-suffix',
- 'monitor-repl-backend-list'];
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','backend', 'list', '--suffix'];
- log_cmd('update_suffix_dropdowns', 'Get suffix list', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- // Clear all the dropdowns first
- for (var idx in dropdowns) {
- $("#" + dropdowns[idx]).empty();
- }
- // Update dropdowns
- var obj = JSON.parse(data);
- for (var idx in obj['items']) {
- for (var list in dropdowns){
- $("#" + dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>');
- }
- }
- update_progress();
- }).fail(function(data) {
- if (quiet === undefined) {
- popup_err("Error", "Failed to get backend suffix list\n" + data.message);
- }
- check_inst_alive(1);
- });
-}
-
-function get_and_set_localpwp (quiet) {
- // Now populate the table
- console.log("Loading local password policies...");
- var suffix = $('#local-pwp-suffix').val();
- if (suffix == null){
- return;
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','localpwp', 'list', suffix ];
- log_cmd('get_and_set_localpwp', 'Get local password policies', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var obj = JSON.parse(data);
- update_progress();
- // Empty table
- pwp_table.clear().draw();
-
- // Populate table
- for (var idx in obj['items']) {
- pwp_table.row.add([
- obj['items'][idx][0],
- obj['items'][idx][1],
- local_pwp_html,]
- ).draw( false );
- }
- console.log("Finished loading password policies.");
- }).fail(function(data) {
- check_inst_alive(0);
- });
-}
-
-function get_and_set_sasl () {
- // First empty the table
- console.log("Loading SASL configuration...");
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'list'];
- log_cmd('get_and_set_sasl', 'Get SASL mappings', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var obj = JSON.parse(data);
- update_progress();
- sasl_table.clear().draw();
- for (var idx in obj['items']) {
- var map_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'get', obj['items'][idx] ];
- log_cmd('get_and_set_sasl', 'Get SASL mapping', map_cmd);
- cockpit.spawn(map_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var map_obj = JSON.parse(data);
- // Update html table
- var sasl_priority = '100';
- if ( map_obj['attrs'].hasOwnProperty('nssaslmappriority') ){
- sasl_priority = map_obj['attrs'].nssaslmappriority
- }
- sasl_table.row.add( [
- map_obj['attrs'].cn,
- map_obj['attrs'].nssaslmapregexstring,
- map_obj['attrs'].nssaslmapbasedntemplate,
- map_obj['attrs'].nssaslmapfiltertemplate,
- sasl_priority,
- sasl_action_html
- ] ).draw( false );
- });
- }
- console.log("Finished loading SASL configuration.");
- }).fail(function(data) {
- popup_err("Failed to SASL configuration", data.message);
- check_inst_alive(1);
- });
-}
-
-function apply_mods(mods) {
- let mod = mods.pop();
-
- if (!mod) {
- return 0; /* all done*/
- }
- let cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'replace'];
- cmd.push(mod.attr + "=" + mod.val);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- config_values[mod.attr] = mod.val;
- // Continue with next mods (if any))
- apply_mods(mods);
- })
- .fail(function(err) {
- var err_obj = JSON.parse(err);
- popup_err("Failed to update attribute: " + mod.attr, err_obj.info);
- // Reset HTML for remaining values that have not been processed
- $("#" + mod.attr).val(config_values[mod.attr]);
- for (remaining in mods) {
- $("#" + remaining.attr).val(config_values[remaining.attr]);
- }
- check_inst_alive(0);
- return -1; // Stop on error
- });
-}
-
-function delete_mods(mods) {
- let mod = mods.pop();
-
- if (!mod) {
- return 0; /* all done*/
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'delete', mod.attr];
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).then(function() {
- config_values[mod.attr] = "";
- // Continue with next mods (if any))
- delete_mods(mods);
- }, function(ex, data) {
- var err_obj = JSON.parse(data);
- popup_err("Failed to delete attribute: " + mod.attr, err_obj.info);
- // Reset HTML for remaining values that have not been processed
- $("#" + mod.attr).val(config_values[mod.attr]);
- for (remaining in mods) {
- $("#" + remaining.attr).val(config_values[remaining.attr]);
- }
- check_inst_alive(0);
- return -1; // Stop on error
- });
-}
-
-function save_config() {
- // Loop over current config_values check for differences
- let mod_list = [];
- let del_list = [];
- for (var attr in config_values) {
- var mod = {};
- if ( $("#" + attr).is(':checkbox')) {
- // Handle check boxes
- if ( $("#" + attr).is(":checked")) {
- if (config_values[attr] != "on") {
- mod['attr'] = attr;
- mod['val'] = "on";
- mod_list.push(mod);
- }
- } else {
- // Not checked
- if (config_values[attr] != "off") {
- mod['attr'] = attr;
- mod['val'] = "off";
- mod_list.push(mod);
- }
- }
- } else {
- // Normal input
- var val = $("#" + attr).val();
- // But first check for rootdn-pw changes and check confirm input matches
- if (attr == "nsslapd-rootpw") {
- if (val != config_values[attr] || val != $("#nsslapd-rootpw-confirm").val()) {
- // Password change, make sure passwords match
- if (val != $("#nsslapd-rootpw-confirm").val()){
- popup_msg("Passwords do not match!", "The Directory Manager passwords do not match, please correct before saving again.");
- return;
- }
- }
- if (val.length < 8) {
- popup_msg("Password is too short!", "The Directory Manager password must be at least 8 characters long.");
- $("#nsslapd-rootpw").val(config_values[attr]);
- $("#nsslapd-rootpw-confirm").val(config_values[attr]);
- return;
- }
- }
-
- if (attr == "nsslapd-port") {
- if (!valid_port(val)) {
- popup_msg("Port number is not valid");
- $("#nsslapd-port").val(config_values[attr]);
- }
- }
-
- if (attr.indexOf("logrotationsynchour") != -1) {
- if (!valid_num(val) || val < 0 || val > 23) {
- popup_msg("Invalid value", "You must use a number between 0 - 23 for: " + attr);
- $("#" + attr).val(config_values[attr])
- return;
- }
- }
- if (attr.indexOf("logrotationsyncmin") != -1) {
- if (!valid_num(val) || val < 0 || val > 59){
- popup_msg("Invalid value", "You must use a number between 0 - 59 for: " + attr);
- $("#" + attr).val(config_values[attr])
- return;
- }
- }
-
- if (val && val != config_values[attr]) {
- mod['attr'] = attr;
- mod['val'] = val;
- mod_list.push(mod);
- } else if (val == "" && val != config_values[attr]) {
- mod['attr'] = attr;
- del_list.push(mod);
- }
- }
- }
-
- // Save access log levels
- var access_log_level = 0;
- $(".ds-accesslog-table").each(function() {
- var val = this.id;
- if (this.checked){
- val = parseInt(val.replace("accesslog-", ""));
- access_log_level += val;
- }
- });
- if (config_values["nsslapd-accesslog-level"] === undefined) {
- config_values["nsslapd-accesslog-level"] = "256";
- }
- if (config_values["nsslapd-accesslog-level"] != access_log_level) {
- mod = {}
- mod['attr'] = "nsslapd-accesslog-level";
- mod['val'] = access_log_level;
- mod_list.push(mod);
- }
-
- // Save error log levels
- var error_log_level = 0;
- $(".ds-errorlog-table").each(function() {
- var val = this.id;
- if (this.checked) {
- val = parseInt(val.replace("errorlog-", ""));
- error_log_level += val;
- }
- });
- if (config_values["nsslapd-errorlog-level"] === undefined ||
- config_values["nsslapd-errorlog-level"] == "16384")
- {
- config_values["nsslapd-errorlog-level"] = "0";
- }
- if (config_values["nsslapd-errorlog-level"] != error_log_level) {
- mod = {}
- mod['attr'] = "nsslapd-errorlog-level";
- mod['val'] = error_log_level;
- mod_list.push(mod);
- }
-
- // Build dsconf commands to apply all the mods
- if (mod_list.length || del_list.length) {
- let err = 0;
- if (mod_list.length) {
- if (apply_mods(mod_list) == -1) {
- return;
- }
- }
- if (del_list.length) {
- if (delete_mods(del_list) == -1) {
- return;
- }
- }
- popup_success("Successfully updated configuration");
- } else {
- // No changes to save, log msg? popup_msg()
- }
-}
-
-function do_backup(server_inst, backup_name) {
- var cmd = [DSCTL, '-j', server_inst, 'status'];
- $("#backup-spinner").show();
- cockpit.spawn(cmd, { superuser: true}).
- done(function(status_data) {
- var status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- var cmd = [DSCONF, "-j", server_inst, 'backup', 'create', backup_name];
- log_cmd('#ds-backup-btn (click)', 'Backup server instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#backup-spinner").hide();
- popup_success("Backup has been created");
- $("#backup-form").modal('toggle');
- }).
- fail(function(data) {
- $("#backup-spinner").hide();
- popup_err("Failed to backup the server", data.message);
- })
- } else {
- var cmd = [DSCTL, server_inst, 'db2bak', backup_name];
- log_cmd('#ds-backup-btn (click)', 'Backup server instance (offline)', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#backup-spinner").hide();
- popup_success("Backup has been created");
- $("#backup-form").modal('toggle');
- }).
- fail(function(data) {
- $("#backup-spinner").hide();
- popup_err("Failed to backup the server", data.message);
- });
- }
- }).
- fail(function() {
- popup_err("Failed to check the server status", data.message);
- });
-}
-
-/*
- * load the server config pages
- */
-$(document).ready( function() {
-
- // Set an interval event to wait for all the pages to load, then load the config
- var init_config = setInterval(function() {
- if (server_page_loaded == 1 && security_page_loaded == 1 && db_page_loaded == 1 &&
- repl_page_loaded == 1 && schema_page_loaded == 1 && plugin_page_loaded == 1 &&
- monitor_page_loaded == 1)
- {
- get_insts();
-
- /*
- * Stop, Start, and Restart server
- */
- document.getElementById("start-server-btn").addEventListener("click", function() {
- $("#ds-start-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Starting instance <b>" + server_id + "</b>...");
- $("#start-instance-form").modal('toggle');
- var cmd = [DSCTL, server_inst, 'start'];
- log_cmd('#start-server-btn (click)', 'Start server instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $("#start-instance-form").modal('toggle');
- load_config(true);
- popup_success("Started instance \"" + server_id + "\"");
- }).fail(function(data) {
- $("#start-instance-form").modal('toggle');
- popup_err("Failed to start instance \"" + server_id, data.message);
- });
- });
-
- document.getElementById("stop-server-btn").addEventListener("click", function() {
- $("#ds-stop-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Stopping instance <b>" + server_id + "</b>...");
- $("#stop-instance-form").modal('toggle');
- var cmd = [DSCTL, server_inst, 'stop'];
- log_cmd('#stop-server-btn (click)', 'Stop server instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $("#stop-instance-form").modal('toggle');
- popup_success("Stopped instance \"" + server_id + "\"");
- check_inst_alive();
- }).fail(function(data) {
- $("#stop-instance-form").modal('toggle');
- popup_err("Error", "Failed to stop instance \"" + server_id+ "\"", data.message);
- check_inst_alive();
- });
- });
-
-
- document.getElementById("restart-server-btn").addEventListener("click", function() {
- $("#ds-restart-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Restarting instance <b>" + server_id + "</b>...");
- $("#restart-instance-form").modal('toggle');
- var cmd = [DSCTL, server_inst, 'restart'];
- log_cmd('#restart-server-btn (click)', 'Restart server instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $("#restart-instance-form").modal('toggle');
- load_config(true);
- popup_success("Restarted instance \"" + server_id + "\"");
- }).fail(function(data) {
- $("#restart-instance-form").modal('toggle');
- popup_err("Failed to restart instance \"" + server_id + "\"", data.message);
- });
- });
-
- document.getElementById("remove-server-btn").addEventListener("click", function() {
- popup_confirm("Are you sure you want to this remove instance: <b>" + server_id + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCTL, server_inst, "remove", "--do-it"];
- $("#ds-remove-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Removing instance <b>" + server_id + "</b>...");
- $("#remove-instance-form").modal('toggle');
- log_cmd('#remove-server-btn (click)', 'Remove instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $("#remove-instance-form").modal('toggle');
- popup_success("Instance has been deleted");
- get_insts();
- }).fail(function(data) {
- $("#remove-instance-form").modal('toggle');
- popup_err("Failed to remove instance", data.message);
- });
- }
- });
- });
-
- clearInterval(init_config);
- }
- }, 250);
-
- console.log("Loading Server Page...");
-
- $("#main-banner").load("banner.html");
- check_for_389();
-
- $("#server-tab").css( 'color', '#228bc0');
-
- $("#server-content").load("servers.html", function () {
- // Initialize all the tables first
- sasl_table = $('#sasl-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "emptyTable": "No SASL Mappings",
- "search": "Search Mappings"
- },
- "columnDefs": [ {
- "targets": 5,
- "orderable": false
- } ]
- });
-
- // backup/restore table
- var backup_table = $('#backup-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "emptyTable": "No backups available for restore",
- "search": "Search Backups"
- },
- "columnDefs": [ {
- "targets": [3, 4],
- "orderable": false
- } ],
- "columns": [
- { "width": "120px" },
- { "width": "80px" },
- { "width": "30px" },
- { "width": "40px" },
- { "width": "30px" }
- ],
- });
-
- // Set up local passwd policy table
- pwp_table = $('#passwd-policy-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "emptyTable": "No local policies",
- "search": "Search Policies"
- },
- "columnDefs": [ {
- "targets": 2,
- "orderable": false
- } ]
- });
-
- $('.disk-monitoring').hide();
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-config").show();
-
- // To remove text border on firefox on dropdowns)
- if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
- $("select").focus( function() {
- this.style.setProperty( 'outline', 'none', 'important' );
- this.style.setProperty( 'color', 'rgba(0,0,0,0)', 'important' );
- this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );
- });
- }
-
- $(".save-button").on('click', function (){
- // This is for all pages. Click Save -> it saves everything
- save_all();
- });
-
- // Events
- $(".ds-nav-choice").on('click', function (){
- $(".ds-tab-list").css( 'color', '#777');
- var tab = $(this).attr("parent-id");
- $("#" + tab).css( 'color', '#228bc0');
- });
-
- $(".ds-tab-standalone").on('click', function (){
- $(".ds-tab-list").css( 'color', '#777');
- $(this).css( 'color', '#228bc0');
- });
-
- $("#server-config-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-config").show();
- });
- $("#server-sasl-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-sasl").show();
- });
- $("#server-gbl-pwp-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#global-password-policy").show();
- });
- $("#server-local-pwp-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#local-password-policy").show();
- });
- $("#server-log-access-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-access-log").show();
- });
- $("#server-log-audit-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-audit-log").show();
- });
- $("#server-log-auditfail-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-auditfail-log").show();
- });
- $("#server-log-errors-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-errors-log").show();
- });
- $("#server-tasks-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-tasks").show();
- });
- $("#server-tuning-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-tuning").show();
- });
- $("#server-ldapi-btn").on("click", function() {
- $(".all-pages").hide();
- $("#server-content").show();
- $("#server-ldapi").show();
- });
-
- // Disable disk monitoring input if not in use
- $("#nsslapd-disk-monitoring").change(function() {
- if(this.checked) {
- $('.disk-monitoring').show();
- } else {
- $('.disk-monitoring').hide();
- }
- });
-
- $('.ds-loglevel-table tr').click(function(event) {
- if (event.target.type !== 'checkbox') {
- $(':checkbox', this).trigger('click');
- }
- });
-
- $("#create-sasl-map-btn").on("click", function () {
- clear_sasl_map_form();
- $("#sasl-map-name").prop("readonly", false);
- });
-
- $("#test-sasl-regex").change(function() {
- if(this.checked) {
- // Test SASL mapping
- $("#sasl-test-div").show();
- } else {
- $("#sasl-test-div").hide();
- }
- });
-
- // Test SASL Mapping Regex
- $("#sasl-test-regex-btn").on('click', function () {
- var result = "No match!"
- var regex = $("#sasl-map-regex").val().replace(/\\\(/g, '(').replace(/\\\)/g, ')');
- var test_string = $("#sasl-test-regex-string").val();
- var sasl_regex = RegExp(regex);
- if (sasl_regex.test(test_string)){
- popup_msg("Match", "The text matches the regular expression");
- } else {
- popup_msg("No Match", "The text does not match the regular expression");
- }
- });
-
- // Edit SASL mapping
- $(document).on('click', '.sasl-edit-btn', function(e) {
- // Load the Edit form
- e.preventDefault();
- clear_sasl_map_form();
- var data = sasl_table.row( $(this).parents('tr') ).data();
- var edit_sasl_name = data[0];
- var edit_sasl_regex = data[1];
- var edit_sasl_base = data[2];
- var edit_sasl_filter = data[3];
- var edit_sasl_priority = data[4];
-
- $("#sasl-header").html("Edit SASL Mapping");
- $("#sasl-map-name").val(edit_sasl_name);
- $("#sasl-map-name").prop("readonly", true);
- $("#sasl-map-regex").val(edit_sasl_regex);
- $("#sasl-map-base").val(edit_sasl_base);
- $("#sasl-map-filter").val(edit_sasl_filter);
- $("#sasl-map-priority").val(edit_sasl_priority);
- $("#sasl-map-form").modal("toggle");
- });
-
- // Verify SASL Mapping regex - open modal and ask for "login" to test regex mapping
- $(document).on('click', '.sasl-verify-btn', function(e) {
- // TODO - get this working
- e.preventDefault();
- var data = sasl_table.row( $(this).parents('tr') ).data();
- var verify_sasl_name = data[0];
- });
-
- // Delete SASL Mapping
- $(document).on('click', '.sasl-del-btn', function(e) {
- e.preventDefault();
- var data = sasl_table.row( $(this).parents('tr') ).data();
- var del_sasl_name = data[0];
- var sasl_row = $(this); // Store element for callback
- popup_confirm("Are you sure you want to delete sasl mapping: <b>" + del_sasl_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'delete', del_sasl_name];
- log_cmd('.sasl-del-btn (click)', 'Delete SASL mapping', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- sasl_table.row( sasl_row.parents('tr') ).remove().draw( false );
- popup_success("Removed SASL mapping <b>" + del_sasl_name + "</b>");
- }).fail(function(data) {
- popup_err("Failed To Delete SASL Mapping: <b>" + del_sasl_name + "</b>", data.message);
- });
- }
- });
- });
-
- // Load password policy and update form based on settings
- // TODO
-
- // Global password policy form control
- $("#passwordhistory").change(function() {
- if(this.checked) {
- $('#passwordinhistory').attr('disabled', false);
- } else {
- $('#passwordinhistory').attr('disabled', true);
- }
- });
- if ( $("#passwordhistory").is(":checked") ) {
- $('#passwordinhistory *').attr('disabled', false);
- } else {
- $('#passwordinhistory *').attr('disabled', true);
- }
-
- $("#passwordexp").change(function() {
- if(this.checked) {
- $('#expiration-attrs *').attr('disabled', false);
- } else {
- $('#expiration-attrs *').attr('disabled', true);
- }
- });
- if ( $("#passwordexp").is(":checked") ) {
- $('#expiration-attrs *').attr('disabled', false);
- } else {
- $('#expiration-attrs *').attr('disabled', true);
- }
-
- $("#passwordchecksyntax").change(function() {
- if(this.checked) {
- $('#syntax-attrs *').attr('disabled', false);
- } else {
- $('#syntax-attrs *').attr('disabled', true);
- }
- });
- if ( $("#passwordchecksyntax").is(":checked") ) {
- $('#syntax-attrs *').attr('disabled', false);
- } else {
- $('#syntax-attrs *').attr('disabled', true);
- }
-
- $("#passwordlockout").change(function() {
- if(this.checked) {
- $('#lockout-attrs *').attr('disabled', false);
- } else {
- $('#lockout-attrs *').attr('disabled', true);
- }
- });
- if ( $("#passwordlockout").is(":checked") ) {
- $('#lockout-attrs *').attr('disabled', false);
- } else {
- $('#lockout-attrs *').attr('disabled', true);
- }
-
- /*
- * local password policy form control
- */
- $("#local-passwordhistory").change(function() {
- if(this.checked) {
- $('#local-passwordinhistory').attr('disabled', false);
- } else {
- $('#local-passwordinhistory').attr('disabled', true);
- }
- });
- if ( $("#local-passwordhistory").is(":checked") ) {
- $('#local-passwordhistory *').attr('disabled', false);
- } else {
- $('#local-passwordhistorys *').attr('disabled', true);
- }
-
- $("#local-passwordexp").change(function() {
- if(this.checked) {
- $('#local-expiration-attrs *').attr('disabled', false);
- } else {
- $('#local-expiration-attrs *').attr('disabled', true);
- }
- });
- if ( $("#local-passwordexp").is(":checked") ) {
- $('#local-expiration-attrs *').attr('disabled', false);
- } else {
- $('#local-expiration-attrs *').attr('disabled', true);
- }
-
- $("#local-passwordchecksyntax").change(function() {
- if(this.checked) {
- $('#local-syntax-attrs *').attr('disabled', false);
- } else {
- $('#local-syntax-attrs *').attr('disabled', true);
- }
- });
- if ( $("#local-passwordchecksyntax").is(":checked") ) {
- $('#local-syntax-attrs *').attr('disabled', false);
- } else {
- $('#local-syntax-attrs *').attr('disabled', true);
- }
-
- $("#local-passwordlockout").change(function() {
- if(this.checked) {
- $('#local-lockout-attrs *').attr('disabled', false);
- } else {
- $('#local-lockout-attrs *').attr('disabled', true);
- }
- });
- if ( $("#local-passwordlockout").is(":checked") ) {
- $('local-lockout-attrs *').attr('disabled', false);
- } else {
- $('#local-lockout-attrs *').attr('disabled', true);
- }
-
- /*
- * Logging form control
- */
- $("#nsslapd-accesslog-logging-enabled").change(function() {
- if(this.checked) {
- $('#accesslog-attrs *').attr('disabled', false);
- } else {
- $('#accesslog-attrs *').attr('disabled', true);
- }
- });
-
- $("#nsslapd-errorlog-logging-enabled").change(function() {
- if(this.checked) {
- $('#errorlog-attrs *').attr('disabled', false);
- } else {
- $('#errorlog-attrs *').attr('disabled', true);
- }
- });
-
- $("#nsslapd-auditlog-logging-enabled").change(function() {
- if(this.checked) {
- $('#auditlog-attrs *').attr('disabled', false);
- } else {
- $('#auditlog-attrs *').attr('disabled', true);
- }
- });
-
- $("#nsslapd-auditfaillog-logging-enabled").change(function() {
- if(this.checked) {
- $('#auditfaillog-attrs *').attr('disabled', false);
- } else {
- $('#auditfaillog-attrs *').attr('disabled', true);
- }
- });
-
- $("#nsslapd-ndn-cache-enabled").change(function() {
- if(this.checked) {
- $('#nsslapd-ndn-cache-max-size').attr('disabled', false);
- } else {
- $('#nsslapd-ndn-cache-max-size').attr('disabled', true);
- }
- });
-
- // LDAPI form control
- $("#nsslapd-ldapimaptoentries").change(function() {
- if (this.checked){
- $(".autobind-entry-attrs").show();
- } else {
- $(".autobind-entry-attrs").hide();
- }
- });
-
- /*
- * Modal Forms
- */
-
- /*
- * Local password policy
- */
- $("#create-local-pwp-btn").on("click", function () {
- clear_local_pwp_form();
- });
-
- $("#local-pwp-save").on("click", function() {
- /*
- * We are either saving a new policy or editing an existing one
- * If we are editing and we remove a setting, we have to set it
- * to the default value - so it makes things a little more tedious
- * for editing a local policy
- */
-
- // Is this the create or edit form?
- var edit = false;
- if ( $("#local-pwp-header").text().startsWith('Edit') ) {
- edit = true;
- }
-
- /*
- * Get all the current values from the form.
- */
- var policy_name = $("#local-entry-dn").val();
- if (policy_name == "" || !valid_dn(policy_name)) {
- popup_msg("Error", "You must enter a valid DN for the local password policy");
- return;
- }
- var pwp_track = "off";
- if ( $("#local-passwordtrackupdatetime").is(":checked") ) {
- pwp_track = "on";
- }
- var pwp_passwordchange = "off";
- if ($("#local-passwordchange").is(":checked") ){
- pwp_passwordchange = "on";
- }
- var pwp_passwordmustchange = "off";
- if ($("#local-passwordmustchange").is(":checked") ){
- pwp_passwordmustchange = "on";
- }
- var pwp_history = "off";
- if ($("#local-passwordhistory").is(":checked") ){
- pwp_history = "on";
- }
- var pwp_exp = "off";
- if ($("#local-passwordexp").is(":checked") ){
- pwp_exp = "on";
- }
- var pwp_sendexp = "off";
- if ($("#local-passwordsendexpiringtime").is(":checked") ){
- pwp_sendexp = "on";
- }
- var pwp_lockout = "off";
- if ($("#local-passwordlockout").is(":checked") ){
- pwp_lockout = "on";
- }
- var pwp_unlock = "off";
- if ($("#local-passwordunlock").is(":checked") ){
- pwp_unlock = "on";
- }
- var pwp_checksyntax = "off";
- if ($("#local-passwordchecksyntax").is(":checked") ){
- pwp_checksyntax = "on";
- }
- var pwp_palindrome = "on";
- if ( $("#local-passwordpalindrome").is(":checked") ){
- pwp_palindrome = "off";
- }
- var pwp_dictcheck = "off";
- if ( $("#local-passworddictcheck").is(":checked") ){
- pwp_dictcheck = "on";
- }
-
- var pwp_admin = $("#local-passwordadmindn").val();
- var pwp_inhistory = $("#local-passwordinhistory").val();
- var pwp_minage = $("#local-passwordminage").val();
- var pwp_maxage = $("#local-passwordmaxage").val();
- var pwp_gracelimit = $("#local-passwordgracelimit").val();
- var pwp_warning = $("#local-passwordwarning").val();
- var pwp_maxfailure = $("#local-passwordmaxfailure").val();
- var pwp_failcount = $("#local-passwordresetfailurecount").val();
- var pwp_lockoutdur = $("#local-passwordlockoutduration").val();
- var pwp_minlen = $("#local-passwordminlength").val();
- var pwp_mindigits = $("#local-passwordmindigits").val();
- var pwp_minalphas = $("#local-passwordminalphas").val();
- var pwp_minuppers = $("#local-passwordminuppers").val();
- var pwp_minlowers = $("#local-passwordminlowers").val();
- var pwp_minspecials = $("#local-passwordminspecials").val();
- var pwp_min8bits = $("#local-passwordmin8bit").val();
- var pwp_maxrepeats = $("#local-passwordmaxrepeats").val();
- var pwp_mincat = $("#local-passwordmincategories").val();
- var pwp_mintoken = $("#local-passwordmintokenlength").val();
- var pwp_badwords = $("#local-passwordbadwords").val();
- var pwp_userattrs = $("#local-passworduserattributes").val();
- var pwp_maxseq = $("#local-passwordmaxsequence").val();
- var pwp_maxseqset = $("#local-passwordmaxseqsets").val();
- var pwp_maxclass = $("#local-passwordmaxclasschars").val();
- var pwp_scheme = $("#local-passwordstoragescheme").val();
-
- var pwp_type = "User Policy";
- if ( $("#subtree-pwp-radio").is(":checked")) {
- pwp_type = "Subtree Policy";
- }
-
- /*
- * Go through all the settings and create an arg list, but if editing
- * the policy and the value is now "", we need to set it to the default
- * value.
- */
- arg_list = [];
-
- // Do the on/off settings first
- arg_list.push('--pwdtrack=' + pwp_track);
- arg_list.push('--pwdchange=' + pwp_passwordchange);
- arg_list.push('--pwdmustchange=' + pwp_passwordmustchange);
- arg_list.push('--pwdhistory=' + pwp_history);
- arg_list.push('--pwdexpire=' + pwp_exp);
- arg_list.push('--pwdlockout=' + pwp_lockout);
- arg_list.push('--pwdunlock=' + pwp_unlock);
- arg_list.push('--pwdchecksyntax=' + pwp_checksyntax);
- arg_list.push('--pwdpalindrome=' + pwp_palindrome);
- arg_list.push('--pwddictcheck=' + pwp_dictcheck);
- arg_list.push('--pwdsendexpiring=' + pwp_sendexp);
- // Do the rest
- if ( !add_validate_arg (arg_list, "num", pwp_inhistory, "0", edit, 'passwordinhistory', '--pwdhistorycount', 'Passwords in history') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minage, "0", edit, 'passwordminage', '--pwdminage', 'Allowed Password Changes') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxage, "0", edit, 'passwordmaxage', '--pwdmaxage', 'Password Expiration Time') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_gracelimit, "0", edit, 'passwordgracelimit', '--pwdgracelimit', 'Allowed Logins') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_inhistory, "0", edit, 'passwordwarning', '--pwdwarning', 'Password Warning') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxfailure, "0", edit, 'passwordmaxfailure', '--pwdmaxfailures', 'Number of Failed Logins') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_failcount, "0", edit, 'passwordresetfailurecount', '--pwdresetfailcount', 'Failure Count Reset') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_lockoutdur, "0", edit, 'passwordlockoutduration', '--pwdlockoutduration', 'Time Until Account Unlock') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minlen, "6", edit, 'passwordminlength', '--pwdminlen', 'Password Minimum Length') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_mindigits, "0", edit, 'passwordmindigits', '--pwdmindigits', 'Minimum Digits') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minalphas, "0", edit, 'passwordminalphas', '--pwdminalphas', 'Minimum Alphas') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minuppers, "0", edit, 'passwordminuppers', '--pwdminuppers', 'Minimum Uppercase Characters') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minlowers, "0", edit, 'passwordminlowers', '--pwdminlowers', 'Minimum Lowercase Characters') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_minspecials, "0", edit, 'passwordminspecials', '--pwdminspecials', 'Minimum Special Characters') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_min8bits, "0", edit, 'passwordmin8bits', '--pwdmin8bits', 'Minimum Alphas') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxrepeats, "0", edit, 'passwordmaxrepeats', '--pwdmaxrepeats', 'Maximum Repeats') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_mincat, "3", edit, 'passwordmincategories', '--pwdmincatagories', 'Minimum Catagories') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_mintoken, "3", edit, 'passwordmintokenlength', '--pwdmintokenlen', 'Minimum Alphas') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxseq, "0", edit, 'passwordmaxsequence', '--pwdmaxseq', 'Maximum Sequence') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxseqset, "0", edit, 'passwordmaxseqsets', '--pwdmaxseqsets', 'Maximum Sequence Sets') ) { return; }
- if ( !add_validate_arg (arg_list, "num", pwp_maxclass, "0", edit, 'passwordmaxclasschars', '--pwdmaxclasschars', 'Maximum Character Classes') ) { return; }
- if ( !add_validate_arg (arg_list, "dn", pwp_admin, "", edit, 'passwordadmindn', '--pwdadmin', 'Password Administrator') ) { return; }
- if (pwp_badwords != "" || (edit && localpwp_values['passwordbadwords'] !== undefined && pwp_badwords != localpwp_values['passwordbadwords'])) {
- arg_list.push('--pwdbadwords=' + pwp_badwords);
- }
- if (pwp_userattrs != "" || (edit && localpwp_values['passworduserattributes'] !== undefined && pwp_userattrs != localpwp_values['passworduserattributes'])) {
- arg_list.push('--pwduserattrs=' + pwp_userattrs);
- }
- if (pwp_scheme != "" || (edit && localpwp_values['passwordstoragescheme'] !== undefined && pwp_scheme != localpwp_values['passwordstoragescheme'])) {
- arg_list.push('--pwdscheme=' + pwp_scheme);
- }
-
- /*
- * Update/Add Password policy to DS
- */
- if ( edit ) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','localpwp', 'set', policy_name];
- cmd = cmd.concat(arg_list);
- log_cmd('local-pwp-save', 'Set local password policy', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Successfully edited local password policy');
- $("#local-pwp-form").modal('toggle')
- }).fail(function(data) {
- popup_err("Failed to edit local password policy", data.message);
- });
- } else {
- // Create new local policy
- var action = "addsubtree";
- if (pwp_type == "User Policy") {
- action = "adduser";
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','localpwp', action, policy_name];
- cmd = cmd.concat(arg_list);
- log_cmd('local-pwp-save', 'Add local password policy', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- pwp_table.row.add( [
- policy_name,
- pwp_type,
- local_pwp_html
- ] ).draw( false );
- popup_success('Successfully created local password policy');
- $("#local-pwp-form").modal('toggle');
- }).fail(function(data) {
- popup_err("Failed to create local password policy", data.message);
- });
- }
- });
-
- // Delete local password policy
- $(document).on('click', '.delete-local-pwp', function(e) {
- e.preventDefault();
- // Update HTML table
- var data = pwp_table.row( $(this).parents('tr') ).data();
- var del_pwp_name = data[0];
- var pwp_row = $(this);
- popup_confirm("Are you sure you want to delete local password policy: <b>" + del_pwp_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- // Delete pwp from DS
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','localpwp', 'remove', del_pwp_name];
- log_cmd('.delete-local-pwp (click)', 'Remove local password policy', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- // Update html table
- pwp_table.row( pwp_row.parents('tr') ).remove().draw( false );
- popup_success('Successfully deleted local password policy');
- }).fail(function(data) {
- popup_err("Failed to delete local password policy", data.message);
- });
- }
- });
- });
-
- // SASL Mappings Form
- $("#sasl-map-save").on("click", function() {
- var sasl_map_name = $("#sasl-map-name").val();
- var sasl_regex = $("#sasl-map-regex").val();
- var sasl_base = $("#sasl-map-base").val();
- var sasl_filter = $("#sasl-map-filter").val();
- var sasl_priority = $("#sasl-map-priority").val();
-
- // Validate values
- if (sasl_map_name == '') {
- report_err($("#sasl-map-name"), 'You must provide a mapping name');
- return;
- }
- if (sasl_map_name == '') {
- report_err($("#sasl-map-regex"), 'You must provide an regex');
- return;
- }
- if (sasl_regex == '') {
- report_err($("#sasl-map-base"), 'You must provide a base DN template');
- return;
- }
- if (sasl_filter == '') {
- report_err($("#sasl-map-filter"), 'You must provide an filter template');
- return;
- }
- if (sasl_priority == '') {
- sasl_priority = '100'
- } else if (valid_num(sasl_priority)) {
- var priority = Number(sasl_priority);
- if (priority < 1 || priority > 100) {
- report_err($("#sasl-map-priority"), 'You must provide a number between 1 and 100');
- return;
- }
- } else {
- report_err($("#sasl-map-priority"), 'You must provide a number between 1 and 100');
- return;
- }
-
- // Build command line args
- var sasl_name_cmd = "--cn=" + sasl_map_name ;
- var sasl_regex_cmd = "--nsSaslMapRegexString=" + sasl_regex;
- var sasl_base_cmd = "--nsSaslMapBaseDNTemplate=" + sasl_base;
- var sasl_filter_cmd = "--nsSaslMapFilterTemplate=" + sasl_filter;
- var sasl_priority_cmd = "--nsSaslMapPriority=" + sasl_priority;
-
- if ( $("#sasl-header").html().includes("Create") ) {
- // Create new mapping and update table
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'create',
- sasl_name_cmd, sasl_regex_cmd, sasl_base_cmd, sasl_filter_cmd, sasl_priority_cmd];
- log_cmd('#sasl-map-save (click)', 'Add SASL mapping', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- // Update html table
- sasl_table.row.add( [
- sasl_map_name,
- sasl_regex,
- sasl_base,
- sasl_filter,
- sasl_priority,
- sasl_action_html
- ] ).draw( false );
- popup_success("Successfully added new SASL mapping");
- $("#sasl-map-form").modal('toggle');
- }).fail(function(data) {
- popup_err("Failed To Add SASL Mapping: " + sasl_map_name, data.message);
- $("#sasl-map-form").modal("toggle");
- });
- } else {
- // Editing mapping. First delete the old mapping
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'delete', sasl_map_name];
- log_cmd('#sasl-map-save (click)', 'Delete SASL mapping', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- // Remove row from old
- sasl_table.rows( function ( idx, data, node ) {
- return data[0] == sasl_map_name;
- }).remove().draw();
-
- // Then add new mapping and update table
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'create',
- sasl_name_cmd, sasl_regex_cmd, sasl_base_cmd, sasl_filter_cmd, sasl_priority_cmd];
- log_cmd('#sasl-map-save (click)', 'Add SASL mapping', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- // Update html table
- sasl_table.row.add( [
- sasl_map_name,
- sasl_regex,
- sasl_base,
- sasl_filter,
- sasl_priority,
- sasl_action_html
- ] ).draw( false );
- popup_success("Successfully added new SASL mapping");
- $("#sasl-map-form").modal('toggle');
- }).fail(function(data) {
- popup_err("Failure Adding SASL Mapping",
- "Failed To Add SASL Mapping: <b>" + sasl_map_name + "</b>: \n" + data.message);
- $("#sasl-map-form").modal("toggle");
- });
- }).fail(function(data) {
- popup_err("Failure Deleting Old SASL Mapping",
- "Failed To Delete SASL Mapping: <b>" + sasl_map_name + "</b>: \n" + data.message);
- $("#sasl-map-form").modal("toggle");
- });
- }
- });
-
- /* Backup server */
- $("#ds-backup-btn").on('click', function () {
- var backup_name = $("#backup-name").val();
- if (backup_name == ""){
- popup_msg("Error", "Backup must have a name");
- return;
- }
- if (backup_name.indexOf(' ') >= 0) {
- popup_msg("Error", "Backup name can not contain any spaces");
- return;
- }
- if (backup_name.indexOf('/') >= 0) {
- popup_msg("Error", "Backup name can not contain a forward slash. " +
- "Backups are written to the server's backup directory (nsslapd-bakdir)");
- return;
- }
-
- // First check if backup name is already used
- var check_cmd = [DSCTL, '-j', server_inst, 'backups'];
- log_cmd('#restore-server-btn (click)', 'Restore server instance', check_cmd);
- cockpit.spawn(check_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var obj = JSON.parse(data);
- var found_backup = false;
- for (var i = 0; i < obj.items.length; i++) {
- if (obj.items[i][0] == backup_name) {
- found_backup = true;
- break;
- }
- }
- if (found_backup) {
- popup_confirm("A backup already exists with this name, replace it?", "Confirmation", function (yes) {
- if (yes) {
- do_backup(server_inst, backup_name);
- } else {
- return;
- }
- });
- } else {
- do_backup(server_inst, backup_name);
- }
- });
- });
-
- $("#backup-server-btn").on('click', function () {
- $("#backup-name").val("");
- });
-
- /* Restore. load restore table with current backups */
- $("#restore-server-btn").on('click', function () {
- var cmd = [DSCTL, '-j', server_inst, 'backups'];
- log_cmd('#restore-server-btn (click)', 'Restore server instance', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var backup_btn = "<button class=\"btn btn-default restore-btn\" type=\"button\">Restore</button>";
- var del_btn = "<button title=\"Delete backup directory\" class=\"btn btn-default ds-del-backup-btn\" type=\"button\"><span class='glyphicon glyphicon-trash'></span></button>";
- var obj = JSON.parse(data);
- backup_table.clear().draw( false );
- for (var i = 0; i < obj.items.length; i++) {
- var backup_name = obj.items[i][0];
- var backup_date = obj.items[i][1];
- var backup_size = obj.items[i][2];
- backup_table.row.add([backup_name, backup_date, backup_size, backup_btn, del_btn]).draw( false );
- }
- }).fail(function(data) {
- popup_err("Failed to get list of backups", data.message);
- });
- });
-
- /* Restore server */
- $(document).on('click', '.restore-btn', function(e) {
- e.preventDefault();
- var data = backup_table.row( $(this).parents('tr') ).data();
- var restore_name = data[0];
- popup_confirm("Are you sure you want to restore this backup: <b>" + restore_name + "<b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCTL, '-j', server_inst, 'status'];
- $("#restore-spinner").show();
- cockpit.spawn(cmd, { superuser: true}).
- done(function(status_data) {
- var status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- var cmd = [DSCONF, server_inst, 'backup', 'restore', restore_name];
- log_cmd('.restore-btn (click)', 'Restore server instance(online)', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#restore-spinner").hide();
- popup_success("The backup has been restored");
- $("#restore-form").modal('toggle');
- }).
- fail(function(data) {
- $("#restore-spinner").hide();
- popup_err("Failed to restore from the backup", data.message);
- });
- } else {
- var cmd = [DSCTL, server_inst, 'bak2db', restore_name];
- log_cmd('.restore-btn (click)', 'Restore server instance(offline)', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#restore-spinner").hide();
- popup_success("The backup has been restored");
- $("#restore-form").modal('toggle');
- }).
- fail(function(data) {
- $("#restore-spinner").hide();
- popup_err("Failed to restore from the backup", data.message);
- });
- }
- }).
- fail(function() {
- popup_err("Failed to check the server status", data.message);
- });
- }
- });
- });
-
- /* Delete backup directory */
- $(document).on('click', '.ds-del-backup-btn', function(e) {
- e.preventDefault();
- var data = backup_table.row( $(this).parents('tr') ).data();
- var restore_name = data[0];
- var backup_row = $(this);
- popup_confirm("Are you sure you want to delete this backup: <b>" + restore_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCTL, server_inst, 'backups', '--delete', restore_name];
- $("#restore-spinner").show();
- log_cmd('.ds-del-backup-btn (click)', 'Delete backup', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $("#restore-spinner").hide();
- backup_table.row( backup_row.parents('tr') ).remove().draw( false );
- popup_success("The backup has been deleted");
- }).fail(function(data) {
- $("#restore-spinner").hide();
- popup_err("Failed to delete the backup", data.message);
- });
- }
- });
- });
-
- /* reload schema */
- $("#schema-reload-btn").on("click", function () {
- var schema_dir = $("#reload-dir").val();
- if (schema_dir != ""){
- var cmd = [DSCONF, server_inst, 'schema', 'reload', '--schemadir', schema_dir, '--wait'];
- } else {
- var cmd = [DSCONF, server_inst, 'schema', 'reload', '--wait'];
- }
- $("#reload-spinner").show();
- log_cmd('#schema-reload-btn (click)', 'Reload schema files', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success("Successfully reloaded schema"); // TODO use timed interval success msg (waiting for another PR top be merged before we can add it)
- $("#schema-reload-form").modal('toggle');
- $("#reload-spinner").hide();
- }).fail(function(data) {
- popup_err("Failed to reload schema files", data.message);
- $("#reload-spinner").hide();
- });
- });
-
- // Create instance form
- $("#create-server-btn").on("click", function() {
- clear_inst_form();
- set_ports();
- });
- $("#no-inst-create-btn").on("click", function () {
- clear_inst_form();
- });
-
- // Create Instance
- $("#create-inst-save").on("click", function() {
- $(".ds-modal-error").hide();
- $(".ds-inst-input").css("border-color", "initial");
-
- /*
- * Validate settings and update the INF settings
- */
- var setup_inf = create_inf_template;
-
- // Server ID
- var new_server_id = $("#create-inst-serverid").val();
- if (new_server_id == ""){
- report_err($("#create-inst-serverid"), 'You must provide an Instance name');
- $("#create-inst-serverid").css("border-color", "red");
- return;
- } else {
- new_server_id = new_server_id.replace(/^slapd-/i, ""); // strip "slapd-"
- if (new_server_id.length > 128) {
- report_err($("#create-inst-serverid"), 'Instance name is too long, it must not exceed 128 characters');
- $("#create-inst-serverid").css("border-color", "red");
- return;
- }
- if (new_server_id.match(/^[#%:A-Za-z0-9_\-]+$/g)) {
- setup_inf = setup_inf.replace('INST_NAME', new_server_id);
- } else {
- report_err($("#create-inst-serverid"), 'Instance name can only contain letters, numbers, and: # % : - _');
- $("#create-inst-serverid").css("border-color", "red");
- return;
- }
- }
-
- // Port
- var server_port = $("#create-inst-port").val();
- if (server_port == ""){
- report_err($("#create-inst-port"), 'You must provide a port number');
- $("#create-inst-port").css("border-color", "red");
- return;
- } else if (!valid_port(server_port)) {
- report_err($("#create-inst-port"), 'Port must be a number between 1 and 65534!');
- $("#create-inst-port").css("border-color", "red");
- return;
- } else {
- setup_inf = setup_inf.replace('PORT', server_port);
- }
-
- // Secure Port
- var secure_port = $("#create-inst-secureport").val();
- if (secure_port == ""){
- report_err($("#create-inst-secureport"), 'You must provide a secure port number');
- $("#create-inst-secureport").css("border-color", "red");
- return;
- } else if (!valid_port(secure_port)) {
- report_err($("#create-inst-secureport"), 'Secure port must be a number!');
- $("#create-inst-secureport").css("border-color", "red");
- return;
- } else {
- setup_inf = setup_inf.replace('SECURE_PORT', secure_port);
- }
-
- // Root DN
- var server_rootdn = $("#create-inst-rootdn").val();
- if (server_rootdn == ""){
- report_err($("#create-inst-rootdn"), 'You must provide a Directory Manager DN');
- $("#create-inst-rootdn").css("border-color", "red");
- return;
- } else {
- setup_inf = setup_inf.replace('ROOTDN', server_rootdn);
- }
-
- // Setup Self-Signed Certs
- if ( $("#create-inst-tls").is(":checked") ){
- setup_inf = setup_inf.replace('SELF_SIGN', 'True');
- } else {
- setup_inf = setup_inf.replace('SELF_SIGN', 'False');
- }
-
- // Root DN password
- var root_pw = $("#rootdn-pw").val();
- var root_pw_confirm = $("#rootdn-pw-confirm").val();
- if (root_pw != root_pw_confirm) {
- report_err($("#rootdn-pw"), 'Directory Manager passwords do not match!');
- $("#rootdn-pw-confirm").css("border-color", "red");
- return;
- } else if (root_pw == ""){
- report_err($("#rootdn-pw"), 'Directory Manager password can not be empty!');
- $("#rootdn-pw-confirm").css("border-color", "red");
- return;
- } else if (root_pw.length < 8) {
- report_err($("#rootdn-pw"), 'Directory Manager password must have at least 8 characters');
- $("#rootdn-pw-confirm").css("border-color", "red");
- return;
- } else {
- setup_inf = setup_inf.replace('ROOTPW', root_pw);
- }
-
- // Backend/Suffix
- var backend_name = $("#backend-name").val();
- var backend_suffix = $("#backend-suffix").val();
- if ( (backend_name != "" && backend_suffix == "") || (backend_name == "" && backend_suffix != "") ) {
- if (backend_name == ""){
- report_err($("#backend-name"), 'If you specify a backend suffix, you must also specify a backend name');
- $("#backend-name").css("border-color", "red");
- return;
- } else {
- report_err($("#backend-suffix"), 'If you specify a backend name, you must also specify a backend suffix');
- $("#backend-suffix").css("border-color", "red");
- return;
- }
- }
- if (backend_name != ""){
- // We definitely have a backend name and suffix, next validate the suffix is a DN
- if (valid_dn(backend_suffix)) {
- // It's valid, add it
- setup_inf += "\n[backend-" + backend_name + "]\nsuffix = " + backend_suffix + "\n";
- } else {
- // Not a valid DN
- report_err($("#backend-suffix"), 'Invalid DN for Backend Suffix');
- return;
- }
- if ( $("#create-sample-entries").is(":checked") ) {
- setup_inf += '\nsample_entries = yes\n';
- } else if ( $("#create-suffix-entry").is(":checked") ) {
- setup_inf += '\ncreate_suffix_entry = yes\n';
- }
- }
-
- /*
- * Here are steps we take to create the instance
- *
- * [1] Get FQDN Name for nsslapd-localhost setting in setup file
- * [2] Create a file for the inf setup parameters
- * [3] Set strict permissions on that file
- * [4] Populate the new setup file with settings (including cleartext password)
- * [5] Create the instance
- * [6] Remove setup file
- */
- cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex, data) {
- // Failed to get FQDN
- popup_err("Failed to get hostname!", data);
- }).done(function (data){
- /*
- * We have FQDN, so set the hostname in inf file, and create the setup file
- */
- setup_inf = setup_inf.replace('FQDN', data);
- var setup_file = "/tmp/389-setup-" + (new Date).getTime() + ".inf";
- var rm_cmd = ['rm', setup_file];
- var create_file_cmd = ['touch', setup_file];
- cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
- // Failed to create setup file
- popup_err("Failed to create installation file!", data);
- }).done(function (){
- /*
- * We have our new setup file, now set permissions on that setup file before we add sensitive data
- */
- var chmod_cmd = ['chmod', '600', setup_file];
- cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
- // Failed to set permissions on setup file
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- $("#create-inst-spinner").hide();
- popup_err("Failed to set permission on setup file " + setup_file + ": ", data);
- }).done(function (){
- /*
- * Success we have our setup file and it has the correct permissions.
- * Now populate the setup file...
- */
- let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file];
- cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {
- // Failed to populate setup file
- popup_err("Failed to populate installation file!", data);
- }).done(function (){
- /*
- * Next, create the instance...
- */
- cmd = [DSCREATE, 'from-file', setup_file];
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV] }).fail(function(ex, data) {
- // Failed to create the new instance!
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- $("#create-inst-spinner").hide();
- popup_err("Failed to create instance!", data);
- }).done(function (){
- // Success!!! Now cleanup everything up...
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- $("#create-inst-spinner").hide();
- $("#server-list-menu").attr('disabled', false);
- $("#no-instances").hide();
- get_insts(); // Refresh server list
- popup_success("Successfully created instance: <b>slapd-" + new_server_id + "</b>");
- $("#create-inst-form").modal('toggle');
- });
- });
- $("#create-inst-spinner").show();
- });
- });
- }).fail(function(data) {
- console.log("failed: " + data.message);
- });
- });
-
- // Accordion opening/closings
- $(".ds-accordion-panel").css('display','none');
-
- $("#config-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Advanced Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Advanced Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $("#tuning-config-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Advanced Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Advanced Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $('#local-pwp-suffix').on('change', function () {
- // Reload the table for the new selected suffix
- get_and_set_localpwp();
- });
-
- // Edit Local Password Policy
- $(document).on('click', '.edit-local-pwp', function(e) {
- e.preventDefault();
- clear_local_pwp_form();
-
- var data = pwp_table.row( $(this).parents('tr') ).data();
- var policy_name = data[0];
-
- // lookup the entry, and get the current settings
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','localpwp', 'get', policy_name];
- log_cmd('.edit-local-pwp (click)', 'Get local password policy', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- localpwp_values = {}; // Clear it out
- var obj = JSON.parse(data);
- for (var attr in obj['attrs']) {
- var val = obj['attrs'][attr];
- $("#local-" + attr).val(val);
- if (val == "on") {
- $("#local-" + attr).prop('checked', true);
- $("#local-" + attr).trigger('change');
- } else if (val == "off") {
- $("#local-" + attr).prop('checked', false);
- $("#local-" + attr).trigger('change');
- }
- localpwp_values[attr] = val;
- }
- if ( obj['pwp_type'] == "User") {
- $("#user-pwp-radio").prop("checked", true );
- } else {
- $("#subtree-pwp-radio").prop("checked", true );
- }
-
- // Set the form header and fields
- $("#local-pwp-header").html("<b>Edit Local Password Policy</b>");
- $("#local-entry-dn").val(policy_name);
- // Disable radio buttons
- $("#subtree-pwp-radio").attr('disabled', true);
- $("#user-pwp-radio").attr('disabled', true);
- $("#local-entry-dn").attr('disabled', true);
-
- // Open form
- $("#local-pwp-form").modal('toggle');
-
- }).fail(function(data) {
- popup_err("Failed to get local password policy", data.message);
- });
- });
-
- // Mark this page as loaded
- server_page_loaded = 1;
- }); // servers.html loaded
-}); // Document ready
diff --git a/src/cockpit/389-console/webpack.config.js b/src/cockpit/389-console/webpack.config.js
index d3d766b..3389135 100644
--- a/src/cockpit/389-console/webpack.config.js
+++ b/src/cockpit/389-console/webpack.config.js
@@ -30,8 +30,6 @@ var info = {
"fonts",
"images",
"index.html",
- "servers.html",
- "servers.js",
"static",
"manifest.json"
]
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index 37f0437..d71ec5d 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -179,7 +179,7 @@ class DSLdapObject(DSLogging):
# ensure all the keys are lowercase
str_attrs = dict((k.lower(), v) for k, v in list(str_attrs.items()))
- response = json.dumps({"type": "entry", "dn": ensure_str(self._dn), "attrs": str_attrs})
+ response = json.dumps({"type": "entry", "dn": ensure_str(self._dn), "attrs": str_attrs}, indent=4)
return response
@@ -217,6 +217,17 @@ class DSLdapObject(DSLogging):
# How can we be sure this returns the primary one?
return ensure_str(self.get_attr_val(self._rdn_attribute))
+ def get_basedn(self):
+ """Get the suffix this entry belongs to
+ """
+ from lib389.backend import Backends
+ backends = Backends(self._instance).list()
+ for backend in backends:
+ suffix = backend.get_suffix()
+ if self._dn.endswith(suffix):
+ return suffix
+ return ""
+
def present(self, attr, value=None):
"""Assert that some attr, or some attr / value exist on the entry.
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 8173c1b..f21ff6d 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -949,4 +949,6 @@ class DatabaseConfig(DSLdapObject):
self._must_attributes = ['cn']
self._create_objectclasses = ['top', 'extensibleObject']
self._protected = True
- self._dn = "cn=config,cn=ldbm database,cn=plugins,cn=config"
+ # Have to set cn=bdb, but when we can choose between bdb and lmdb we'll
+ # have some hoops to jump through.
+ self._dn = "cn=bdb,cn=config,cn=ldbm database,cn=plugins,cn=config"
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
index 7dd45b3..231ffe8 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -168,7 +168,7 @@ def _generic_list(inst, basedn, log, manager_class, args=None):
ol = mc.list()
if len(ol) == 0:
if args and args.json:
- print(json.dumps({"type": "list", "items": []}))
+ print(json.dumps({"type": "list", "items": []}, indent=4))
else:
log.info("No objects to display")
elif len(ol) > 0:
@@ -182,7 +182,7 @@ def _generic_list(inst, basedn, log, manager_class, args=None):
else:
print(o_str)
if args and args.json:
- print(json.dumps(json_result))
+ print(json.dumps(json_result, indent=4))
# Display these entries better!
@@ -214,7 +214,7 @@ def _generic_get_attr(inst, basedn, log, manager_class, args=None):
else:
print(mc.display_attr(attr).rstrip())
if args.json:
- print(json.dumps({"type": "entry", "dn": mc._dn, "attrs": vals}))
+ print(json.dumps({"type": "entry", "dn": mc._dn, "attrs": vals}, indent=4))
def _generic_get_dn(inst, basedn, log, manager_class, dn, args=None):
@@ -247,6 +247,7 @@ def _generic_replace_attr(inst, basedn, log, manager_class, args=None):
if "=" in myattr:
[attr, val] = myattr.split("=", 1)
mc.replace(attr, val)
+ print("MARK val: " + val)
print("Successfully replaced \"{}\"".format(attr))
else:
raise ValueError("You must specify a value to replace the attribute ({})".format(myattr))
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index b62446e..aaa6613 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -144,7 +144,7 @@ def backend_list(inst, basedn, log, args):
be_list.sort()
if args.json:
- print(json.dumps({"type": "list", "items": be_list}))
+ print(json.dumps({"type": "list", "items": be_list}, indent=4))
else:
if len(be_list) > 0:
for be in be_list:
@@ -331,13 +331,13 @@ def backend_get_subsuffixes(inst, basedn, log, args):
if len(subsuffixes) > 0:
subsuffixes.sort()
if args.json:
- print(json.dumps({"type": "list", "items": subsuffixes}))
+ print(json.dumps({"type": "list", "items": subsuffixes}, indent=4))
else:
for sub in subsuffixes:
print(sub)
else:
if args.json:
- print(json.dumps({"type": "list", "items": []}))
+ print(json.dumps({"type": "list", "items": []}, indent=4))
else:
print("No sub-suffixes under this backend")
@@ -432,7 +432,7 @@ def backend_get_tree(inst, basedn, log, args):
# No suffixes, return empty list
if len(nodes) == 0:
if args.json:
- log.info(json.dumps(nodes))
+ log.info(json.dumps(nodes, indent=4))
else:
log.info("There are no suffixes defined")
else:
@@ -442,7 +442,7 @@ def backend_get_tree(inst, basedn, log, args):
# Done
if args.json:
- log.info(json.dumps(nodes))
+ log.info(json.dumps(nodes, indent=4))
else:
print_suffix_tree(nodes, 1, log)
@@ -580,7 +580,7 @@ def backend_get_index(inst, basedn, log, args):
else:
print(index.display())
if args.json:
- print(json.dumps({"type": "list", "items": results}))
+ print(json.dumps({"type": "list", "items": results}, indent=4))
def backend_list_index(inst, basedn, log, args):
@@ -600,7 +600,7 @@ def backend_list_index(inst, basedn, log, args):
print(index.display())
if args.json:
- print(json.dumps({"type": "list", "items": results}))
+ print(json.dumps({"type": "list", "items": results}, indent=4))
def backend_del_index(inst, basedn, log, args):
@@ -643,7 +643,7 @@ def backend_attr_encrypt(inst, basedn, log, args):
else:
for result in results:
json_results.append(json.loads(result.get_all_attrs_json()))
- print(json.dumps({"type": "list", "items": json_results}))
+ print(json.dumps({"type": "list", "items": json_results}, indent=4))
else:
if len(results) == 0:
@@ -694,7 +694,7 @@ def backend_list_vlv(inst, basedn, log, args):
print()
if args.json:
- print(json.dumps({"type": "list", "items": results}))
+ print(json.dumps({"type": "list", "items": results}, indent=4))
def backend_get_vlv(inst, basedn, log, args):
@@ -727,7 +727,7 @@ def backend_get_vlv(inst, basedn, log, args):
print()
if args.json:
- print(json.dumps({"type": "list", "items": results}))
+ print(json.dumps({"type": "list", "items": results}, indent=4))
def backend_create_vlv(inst, basedn, log, args):
diff --git a/src/lib389/lib389/cli_conf/chaining.py b/src/lib389/lib389/cli_conf/chaining.py
index 783a9f3..7c93eb3 100644
--- a/src/lib389/lib389/cli_conf/chaining.py
+++ b/src/lib389/lib389/cli_conf/chaining.py
@@ -69,7 +69,7 @@ def config_get(inst, basedn, log, args):
if args.avail_controls:
ctrls = chain_cfg.get_controls()
if args.json:
- print(json.dumps({"type": "list", "items": ctrls}))
+ print(json.dumps({"type": "list", "items": ctrls}, indent=4))
else:
print("Available Components:")
for ctrl in ctrls:
@@ -77,7 +77,7 @@ def config_get(inst, basedn, log, args):
if args.avail_comps:
comps = chain_cfg.get_comps()
if args.json:
- print(json.dumps({"type": "list", "items": comps}))
+ print(json.dumps({"type": "list", "items": comps}, indent=4))
else:
print("Available Controls:")
for comp in comps:
diff --git a/src/lib389/lib389/cli_conf/conflicts.py b/src/lib389/lib389/cli_conf/conflicts.py
index 620f68c..c16fc1a 100644
--- a/src/lib389/lib389/cli_conf/conflicts.py
+++ b/src/lib389/lib389/cli_conf/conflicts.py
@@ -18,7 +18,7 @@ def list_conflicts(inst, basedn, log, args):
results = []
for conflict in conflicts:
results.append(json.loads(conflict.get_all_attrs_json()))
- log.info(json.dumps({'type': 'list', 'items': results}))
+ log.info(json.dumps({'type': 'list', 'items': results}, indent=4))
else:
if len(conflicts) > 0:
for conflict in conflicts:
@@ -35,7 +35,7 @@ def cmp_conflict(inst, basedn, log, args):
results = []
results.append(json.loads(conflict.get_all_attrs_json()))
results.append(json.loads(valid_entry.get_all_attrs_json()))
- log.info(json.dumps({'type': 'list', 'items': results}))
+ log.info(json.dumps({'type': 'list', 'items': results}, indent=4))
else:
log.info("Conflict Entry:\n")
log.info(conflict.display(conflict_attrs))
@@ -64,7 +64,7 @@ def list_glue(inst, basedn, log, args):
results = []
for glue in glues:
results.append(json.loads(glue.get_all_attrs_json()))
- log.info(json.dumps({'type': 'list', 'items': results}))
+ log.info(json.dumps({'type': 'list', 'items': results}, indent=4))
else:
if len(glues) > 0:
for glue in glues:
diff --git a/src/lib389/lib389/cli_conf/monitor.py b/src/lib389/lib389/cli_conf/monitor.py
index ff3712e..19806e5 100644
--- a/src/lib389/lib389/cli_conf/monitor.py
+++ b/src/lib389/lib389/cli_conf/monitor.py
@@ -96,7 +96,7 @@ def disk_monitor(inst, basedn, log, args):
log.info("Percentage Used: " + percent + "%\n")
if args.json:
- log.info(json.dumps({"type": "list", "items": disk_list}))
+ log.info(json.dumps({"type": "list", "items": disk_list}, indent=4))
def create_parser(subparsers):
diff --git a/src/lib389/lib389/cli_conf/plugin.py b/src/lib389/lib389/cli_conf/plugin.py
index db2ba58..b50837c 100644
--- a/src/lib389/lib389/cli_conf/plugin.py
+++ b/src/lib389/lib389/cli_conf/plugin.py
@@ -53,7 +53,7 @@ def plugin_list(inst, basedn, log, args):
plugins = mc.list()
if len(plugins) == 0:
if args and args.json:
- print(json.dumps({"type": "list", "items": []}))
+ print(json.dumps({"type": "list", "items": []}, indent=4))
else:
plugin_log.info("No objects to display")
elif len(plugins) > 0:
@@ -67,7 +67,7 @@ def plugin_list(inst, basedn, log, args):
else:
plugin_log.info(plugin_data["cn"][0])
if args and args.json:
- print(json.dumps(json_result))
+ print(json.dumps(json_result, indent=4))
def plugin_get(inst, basedn, log, args):
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
index 67bfd87..2838afc 100644
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
@@ -8,6 +8,7 @@
import json
import ldap
+from lib389.backend import Backends
from lib389.utils import ensure_str
from lib389.pwpolicy import PwPolicyEntries, PwPolicyManager
from lib389.idm.account import Account
@@ -38,21 +39,30 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
attr_list = list(pwp_manager.arg_to_attr.values())
if "global" in policy_type.lower():
targetdn = 'cn=config'
- attr_list.extend(['passwordIsGlobalPolicy', 'nsslapd-pwpolicy_local'])
+ policydn = targetdn
+ basedn = targetdn
+ attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])
all_attrs = inst.config.get_attrs_vals_utf8(attr_list)
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
else:
policy = pwp_manager.get_pwpolicy_entry(targetdn)
- targetdn = policy.dn
+ basedn = policy.get_basedn()
+ policydn = policy.dn
all_attrs = policy.get_attrs_vals_utf8(attr_list)
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
if use_json:
- print(json.dumps({"type": "entry", "pwp_type": policy_type, "dn": ensure_str(targetdn), "attrs": attrs}))
+ print(json.dumps({
+ "dn": ensure_str(policydn),
+ "targetdn": targetdn,
+ "type": "entry",
+ "pwp_type": policy_type,
+ "basedn": basedn,
+ "attrs": attrs}, indent=4))
else:
if "global" in policy_type.lower():
response = "Global Password Policy: cn=config\n------------------------------------\n"
else:
- response = "Local {} Policy: {}\n------------------------------------\n".format(policy_type, targetdn)
+ response = "Local {} Policy for \"{}\": {}\n------------------------------------\n".format(policy_type, targetdn, policydn)
for key, value in list(attrs.items()):
if len(value) == 0:
value = ""
@@ -64,33 +74,55 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
def list_policies(inst, basedn, log, args):
log = log.getChild('list_policies')
- targetdn = args.DN[0]
+
+ if args.DN is None:
+ # list all the password policies for all the backends
+ targetdns = []
+ backends = Backends(inst).list()
+ for backend in backends:
+ targetdns.append(backend.get_suffix())
+ else:
+ targetdns = [args.DN]
if args.json:
result = {'type': 'list', 'items': []}
else:
result = ""
- # Verify target dn exists before getting started
- user_entry = Account(inst, args.DN[0])
- if not user_entry.exists():
- raise ValueError('The target entry dn does not exist')
-
- # User pwpolicy entry is under the container that is under the parent,
- # so we need to go one level up
- pwp_entries = PwPolicyEntries(inst, targetdn)
- for pwp_entry in pwp_entries.list():
- dn_comps = ldap.dn.explode_dn(pwp_entry.get_attr_val_utf8_l('cn'))
- dn_comps.pop(0)
- entrydn = ",".join(dn_comps)
- policy_type = _get_policy_type(inst, entrydn)
- if args.json:
- result['items'].append([entrydn, policy_type])
- else:
- result += "%s (%s)\n" % (entrydn, policy_type.lower())
+ for targetdn in targetdns:
+ # Verify target dn exists before getting started
+ user_entry = Account(inst, targetdn)
+ if not user_entry.exists():
+ raise ValueError('The target entry dn does not exist')
+
+ # User pwpolicy entry is under the container that is under the parent,
+ # so we need to go one level up
+ pwp_entries = PwPolicyEntries(inst, targetdn)
+ pwp_manager = PwPolicyManager(inst)
+ attr_list = list(pwp_manager.arg_to_attr.values())
+
+ for pwp_entry in pwp_entries.list():
+ dn_comps = ldap.dn.explode_dn(pwp_entry.get_attr_val_utf8_l('cn'))
+ dn_comps.pop(0)
+ entrydn = ",".join(dn_comps)
+ policy_type = _get_policy_type(inst, entrydn)
+ all_attrs = pwp_entry.get_attrs_vals_utf8(attr_list)
+ attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
+ if args.json:
+ result['items'].append(
+ {
+ "dn": pwp_entry.dn,
+ "targetdn": entrydn,
+ "pwp_type": policy_type,
+ "basedn": pwp_entry.get_basedn(),
+ "attrs": attrs
+ }
+ )
+ else:
+ result += "%s (%s)\n" % (entrydn, policy_type.lower())
if args.json:
- print(json.dumps(result))
+ print(json.dumps(result, indent=4))
else:
print(result)
@@ -173,7 +205,7 @@ def create_parser(subparsers):
# List all the local policies
list_parser = local_subcommands.add_parser('list', help='List all the local password policies')
list_parser.set_defaults(func=list_policies)
- list_parser.add_argument('DN', nargs=1, help='Suffix to search for local password policies')
+ list_parser.add_argument('DN', nargs='?', help='Suffix to search for local password policies')
# Get a local policy
get_parser = local_subcommands.add_parser('get', help='Get local password policy entry')
get_parser.set_defaults(func=get_local_policy)
@@ -216,11 +248,12 @@ def create_parser(subparsers):
set_parser.add_argument('--pwdmaxseq', help="The maximum number of allowed monotonic character sequences in a password")
set_parser.add_argument('--pwdmaxseqsets', help="The maximum number of allowed monotonic character sequences that can be duplicated in a password")
set_parser.add_argument('--pwdmaxclasschars', help="The maximum number of sequential characters from the same character class that is allowed in a password")
- set_parser.add_argument('--pwdmincatagories', help="The minimum number of syntax catagory checks")
+ set_parser.add_argument('--pwdmincatagories', help="The minimum number of syntax category checks")
set_parser.add_argument('--pwdmintokenlen', help="Sets the smallest attribute value length that is used for trivial/user words checking. This also impacts \"--pwduserattrs\"")
set_parser.add_argument('--pwdbadwords', help="A space-separated list of words that can not be in a password")
set_parser.add_argument('--pwduserattrs', help="A space-separated list of attributes whose values can not appear in the password (See \"--pwdmintokenlen\")")
- set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enfore CrackLib dictionary checking")
+ set_parser.add_argument('--pwpinheritglobal', help="Set to \"on\" to allow local policies to inherit the global policy")
+ set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enforce CrackLib dictionary checking")
set_parser.add_argument('--pwddictpath', help="Filesystem path to specific/custom CrackLib dictionary files")
# delete local password policy
del_parser = local_subcommands.add_parser('remove', help='Remove a local password policy')
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index c10e3ab..7acbb33 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -113,7 +113,7 @@ def get_ruv(inst, basedn, log, args):
ruv_dict = ruv.format_ruv()
ruvs = ruv_dict['ruvs']
if args and args.json:
- log.info(json.dumps({"type": "list", "items": ruvs}))
+ log.info(json.dumps({"type": "list", "items": ruvs}, indent=4))
else:
add_gap = False
for ruv in ruvs:
@@ -275,7 +275,7 @@ def list_suffixes(inst, basedn, log, args):
suffixes.append(replica.get_suffix())
if args.json:
- log.info(json.dumps({"type": "list", "items": suffixes}))
+ log.info(json.dumps({"type": "list", "items": suffixes}, indent=4))
else:
if len(suffixes) == 0:
log.info("There are no replicated suffixes")
@@ -289,7 +289,7 @@ def get_repl_status(inst, basedn, log, args):
replica = replicas.get(args.suffix)
status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd)
if args.json:
- log.info(json.dumps({"type": "list", "items": status}))
+ log.info(json.dumps({"type": "list", "items": status}, indent=4))
else:
for agmt in status:
log.info(agmt)
@@ -300,7 +300,7 @@ def get_repl_winsync_status(inst, basedn, log, args):
replica = replicas.get(args.suffix)
status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True)
if args.json:
- log.info(json.dumps({"type": "list", "items": status}))
+ log.info(json.dumps({"type": "list", "items": status}, indent=4))
else:
for agmt in status:
log.info(agmt)
@@ -459,7 +459,7 @@ def get_repl_monitor_info(inst, basedn, log, args):
report_items.append(report_item)
if args.json:
- log.info(json.dumps({"type": "list", "items": report_items}))
+ log.info(json.dumps({"type": "list", "items": report_items}, indent=4))
def create_cl(inst, basedn, log, args):
@@ -612,7 +612,7 @@ def list_agmts(inst, basedn, log, args):
else:
log.info(agmt.display())
if args.json:
- log.info(json.dumps(result))
+ log.info(json.dumps(result, indent=4))
def add_agmt(inst, basedn, log, args):
@@ -712,7 +712,7 @@ def check_init_agmt(inst, basedn, log, args):
elif error:
status = "Agreement initialization failed: " + error
if args.json:
- log.info(json.dumps(status))
+ log.info(json.dumps(status, indent=4))
else:
log.info(status)
@@ -787,7 +787,7 @@ def list_winsync_agmts(inst, basedn, log, args):
else:
log.info(agmt.display())
if args.json:
- log.info(json.dumps(result))
+ log.info(json.dumps(result, indent=4))
def add_winsync_agmt(inst, basedn, log, args):
@@ -907,7 +907,7 @@ def check_winsync_init_agmt(inst, basedn, log, args):
elif error:
status = "Agreement initialization failed."
if args.json:
- log.info(json.dumps(status))
+ log.info(json.dumps(status, indent=4))
else:
log.info(status)
@@ -945,7 +945,7 @@ def run_cleanallruv(inst, basedn, log, args):
clean_task.create(properties=properties)
rdn = clean_task.rdn
if args.json:
- log.info(json.dumps(rdn))
+ log.info(json.dumps(rdn, indent=4))
else:
log.info('Created task ' + rdn)
@@ -970,7 +970,7 @@ def list_cleanallruv(inst, basedn, log, args):
else:
log.info(task.display())
if args.json:
- log.info(json.dumps(result))
+ log.info(json.dumps(result, indent=4))
else:
if not tasks_found:
log.info("No CleanAllRUV tasks found")
@@ -1005,7 +1005,7 @@ def list_abort_cleanallruv(inst, basedn, log, args):
else:
log.info(task.display())
if args.json:
- log.info(json.dumps(result))
+ log.info(json.dumps(result, indent=4))
else:
if not tasks_found:
log.info("No CleanAllRUV abort tasks found")
diff --git a/src/lib389/lib389/cli_conf/saslmappings.py b/src/lib389/lib389/cli_conf/saslmappings.py
index ed079a1..0ba825e 100644
--- a/src/lib389/lib389/cli_conf/saslmappings.py
+++ b/src/lib389/lib389/cli_conf/saslmappings.py
@@ -1,11 +1,12 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2018 Red Hat, Inc.
+# Copyright (C) 2020 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import json
from lib389.saslmap import SaslMapping, SaslMappings
from lib389.utils import ensure_str
from lib389.cli_base import (
@@ -25,7 +26,21 @@ RDN = 'cn'
def sasl_map_list(inst, basedn, log, args):
- _generic_list(inst, basedn, log.getChild('sasl_map_list'), MANY, args)
+ if args.details:
+ # List SASL mappings with details
+ mappings = SaslMappings(inst).list()
+ result = {"type": "list", "items": []}
+ for sasl_map in mappings:
+ if args.json:
+ entry = sasl_map.get_all_attrs_json()
+ # Append decoded json object, because we are going to dump it later
+ result['items'].append(json.loads(entry))
+ else:
+ log.info(sasl_map.display())
+ if args.json:
+ log.info(json.dumps(result, indent=4))
+ else:
+ _generic_list(inst, basedn, log.getChild('sasl_map_list'), MANY, args)
def sasl_map_get(inst, basedn, log, args):
@@ -57,15 +72,29 @@ def sasl_map_delete(inst, basedn, log, args, warn=True):
if warn and args.json is False:
_warn(dn, msg="Deleting %s %s" % (SINGULAR.__name__, dn))
_generic_delete(inst, basedn, log.getChild('sasl_map_delete'), SINGULAR, dn, args)
+
+def sasl_get_supported(inst, basedn, log, args):
+ """Get a list of the supported sasl mechanisms"""
+ mechs = inst.rootdse.supported_sasl()
+ if args.json:
+ result = {'type': 'list', 'items': mechs}
+ log.info(json.dumps(result, indent=4, ))
+ else:
+ for mech in mechs:
+ log.info(mech)
def create_parser(subparsers):
- sasl_parser = subparsers.add_parser('sasl', help='Query and manipulate sasl mappings')
+ sasl_parser = subparsers.add_parser('sasl', help='Query and manipulate SASL mappings')
subcommands = sasl_parser.add_subparsers(help='sasl')
- list_mappings_parser = subcommands.add_parser('list', help='List avaliable SASL mappings')
+ list_mappings_parser = subcommands.add_parser('list', help='List available SASL mappings')
list_mappings_parser.set_defaults(func=sasl_map_list)
+ list_mappings_parser.add_argument('--details', action='store_true', default=False,
+ help="Get each SASL Mapping in detail.")
+ get_mech_parser= subcommands.add_parser('get-mechs', help='List available SASL mechanisms')
+ get_mech_parser.set_defaults(func=sasl_get_supported)
get_parser = subcommands.add_parser('get', help='get')
get_parser.set_defaults(func=sasl_map_get)
diff --git a/src/lib389/lib389/cli_conf/schema.py b/src/lib389/lib389/cli_conf/schema.py
index 7764356..463f130 100644
--- a/src/lib389/lib389/cli_conf/schema.py
+++ b/src/lib389/lib389/cli_conf/schema.py
@@ -36,7 +36,7 @@ def list_all(inst, basedn, log, args):
print(dump_json({'type': 'schema',
'objectclasses': objectclass_elems,
'attributetypes': attributetype_elems,
- 'matchingrules': matchingrule_elems}))
+ 'matchingrules': matchingrule_elems}, indent=4))
else:
separator_line = "".join(["-" for _ in range(50)])
print("Objectclasses:\n", separator_line)
@@ -54,7 +54,7 @@ def list_attributetypes(inst, basedn, log, args):
log = log.getChild('list_attributetypes')
schema = Schema(inst)
if args is not None and args.json:
- print(dump_json(schema.get_attributetypes(json=True)))
+ print(dump_json(schema.get_attributetypes(json=True), indent=4))
else:
for attributetype in schema.get_attributetypes():
print(attributetype)
@@ -64,7 +64,7 @@ def list_objectclasses(inst, basedn, log, args):
log = log.getChild('list_objectclasses')
schema = Schema(inst)
if args is not None and args.json:
- print(dump_json(schema.get_objectclasses(json=True)))
+ print(dump_json(schema.get_objectclasses(json=True), indent=4))
else:
for oc in schema.get_objectclasses():
print(oc)
@@ -74,7 +74,7 @@ def list_matchingrules(inst, basedn, log, args):
log = log.getChild('list_matchingrules')
schema = Schema(inst)
if args is not None and args.json:
- print(dump_json(schema.get_matchingrules(json=True)))
+ print(dump_json(schema.get_matchingrules(json=True), indent=4))
else:
for mr in schema.get_matchingrules():
print(mr)
@@ -86,7 +86,7 @@ def query_attributetype(inst, basedn, log, args):
# Need the query type
attr = _get_arg(args.name, msg="Enter attribute to query")
if args.json:
- print(dump_json(schema.query_attributetype(attr, json=args.json)))
+ print(dump_json(schema.query_attributetype(attr, json=args.json), indent=4))
else:
attributetype, must, may = schema.query_attributetype(attr, json=args.json)
print(attributetype)
@@ -107,7 +107,7 @@ def query_objectclass(inst, basedn, log, args):
oc = _get_arg(args.name, msg="Enter objectclass to query")
result = schema.query_objectclass(oc, json=args.json)
if args.json:
- print(dump_json(result))
+ print(dump_json(result, indent=4))
else:
print(result)
@@ -119,7 +119,7 @@ def query_matchingrule(inst, basedn, log, args):
attr = _get_arg(args.name, msg="Enter attribute to query")
result = schema.query_matchingrule(attr, json=args.json)
if args.json:
- print(dump_json(result))
+ print(dump_json(result, indent=4))
else:
print(result)
@@ -203,7 +203,7 @@ def get_syntaxes(inst, basedn, log, args):
schema = Schema(inst)
result = schema.get_attr_syntaxes(json=args.json)
if args.json:
- print(dump_json(result))
+ print(dump_json(result, indent=4))
else:
for id, name in result.items():
print("%s (%s)", name, id)
diff --git a/src/lib389/lib389/cli_conf/security.py b/src/lib389/lib389/cli_conf/security.py
index 1d60a2f..c565974 100644
--- a/src/lib389/lib389/cli_conf/security.py
+++ b/src/lib389/lib389/cli_conf/security.py
@@ -83,7 +83,7 @@ def _security_generic_get(inst, basedn, logs, args, attrs_map):
val = ""
result[props.attr.lower()] = val
if args.json:
- print(json.dumps({'type': 'list', 'items': result}))
+ print(json.dumps({'type': 'list', 'items': result}, indent=4))
else:
print('\n'.join([f'{attr}: {value or ""}' for attr, value in result.items()]))
@@ -195,7 +195,7 @@ def security_ciphers_list(inst, basedn, log, args):
lst = enc.ciphers
if args.json:
- print(json.dumps({'type': 'list', 'items': lst}))
+ print(json.dumps({'type': 'list', 'items': lst}, indent=4))
else:
if lst == []:
log.getChild('security').warn('List of ciphers is empty')
@@ -274,7 +274,7 @@ def cert_list(inst, basedn, log, args):
log.info('Expires: {}'.format(cert[3]))
log.info('Trust Flags: {}\n'.format(cert[4]))
if args.json:
- log.info(json.dumps(cert_list))
+ log.info(json.dumps(cert_list, indent=4))
def cacert_list(inst, basedn, log, args):
@@ -304,7 +304,7 @@ def cacert_list(inst, basedn, log, args):
log.info('Expires: {}'.format(cert[3]))
log.info('Trust Flags: {}\n'.format(cert[4]))
if args.json:
- log.info(json.dumps(cert_list))
+ log.info(json.dumps(cert_list, indent=4))
def cert_get(inst, basedn, log, args):
@@ -323,7 +323,7 @@ def cert_get(inst, basedn, log, args):
'expires': details[3],
'flags': details[4],
}
- }
+ }, indent=4
)
)
else:
diff --git a/src/lib389/lib389/cli_ctl/health.py b/src/lib389/lib389/cli_ctl/health.py
index a420163..10eaa44 100644
--- a/src/lib389/lib389/cli_ctl/health.py
+++ b/src/lib389/lib389/cli_ctl/health.py
@@ -96,7 +96,7 @@ def health_check_run(inst, log, args):
if not args.json:
log.info("No issues found.")
else:
- log.info(json.dumps(report))
+ log.info(json.dumps(report, indent=4))
else:
plural = ""
if count > 1:
@@ -109,7 +109,7 @@ def health_check_run(inst, log, args):
idx += 1
log.info('\n\n===== End Of Report ({} Issue{} found) ====='.format(count, plural))
else:
- log.info(json.dumps(report))
+ log.info(json.dumps(report, indent=4))
disconnect_instance(inst)
diff --git a/src/lib389/lib389/cli_ctl/instance.py b/src/lib389/lib389/cli_ctl/instance.py
index f0111f3..bdeaa9f 100644
--- a/src/lib389/lib389/cli_ctl/instance.py
+++ b/src/lib389/lib389/cli_ctl/instance.py
@@ -52,7 +52,7 @@ def instance_stop(inst, log, args):
def instance_status(inst, log, args):
if args.json:
- print(json.dumps({"type": "result", "running": inst.status()}))
+ print(json.dumps({"type": "result", "running": inst.status()}, indent=4))
return
if inst.status() is True:
diff --git a/src/lib389/lib389/cli_ctl/nsstate.py b/src/lib389/lib389/cli_ctl/nsstate.py
index 6a74178..20855a0 100644
--- a/src/lib389/lib389/cli_ctl/nsstate.py
+++ b/src/lib389/lib389/cli_ctl/nsstate.py
@@ -15,7 +15,7 @@ def get_nsstate(inst, log, args):
dse_ldif = DSEldif(inst)
states = dse_ldif.readNsState(suffix=args.suffix, flip=args.flip)
if args.json:
- log.info(json.dumps(states))
+ log.info(json.dumps(states, indent=4))
else:
for state in states:
log.info("Replica DN: " + state['dn'])
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
index b74ff6f..8653cb1 100644
--- a/src/lib389/lib389/pwpolicy.py
+++ b/src/lib389/lib389/pwpolicy.py
@@ -27,44 +27,45 @@ class PwPolicyManager(object):
self.arg_to_attr = {
'pwdlocal': 'nsslapd-pwpolicy-local',
'pwdscheme': 'passwordstoragescheme',
- 'pwdchange': 'passwordChange',
- 'pwdmustchange': 'passwordMustChange',
- 'pwdhistory': 'passwordHistory',
- 'pwdhistorycount': 'passwordInHistory',
- 'pwdadmin': 'passwordAdminDN',
- 'pwdtrack': 'passwordTrackUpdateTime',
- 'pwdwarning': 'passwordWarning',
- 'pwdisglobal': 'passwordIsGlobalPolicy',
- 'pwdexpire': 'passwordExp',
- 'pwdmaxage': 'passwordMaxAge',
- 'pwdminage': 'passwordMinAge',
- 'pwdgracelimit': 'passwordGraceLimit',
- 'pwdsendexpiring': 'passwordSendExpiringTime',
- 'pwdlockout': 'passwordLockout',
- 'pwdunlock': 'passwordUnlock',
- 'pwdlockoutduration': 'passwordLockoutDuration',
- 'pwdmaxfailures': 'passwordMaxFailure',
- 'pwdresetfailcount': 'passwordResetFailureCount',
- 'pwdchecksyntax': 'passwordCheckSyntax',
- 'pwdminlen': 'passwordMinLength',
- 'pwdmindigits': 'passwordMinDigits',
- 'pwdminalphas': 'passwordMinAlphas',
- 'pwdminuppers': 'passwordMinUppers',
- 'pwdminlowers': 'passwordMinLowers',
- 'pwdminspecials': 'passwordMinSpecials',
- 'pwdmin8bits': 'passwordMin8bit',
- 'pwdmaxrepeats': 'passwordMaxRepeats',
- 'pwdpalindrome': 'passwordPalindrome',
- 'pwdmaxseq': 'passwordMaxSequence',
- 'pwdmaxseqsets': 'passwordMaxSeqSets',
- 'pwdmaxclasschars': 'passwordMaxClassChars',
- 'pwdmincatagories': 'passwordMinCategories',
- 'pwdmintokenlen': 'passwordMinTokenLength',
- 'pwdbadwords': 'passwordBadWords',
- 'pwduserattrs': 'passwordUserAttributes',
- 'pwddictcheck': 'passwordDictCheck',
- 'pwddictpath': 'passwordDictPath',
- 'pwdallowhash': 'nsslapd-allow-hashed-passwords'
+ 'pwdchange': 'passwordchange',
+ 'pwdmustchange': 'passwordmustchange',
+ 'pwdhistory': 'passwordhistory',
+ 'pwdhistorycount': 'passwordinhistory',
+ 'pwdadmin': 'passwordadmindn',
+ 'pwdtrack': 'passwordtrackupdatetime',
+ 'pwdwarning': 'passwordwarning',
+ 'pwdisglobal': 'passwordisglobalpolicy',
+ 'pwdexpire': 'passwordexp',
+ 'pwdmaxage': 'passwordmaxage',
+ 'pwdminage': 'passwordminage',
+ 'pwdgracelimit': 'passwordgracelimit',
+ 'pwdsendexpiring': 'passwordsendexpiringtime',
+ 'pwdlockout': 'passwordlockout',
+ 'pwdunlock': 'passwordunlock',
+ 'pwdlockoutduration': 'passwordlockoutduration',
+ 'pwdmaxfailures': 'passwordmaxfailure',
+ 'pwdresetfailcount': 'passwordresetfailurecount',
+ 'pwdchecksyntax': 'passwordchecksyntax',
+ 'pwdminlen': 'passwordminlength',
+ 'pwdmindigits': 'passwordmindigits',
+ 'pwdminalphas': 'passwordminalphas',
+ 'pwdminuppers': 'passwordminuppers',
+ 'pwdminlowers': 'passwordminlowers',
+ 'pwdminspecials': 'passwordminspecials',
+ 'pwdmin8bits': 'passwordmin8bit',
+ 'pwdmaxrepeats': 'passwordmaxrepeats',
+ 'pwdpalindrome': 'passwordpalindrome',
+ 'pwdmaxseq': 'passwordmaxsequence',
+ 'pwdmaxseqsets': 'passwordmaxseqsets',
+ 'pwdmaxclasschars': 'passwordmaxclasschars',
+ 'pwdmincatagories': 'passwordmincategories',
+ 'pwdmintokenlen': 'passwordmintokenlength',
+ 'pwdbadwords': 'passwordbadwords',
+ 'pwduserattrs': 'passworduserattributes',
+ 'pwddictcheck': 'passworddictcheck',
+ 'pwddictpath': 'passworddictpath',
+ 'pwdallowhash': 'nsslapd-allow-hashed-passwords',
+ 'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global'
}
def is_subtree_policy(self, dn):
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months