#50481 Issue 50325 - Add Security tab to UI
Closed 3 years ago by spichugi. Opened 4 years ago by mreynolds.
mreynolds/389-ds-base ticket50325  into  master

@@ -1,4 +1,7 @@ 

  {

      "presets": ["@babel/env",

-                 "@babel/preset-react"]

+                 "@babel/preset-react"],

+     "plugins": [

+         "@babel/plugin-proposal-class-properties"

+     ]

  }

@@ -104,6 +104,105 @@ 

          "@babel/types": "^7.0.0"

        }

      },

+     "@babel/helper-create-class-features-plugin": {

+       "version": "7.4.4",

+       "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz",

+       "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==",

+       "dev": true,

+       "requires": {

+         "@babel/helper-function-name": "^7.1.0",

+         "@babel/helper-member-expression-to-functions": "^7.0.0",

+         "@babel/helper-optimise-call-expression": "^7.0.0",

+         "@babel/helper-plugin-utils": "^7.0.0",

+         "@babel/helper-replace-supers": "^7.4.4",

+         "@babel/helper-split-export-declaration": "^7.4.4"

+       },

+       "dependencies": {

+         "@babel/generator": {

+           "version": "7.4.4",

+           "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",

+           "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",

+           "dev": true,

+           "requires": {

+             "@babel/types": "^7.4.4",

+             "jsesc": "^2.5.1",

+             "lodash": "^4.17.11",

+             "source-map": "^0.5.0",

+             "trim-right": "^1.0.1"

+           }

+         },

+         "@babel/helper-replace-supers": {

+           "version": "7.4.4",

+           "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz",

+           "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==",

+           "dev": true,

+           "requires": {

+             "@babel/helper-member-expression-to-functions": "^7.0.0",

+             "@babel/helper-optimise-call-expression": "^7.0.0",

+             "@babel/traverse": "^7.4.4",

+             "@babel/types": "^7.4.4"

+           }

+         },

+         "@babel/helper-split-export-declaration": {

+           "version": "7.4.4",

+           "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",

+           "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",

+           "dev": true,

+           "requires": {

+             "@babel/types": "^7.4.4"

+           }

+         },

+         "@babel/parser": {

+           "version": "7.4.5",

+           "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz",

+           "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==",

+           "dev": true

+         },

+         "@babel/traverse": {

+           "version": "7.4.5",

+           "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz",

+           "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==",

+           "dev": true,

+           "requires": {

+             "@babel/code-frame": "^7.0.0",

+             "@babel/generator": "^7.4.4",

+             "@babel/helper-function-name": "^7.1.0",

+             "@babel/helper-split-export-declaration": "^7.4.4",

+             "@babel/parser": "^7.4.5",

+             "@babel/types": "^7.4.4",

+             "debug": "^4.1.0",

+             "globals": "^11.1.0",

+             "lodash": "^4.17.11"

+           }

+         },

+         "@babel/types": {

+           "version": "7.4.4",

+           "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",

+           "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",

+           "dev": true,

+           "requires": {

+             "esutils": "^2.0.2",

+             "lodash": "^4.17.11",

+             "to-fast-properties": "^2.0.0"

+           }

+         },

+         "debug": {

+           "version": "4.1.1",

+           "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",

+           "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",

+           "dev": true,

+           "requires": {

+             "ms": "^2.1.1"

+           }

+         },

+         "source-map": {

+           "version": "0.5.7",

+           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",

+           "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",

+           "dev": true

+         }

+       }

+     },

      "@babel/helper-define-map": {

        "version": "7.1.0",

        "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz",
@@ -336,6 +435,16 @@ 

          "@babel/plugin-syntax-async-generators": "^7.0.0"

        }

      },

+     "@babel/plugin-proposal-class-properties": {

+       "version": "7.4.4",

+       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz",

+       "integrity": "sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==",

+       "dev": true,

+       "requires": {

+         "@babel/helper-create-class-features-plugin": "^7.4.4",

+         "@babel/helper-plugin-utils": "^7.0.0"

+       }

+     },

      "@babel/plugin-proposal-json-strings": {

        "version": "7.0.0",

        "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz",
@@ -7202,6 +7311,14 @@ 

          "warning": "^3.0.0"

        }

      },

+     "react-switch": {

+       "version": "5.0.0",

+       "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-5.0.0.tgz",

+       "integrity": "sha512-+zxY9xj9dMc8Y4gv/kkqQrirfEiIQ+SlQfJDW1Wi81L3xoh1fcbBYyJyh0TnhM/U/b6HxuBmkmU4Ooxgtuoavw==",

+       "requires": {

+         "prop-types": "^15.6.2"

+       }

+     },

      "react-transition-group": {

        "version": "2.5.1",

        "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.1.tgz",

@@ -19,6 +19,7 @@ 

      "@babel/core": "^7.0.0",

      "@babel/preset-env": "^7.0.0",

      "@babel/preset-react": "^7.0.0",

+     "@babel/plugin-proposal-class-properties": "^7.0.0",

      "ajv": "^6.0.0",

      "audit-ci": "^1.7.0",

      "babel-eslint": "^9.0.0",
@@ -57,6 +58,7 @@ 

      "react-bootstrap": "0.32.4",

      "react-bootstrap-typeahead": "3.2.4",

      "react-dom": "16.6.1",

+     "react-switch": "^5.0.0",

      "recompose": "0.30.0",

      "table-resolver": "4.1.1"

    }

@@ -44,7 +44,7 @@ 

  /* Main nav page index.html */

  .ds-content {

      padding: 0;

-     padding-top: 115px;

+     padding-top: 115px;  /* this pushes the content below fixed nav bar */

      padding-bottom: 50px;

      margin-top: 0;

      margin-left: 25px;
@@ -672,6 +672,11 @@ 

      padding-bottom: 10px;

  }

  

+ .ds-cipher-width {

+     max-width: 350px !important;

+     min-width: 350px !important;

+ }

+ 

  /*

   * Popup modal stuff

  */
@@ -1637,8 +1642,8 @@ 

      font-size: 16px;

  }

  

- .ds-no-padding () {

-     padding: 0 !imporant;

+ .ds-no-padding {

+     padding-right: 0 !important;

  }

  

  .alert {
@@ -1650,6 +1655,10 @@ 

      margin-top: 5%;

  }

  

+ .ds-select {

+     width: 120px;

+ }

+ 

  .treeview .list-group-item {

      /* remove focus border */

      outline: none;
@@ -1680,3 +1689,174 @@ 

  .ds-width-auto {

      width: 100%;

  }

+ 

+ /* Dual List CSS */

+ .dual-list-pf-arrows {

+     display: inline-block;

+     margin: auto;

+     position: relative;

+     bottom: 170px;

+     font-size: 23px;

+     color: #bbb;

+ }

+ @media only screen and (max-width: 600px) {

+     .dual-list-pf-arrows {

+         display: block;

+         position: inherit;

+         margin: 5px 0;

+         padding-left: 79px;

+     }

+ }

+ 

+ .dual-list-pf-arrows span {

+     display: block;

+     margin: 25px;

+     cursor: pointer;

+     transition: color 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);

+     transform: rotate(-90deg);

+ }

+ @media only screen and (max-width: 600px) {

+     .dual-list-pf-arrows span {

+         display: inline;

+         margin: 0 20px 0 0;

+     }

+ }

+ 

+ .dual-list-pf-arrows span:hover {

+       color: #8b8d8f;

+ }

+ 

+ .dual-list-pf-body {

+     height: 375px;

+     width: 320px;

+     overflow-y: scroll;

+     overflow-x: auto;

+     display: inline-grid;

+     align-content: flex-start;

+ }

+ 

+ .dual-list-pf-body::-webkit-scrollbar {

+     width: 12px;

+     height: 12px;

+     background: #fafafa;

+ }

+ 

+ .dual-list-pf-body::-webkit-scrollbar-thumb {

+     background: #d1d1d1;

+     border-radius: 6px;

+     border: 3px solid transparent;

+     background-clip: content-box;

+ }

+ 

+ .dual-list-pf-body::-webkit-scrollbar-thumb:hover {

+       background: #bbb;

+       border-radius: 6px;

+       border: 3px solid transparent;

+       background-clip: content-box;

+ }

+ 

+ .dual-list-pf-filter {

+     margin-left: 20px;

+ }

+ 

+ .dual-list-pf-filter input {

+     background-color: #f5f5f5;

+     border: 1px solid #ededed;

+     width: 145px;

+     padding: 0 22px 0 5px;

+     margin-top: 3px;

+     margin-bottom: 3px;

+ }

+ 

+ .dual-list-pf-filter .search-icon {

+     position: relative;

+     right: 20px;

+     bottom: 1px;

+     color: #bbb;

+ }

+ 

+ .dual-list-pf-filter ::-webkit-input-placeholder {

+     font-style: italic;

+ }

+ 

+ .dual-list-pf-footer {

+     padding: 10px;

+     border-top: 1px solid #d1d1d1;

+ }

+ 

+ .dual-list-pf-heading {

+     border-bottom: 1px solid #d1d1d1;

+ }

+ 

+ .dual-list-pf-item {

+     padding: 5px 0;

+     margin-bottom: 0;

+     font-weight: 400;

+     transition: background 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), color 0.3s ease-out;

+     cursor: pointer;

+     white-space: nowrap;

+ }

+ 

+ .dual-list-pf-item input[type='checkbox'] {

+     position: relative;

+     left: 10px;

+     vertical-align: top;

+     cursor: pointer;

+ }

+ 

+ .dual-list-pf-item.selected {

+     background-color: #0088ce;

+     color: white;

+ }

+ 

+ .dual-list-pf-item.disabled {

+     cursor: not-allowed;

+     background: #f5f5f5;

+     color: #8b8d8f;

+ }

+ 

+ .dual-list-pf-item.disabled input[type='checkbox'] {

+     cursor: not-allowed;

+ }

+ 

+ .dual-list-pf-item.child {

+     padding-left: 22px;

+ }

+ 

+ .dual-list-pf-item:hover:not(.selected):not(.disabled) {

+     background-color: #bee1f4;

+     color: inherit;

+ }

+ 

+ .dual-list-pf-item-label {

+     margin-left: 20px;

+ }

+ 

+ .dual-list-pf-main-checkbox {

+     position: relative;

+     left: 10px;

+     vertical-align: text-top;

+     cursor: pointer;

+ }

+ 

+ .dual-list-pf-no-items {

+     margin-top: 30px;

+     text-align: center;

+ }

+ 

+ .dual-list-pf-selector {

+     display: inline-block;

+     border: 1px solid #d1d1d1;

+     user-select: none;

+ }

+ 

+ .dual-list-pf-sort-icon {

+   cursor: pointer;

+ }

+ 

+ .dropdown-kebab-pf.btn-group {

+     margin-left: 10px;

+     float: right;

+     margin-right: 10px;

+ }

+ /* End of dual list */

@@ -8,7 +8,7 @@ 

   * to track the loading, and once all the pages are loaded, then we can load the config

   */

  var server_page_loaded = 0;

- var security_page_loaded = 0;

+ var security_page_loaded = 1;

  var db_page_loaded = 1;

  var repl_page_loaded = 0;

  var plugin_page_loaded = 1;
@@ -482,4 +482,8 @@ 

      $(".all-pages").hide();

      $("#monitor-content").show();

    });

+   $("#security-tab").on("click", function() {

+     $(".all-pages").hide();

+     $("#security-content").show();

+   });

  });

@@ -3,6 +3,7 @@ 

  import { Plugins } from "./plugins.jsx";

  import { Database } from "./database.jsx";

  import { Monitor } from "./monitor.jsx";

+ import { Security } from "./security.jsx";

  

  var serverIdElem;

  
@@ -35,6 +36,12 @@ 

          <Monitor serverId={serverIdElem} key={tabKey} />,

          document.getElementById("monitor")

      );

+ 

+     // Security tab

+     ReactDOM.render(

+         <Security serverId={serverIdElem} key={tabKey} />,

+         document.getElementById("security")

+     );

  }

  

  // We have to create the wrappers because this is no simple way

@@ -21,7 +21,6 @@ 

    <script src="ds.js"></script>

    <script src="schema.js"></script>

    <script src="servers.js"></script>

-   <script src="security.js"></script>

    <script src="replication.js"></script>

    <link href="static/bootstrap.min.css" rel="stylesheet">

    <link href="static/jquery.dataTables.min.css" type="text/css" rel="stylesheet">
@@ -80,23 +79,10 @@ 

              </li>

  

              <!-- Security navtab -->

-             <li class="dropdown ds-nav-tab">

-               <a href="#0" class="dropdown-toggle ds-tab-list" data-toggle="dropdown" id="security-tab">

+             <li class="ds-nav-tab">

+               <a href="#0" class="ds-tab-list ds-tab-standalone" id="security-tab">

                  Security

-                 <b class="caret"></b>

                </a>

-               <ul class="dropdown-menu ds-nav-item">

-                 <li><a href="#0" class="ds-nav-choice" id="sec-config-btn" parent-id="security-tab">Security Settings</a></li>

-                 <li class="dropdown-submenu">

-                   <a tabindex="-1" href="#0">Certificate Management</a>

-                   <ul class="dropdown-menu">

-                     <li><a href="#0" class="ds-nav-choice" id="sec-cacert-btn"  parent-id="security-tab">CA Certificates</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="sec-srvcert-btn" parent-id="security-tab">Server Certificates</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="sec-revoked-btn" parent-id="security-tab">Revoked Certificates</a></li>

-                   </ul>

-                 </li>

-                 <li><a href="#0" class="ds-nav-choice" id="sec-ciphers-btn" parent-id="security-tab">Supported Ciphers</a></li>

-               </ul>

              </li>

  

              <!-- Database navtab -->
@@ -501,6 +487,7 @@ 

        </div>

  

        <div id="security-content" class="all-pages" hidden>

+           <div id="security"></div>

        </div>

  

        <div id="database-content" class="all-pages" hidden>

@@ -53,7 +53,7 @@ 

                      },

                      cell: {

                          props: {

-                             index: 2

+                             index: 1

                          },

                          formatters: [

                              (value, { rowData }) => {
@@ -75,6 +75,7 @@ 

              ]

          };

          this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

      }

  

      getSingleColumn () {
@@ -345,6 +346,7 @@ 

              ]

          };

          this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

      }

  

      getSingleColumn () {
@@ -508,6 +510,7 @@ 

          };

  

          this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

      }

  

      getSingleColumn () {
@@ -703,6 +706,7 @@ 

          };

  

          this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

      } // Constructor

  

      getColumns() {
@@ -877,6 +881,7 @@ 

          };

  

          this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

      } // Constructor

  

      getColumns() {

@@ -0,0 +1,617 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

+ import {

+     Nav,

+     NavItem,

+     TabContainer,

+     TabContent,

+     TabPane,

+     Button,

+     Spinner,

+     noop

+ } from "patternfly-react";

+ import { ConfirmPopup } from "../../lib/notifications.jsx";

+ import {

+     CertTable

+ } from "./securityTables.jsx";

+ import {

+     EditCertModal,

+     SecurityAddCertModal,

+     SecurityAddCACertModal,

+ } from "./securityModals.jsx";

+ import PropTypes from "prop-types";

+ import "../../css/ds.css";

+ import { log_cmd } from "../../lib/tools.jsx";

+ 

+ export class CertificateManagement extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             activeKey: 1,

+             ServerCerts: this.props.ServerCerts,

+             CACerts: this.props.CACerts,

+             showEditModal: false,

+             showAddModal: false,

+             modalSpinner: false,

+             showConfirmDelete: false,

+             certName: "",

+             certFile: "",

+             flags: "",

+             errObj: {},

+             isCACert: false,

+             showConfirmCAChange: false,

+             loading: false,

+         };

+ 

+         this.handleNavSelect = this.handleNavSelect.bind(this);

+         this.addCACert = this.addCACert.bind(this);

+         this.handleAddChange = this.handleAddChange.bind(this);

+         this.addCert = this.addCert.bind(this);

+         this.showAddModal = this.showAddModal.bind(this);

+         this.closeAddModal = this.closeAddModal.bind(this);

+         this.showAddCAModal = this.showAddCAModal.bind(this);

+         this.closeAddCAModal = this.closeAddCAModal.bind(this);

+         this.showEditModal = this.showEditModal.bind(this);

+         this.closeEditModal = this.closeEditModal.bind(this);

+         this.showEditCAModal = this.showEditCAModal.bind(this);

+         this.handleFlagChange = this.handleFlagChange.bind(this);

+         this.editCert = this.editCert.bind(this);

+         this.doEditCert = this.doEditCert.bind(this);

+         this.closeConfirmCAChange = this.closeConfirmCAChange.bind(this);

+         this.showDeleteConfirm = this.showDeleteConfirm.bind(this);

+         this.delCert = this.delCert.bind(this);

+         this.closeConfirmDelete = this.closeConfirmDelete.bind(this);

+         this.reloadCerts = this.reloadCerts.bind(this);

+         this.reloadCACerts = this.reloadCACerts.bind(this);

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({

+             activeKey: key

+         });

+     }

+ 

+     showAddModal () {

+         this.setState({

+             showAddModal: true,

+             errObj: {certName: true, certFile: true}

+         });

+     }

+ 

+     closeAddModal () {

+         this.setState({

+             showAddModal: false,

+             certName: "",

+             certFile: "",

+         });

+     }

+ 

+     showAddCAModal () {

+         this.setState({

+             showAddCAModal: true,

+             errObj: {certName: true, certFile: true}

+         });

+     }

+ 

+     closeAddCAModal () {

+         this.setState({

+             showAddCAModal: false,

+             certName: "",

+             certFile: "",

+         });

+     }

+ 

+     addCert () {

+         if (this.state.certName == "") {

+             this.props.addNotification(

+                 "warning",

+                 `Missing certificate nickname`

+             );

+             return;

+         } else if (this.state.certFile == "") {

+             this.props.addNotification(

+                 "warning",

+                 `Missing certificate file name`

+             );

+             return;

+         }

+ 

+         this.setState({

+             modalSpinner: true,

+             loading: true,

+         });

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "certificate", "add", "--name=" + this.state.certName, "--file=" + this.state.certFile

+         ];

+         log_cmd("addCert", "Adding server cert", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.reloadCACerts();

+                     this.setState({

+                         showAddModal: false,

+                         certFile: '',

+                         certName: '',

+                         modalSpinner: false

+                     });

+                     this.props.addNotification(

+                         "success",

+                         `Successfully added certificate`

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.setState({

+                         modalSpinner: false,

+                         loading: false,

+                     });

+                     this.props.addNotification(

+                         "error",

+                         `Error adding certificate - ${msg}`

+                     );

+                 });

+     }

+ 

+     addCACert () {

+         if (this.state.certName == "") {

+             this.props.addNotification(

+                 "warning",

+                 `Missing certificate nickname`

+             );

+             return;

+         } else if (this.state.certFile == "") {

+             this.props.addNotification(

+                 "warning",

+                 `Missing certificate file name`

+             );

+             return;

+         }

+ 

+         this.setState({

+             modalSpinner: true,

+             loading: true,

+         });

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ca-certificate", "add", "--name=" + this.state.certName, "--file=" + this.state.certFile

+         ];

+         log_cmd("addCACert", "Adding CA certificate", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.reloadCACerts();

+                     this.setState({

+                         showAddCAModal: false,

+                         certFile: '',

+                         certName: '',

+                         modalSpinner: false,

+                     });

+                     this.props.addNotification(

+                         "success",

+                         `Successfully added certificate`

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.setState({

+                         modalSpinner: false,

+                         loading: false,

+                     });

+                     this.props.addNotification(

+                         "error",

+                         `Error adding certificate - ${msg}`

+                     );

+                 });

+     }

+ 

+     showDeleteConfirm(dataRow) {

+         this.setState({

+             showConfirmDelete: true,

+             certName: dataRow.nickname[0],

+         });

+     }

+ 

+     delCert () {

+         this.setState({

+             modalSpinner: true,

+             loading: true

+         });

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "certificate", "del", this.state.certName

+         ];

+         log_cmd("delCert", "Deleting certificate", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.reloadCACerts();

+                     this.setState({

+                         certName: '',

+                         modalSpinner: false,

+                         showConfirmDelete: false,

+                     });

+                     this.props.addNotification(

+                         "success",

+                         `Successfully deleted certificate`

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.setState({

+                         certName: '',

+                         modalSpinner: false,

+                         loading: false,

+                     });

+                     this.props.addNotification(

+                         "error",

+                         `Error deleting certificate - ${msg}`

+                     );

+                 });

+     }

+ 

+     showEditModal (rowData) {

+         this.setState({

+             showEditModal: true,

+             certName: rowData.nickname[0],

+             flags: rowData.flags[0],

+             isCACert: false,

+         });

+     }

+ 

+     closeEditModal () {

+         this.setState({

+             showEditModal: false,

+             flags: ''

+         });

+     }

+ 

+     showEditCAModal (rowData) {

+         this.setState({

+             showEditModal: true,

+             certName: rowData.nickname[0],

+             flags: rowData.flags[0],

+             isCACert: true,

+         });

+     }

+ 

+     editCert () {

+         // Check if CA cert flags were removed

+         if (this.state.isCACert) {

+             let SSLFlags = '';

+             SSLFlags = this.state.flags.split(',', 1);

+             if (!SSLFlags[0].includes('C') || !SSLFlags[0].includes('T')) {

+                 // This could remove the CA cert properties, better warn user

+                 this.setState({

+                     showConfirmCAChange: true

+                 });

+                 return;

+             }

+         }

+         this.doEditCert();

+     }

+ 

+     closeConfirmCAChange () {

+         this.setState({

+             showConfirmCAChange: false

+         });

+     }

+ 

+     doEditCert () {

+         this.setState({

+             modalSpinner: true,

+             loading: true,

+         });

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "certificate", "set-trust-flags", this.state.certName, "--flags=" + this.state.flags

+         ];

+         log_cmd("doEditCert", "Editing trust flags", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.reloadCACerts();

+                     this.setState({

+                         showEditModal: false,

+                         flags: '',

+                         certName: '',

+                         modalSpinner: false,

+                     });

+                     this.props.addNotification(

+                         "success",

+                         `Successfully changed certificate's trust flags`

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.setState({

+                         showEditModal: false,

+                         flags: '',

+                         certName: '',

+                         modalSpinner: false,

+                         loading: false,

+                     });

+                     this.props.addNotification(

+                         "error",

+                         `Error setting trust flags - ${msg}`

+                     );

+                 });

+     }

+ 

+     handleAddChange (e) {

+         const value = e.target.value;

+         let valueErr = false;

+         let errObj = this.state.errObj;

+ 

+         if (value == "") {

+             valueErr = true;

+         }

+         errObj[e.target.id] = valueErr;

+         this.setState({

+             [e.target.id]: value,

+             errObj: errObj

+         });

+     }

+ 

+     handleFlagChange (e) {

+         const checked = e.target.checked;

+         const id = e.target.id;

+         let flags = this.state.flags;

+         let SSLFlags = '';

+         let EmailFlags = '';

+         let OSFlags = '';

+         [SSLFlags, EmailFlags, OSFlags] = flags.split(',');

+ 

+         if (id.endsWith('SSL')) {

+             for (let trustFlag of ['C', 'T', 'c', 'P', 'p']) {

+                 if (id.startsWith(trustFlag)) {

+                     if (checked) {

+                         SSLFlags += trustFlag;

+                     } else {

+                         SSLFlags = SSLFlags.replace(trustFlag, '');

+                     }

+                 }

+             }

+         } else if (id.endsWith('Email')) {

+             for (let trustFlag of ['C', 'T', 'c', 'P', 'p']) {

+                 if (id.startsWith(trustFlag)) {

+                     if (checked) {

+                         EmailFlags += trustFlag;

+                     } else {

+                         EmailFlags = EmailFlags.replace(trustFlag, '');

+                     }

+                 }

+             }

+         } else {

+             // Object Signing (OS)

+             for (let trustFlag of ['C', 'T', 'c', 'P', 'p']) {

+                 if (id.startsWith(trustFlag)) {

+                     if (checked) {

+                         OSFlags += trustFlag;

+                     } else {

+                         OSFlags = OSFlags.replace(trustFlag, '');

+                     }

+                 }

+             }

+         }

+         this.setState({

+             flags: SSLFlags + "," + EmailFlags + "," + OSFlags

+         });

+     }

+ 

+     closeConfirmDelete () {

+         this.setState({

+             showConfirmDelete: false,

+         });

+     }

+ 

+     reloadCerts () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "certificate", "list",

+         ];

+         log_cmd("reloadCerts", "Load certificates", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const certs = JSON.parse(content);

+                     let certNames = [];

+                     for (let cert of certs) {

+                         certNames.push(cert.attrs['nickname']);

+                     }

+                     this.setState({

+                         ServerCerts: certs,

+                         loading: false,

+                     });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.props.addNotification(

+                         "error",

+                         `Error loading server certificates - ${msg}`

+                     );

+                 });

+     }

+ 

+     reloadCACerts () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ca-certificate", "list",

+         ];

+         log_cmd("reloadCACerts", "Load certificates", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     let certs = JSON.parse(content);

+                     this.setState({

+                         CACerts: certs,

+                         loading: false

+                     }, this.reloadCerts);

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.props.addNotification(

+                         "error",

+                         `Error loading CA certificates - ${msg}`

+                     );

+                 });

+     }

+ 

+     render () {

+         let CATitle = 'Trusted Certificate Authorites <font size="1">(' + this.state.CACerts.length + ')</font>';

+         let ServerTitle = 'TLS Certificates <font size="1">(' + this.state.ServerCerts.length + ')</font>';

+ 

+         let certificatePage = '';

+ 

+         if (this.state.loading) {

+             certificatePage =

+                 <div className="ds-loading-spinner ds-center">

+                     <p />

+                     <h4>Loading certificates ...</h4>

+                     <Spinner loading size="md" />

+                 </div>;

+         } else {

+             certificatePage =

+                 <div className="container-fluid">

+                     <div className="ds-tab-table">

+                         <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

+                             <div>

+                                 <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                                     <NavItem eventKey={1}>

+                                         <div dangerouslySetInnerHTML={{__html: CATitle}} />

+                                     </NavItem>

+                                     <NavItem eventKey={2}>

+                                         <div dangerouslySetInnerHTML={{__html: ServerTitle}} />

+                                     </NavItem>

+                                 </Nav>

+                                 <TabContent>

+                                     <TabPane eventKey={1}>

+                                         <div className="ds-margin-top-lg">

+                                             <CertTable

+                                                 certs={this.state.CACerts}

+                                                 key={this.state.CACerts}

+                                                 editCert={this.showEditCAModal}

+                                                 delCert={this.showDeleteConfirm}

+                                             />

+                                             <Button

+                                                 bsStyle="primary"

+                                                 className="ds-margin-top-med"

+                                                 onClick={() => {

+                                                     this.showAddCAModal();

+                                                 }}

+                                             >

+                                                 Add CA Certificate

+                                             </Button>

+                                         </div>

+                                     </TabPane>

+                                     <TabPane eventKey={2}>

+                                         <div className="ds-margin-top-lg">

+                                             <CertTable

+                                                 certs={this.state.ServerCerts}

+                                                 key={this.state.ServerCerts}

+                                                 editCert={this.showEditModal}

+                                                 delCert={this.showDeleteConfirm}

+                                             />

+                                             <Button

+                                                 bsStyle="primary"

+                                                 className="ds-margin-top-med"

+                                                 onClick={() => {

+                                                     this.showAddModal();

+                                                 }}

+                                             >

+                                                 Add Server Certificate

+                                             </Button>

+                                         </div>

+                                     </TabPane>

+                                 </TabContent>

+                             </div>

+                         </TabContainer>

+                     </div>

+                 </div>;

+         }

+         return (

+             <div>

+                 {certificatePage}

+                 <EditCertModal

+                     showModal={this.state.showEditModal}

+                     closeHandler={this.closeEditModal}

+                     handleChange={this.handleFlagChange}

+                     saveHandler={this.editCert}

+                     flags={this.state.flags}

+                     spinning={this.state.modalSpinner}

+                 />

+                 <SecurityAddCertModal

+                     showModal={this.state.showAddModal}

+                     closeHandler={this.closeAddModal}

+                     handleChange={this.handleAddChange}

+                     saveHandler={this.addCert}

+                     spinning={this.state.modalSpinner}

+                     error={this.state.errObj}

+                 />

+                 <SecurityAddCACertModal

+                     showModal={this.state.showAddCAModal}

+                     closeHandler={this.closeAddCAModal}

+                     handleChange={this.handleAddChange}

+                     saveHandler={this.addCACert}

+                     spinning={this.state.modalSpinner}

+                     error={this.state.errObj}

+                 />

+                 <ConfirmPopup

+                     showModal={this.state.showConfirmDelete}

+                     closeHandler={this.closeConfirmDelete}

+                     actionFunc={this.delCert}

+                     msg="Are you sure you want to delete this certificate?"

+                     msgContent={this.state.certName}

+                 />

+                 <ConfirmPopup

+                     showModal={this.state.showConfirmCAChange}

+                     closeHandler={this.closeConfirmCAChange}

+                     actionFunc={this.doEditCert}

+                     msg="Removing the 'C' or 'T' flags from the SSL trust catagory could break all TLS connectivity to and from the server, are you sure you want to proceed?"

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ // Props and defaults

+ 

+ CertificateManagement.propTypes = {

+     serverId: PropTypes.string,

+     CACerts: PropTypes.array,

+     ServerCerts: PropTypes.array,

+     addNotification: PropTypes.func,

+ };

+ 

+ CertificateManagement.defaultProps = {

+     serverId: "",

+     CACerts: [],

+     ServerCerts: [],

+     addNotification: noop,

+ };

+ 

+ export default CertificateManagement;

@@ -0,0 +1,274 @@ 

+ import React from "react";

+ import cockpit from "cockpit";

+ import {

+     Button,

+     Row,

+     Col,

+     ControlLabel,

+     Spinner,

+     noop,

+ } from "patternfly-react";

+ import { log_cmd } from "../../lib/tools.jsx";

+ import PropTypes from "prop-types";

+ import "../../css/ds.css";

+ import { Typeahead } from "react-bootstrap-typeahead";

+ 

+ export class Ciphers extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             allowCiphers: [],

+             denyCiphers: [],

+             cipherPref: "default",

+             prefs: this.props.cipherPref,

+             saving: false,

+         };

+ 

+         this.handlePrefChange = this.handlePrefChange.bind(this);

+         this.saveCipherPref = this.saveCipherPref.bind(this);

+     }

+ 

+     componentWillMount () {

+         let cipherPref = "default";

+         let allowedCiphers = [];

+         let deniedCiphers = [];

+ 

+         // Parse SSL cipher attributes (nsSSL3Ciphers)

+         if (this.props.cipherPref != "") {

+             let rawCiphers = this.props.cipherPref.split(",");

+ 

+             // First check the first element as it has special meaning

+             if (rawCiphers[0].toLowerCase() == "default") {

+                 rawCiphers.shift();

+             } else if (rawCiphers[0].toLowerCase() == "+all") {

+                 cipherPref = "+all";

+                 rawCiphers.shift();

+             } else if (rawCiphers[0].toLowerCase() == "-all") {

+                 cipherPref = "-all";

+                 rawCiphers.shift();

+             }

+ 

+             // Process the remaining ciphers

+             rawCiphers = rawCiphers.map(function(x) { return x.toUpperCase() });

+             for (let cipher of rawCiphers) {

+                 if (cipher.startsWith("+")) {

+                     allowedCiphers.push(cipher.substring(1));

+                 } else if (cipher.startsWith("-")) {

+                     deniedCiphers.push(cipher.substring(1));

+                 }

+             }

+         }

+ 

+         this.setState({

+             cipherPref: cipherPref,

+             allowCiphers: allowedCiphers,

+             denyCiphers: deniedCiphers,

+         });

+     }

+ 

+     saveCipherPref () {

+         /* start the spinner */

+         this.setState({

+             saving: true

+         });

+         let prefs = this.state.cipherPref;

+         for (let cipher of this.state.allowCiphers) {

+             prefs += ",+" + cipher;

+         }

+         for (let cipher of this.state.denyCiphers) {

+             prefs += ",-" + cipher;

+         }

+ 

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ciphers", "set", "--", prefs

+         ];

+         log_cmd("saveCipherPref", "Saving cipher preferences", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.props.addNotification(

+                         "success",

+                         `Successfully set cipher preferences.  You must restart the server for these changes to take effect.`

+                     );

+                     this.setState({

+                         saving: false,

+                     });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.props.addNotification(

+                         "error",

+                         `Error setting cipher preferences - ${msg}`

+                     );

+                     this.setState({

+                         saving: false,

+                     });

+                 });

+     }

+ 

+     handlePrefChange (e) {

+         this.setState({

+             cipherPref: e.target.value,

+         });

+     }

+ 

+     render () {

+         let supportedCiphers = [];

+         let enabledCiphers = [];

+         let cipherPage;

+ 

+         for (let cipher of this.props.supportedCiphers) {

+             if (!this.props.enabledCiphers.includes(cipher)) {

+                 // This cipher is not currently enabled, so list it as available

+                 supportedCiphers.push(cipher);

+             }

+         }

+         for (let cipher of this.props.enabledCiphers) {

+             enabledCiphers.push(cipher);

+         }

+         let supportedList = supportedCiphers.map((name) =>

+             <option key={name}>{name}</option>

+         );

+         let enabledList = enabledCiphers.map((name) =>

+             <option key={name}>{name}</option>

+         );

+ 

+         if (this.state.saving) {

+             cipherPage =

+                 <div className="ds-loading-spinner ds-center">

+                     <p />

+                     <h4>Saving cipher preferences ...</h4>

+                     <Spinner loading size="md" />

+                 </div>;

+         } else {

+             cipherPage =

+                 <div className="container-fluid">

+                     <div className="ds-container">

+                         <div className='ds-inline'>

+                             <div>

+                                 <h4>Enabled Ciphers</h4>

+                             </div>

+                             <div>

+                                 <select

+                                     className="ds-cipher-width"

+                                     size="16"

+                                     title="The current ciphers the server is accepting.  This is only updated after a server restart"

+                                 >

+                                     {enabledList}

+                                 </select>

+                             </div>

+                         </div>

+                         <div className="ds-divider-lrg" />

+                         <div className='ds-inline'>

+                             <div>

+                                 <h4>Other Available Ciphers</h4>

+                             </div>

+                             <div>

+                                 <select className="ds-cipher-width" size="16">

+                                     {supportedList}

+                                 </select>

+                             </div>

+                         </div>

+                     </div>

+                     <hr />

+                     <Row>

+                         <Col componentClass={ControlLabel} sm={2}>

+                              Cipher Suite

+                         </Col>

+                         <Col sm={9}>

+                             <select

+                                 id="cipherPref"

+                                 onChange={this.handlePrefChange}

+                                 defaultValue={this.state.cipherPref}

+                             >

+                                 <option title="default" value="default" key="default">Default Ciphers</option>

+                                 <option title="+all" value="+all" key="all">All Ciphers</option>

+                                 <option title="-all" value="-all" key="none">No Ciphers</option>

+                             </select>

+                         </Col>

+                     </Row>

+                     <Row className="ds-margin-top">

+                         <Col componentClass={ControlLabel} sm={2}>

+                              Allow Specific Ciphers

+                         </Col>

+                         <Col sm={9}>

+                             <Typeahead

+                                 multiple

+                                 onChange={value => {

+                                     this.setState({

+                                         allowCiphers: value

+                                     });

+                                 }}

+                                 selected={this.state.allowCiphers}

+                                 options={this.props.supportedCiphers}

+                                 newSelectionPrefix="Add a cipher: "

+                                 placeholder="Type a cipher..."

+                                 id="allowCipher"

+                             />

+                         </Col>

+                     </Row>

+                     <Row className="ds-margin-top">

+                         <Col componentClass={ControlLabel} sm={2}>

+                              Deny Specific Ciphers

+                         </Col>

+                         <Col sm={9}>

+                             <Typeahead

+                                 multiple

+                                 onChange={value => {

+                                     this.setState({

+                                         denyCiphers: value

+                                     });

+                                 }}

+                                 selected={this.state.denyCiphers}

+                                 options={this.props.supportedCiphers}

+                                 newSelectionPrefix="Add a cipher: "

+                                 placeholder="Type a cipher..."

+                                 id="denyCipher"

+                             />

+                         </Col>

+                     </Row>

+                     <p />

+                     <Button

+                         bsStyle="primary"

+                         className="ds-margin-top"

+                         onClick={() => {

+                             this.saveCipherPref();

+                         }}

+                     >

+                         Save Cipher Preferences

+                     </Button>

+                 </div>;

+         }

+ 

+         return (

+             <div>

+                 {cipherPage}

+             </div>

+         );

+     }

+ }

+ 

+ // Props and defaults

+ 

+ Ciphers.propTypes = {

+     serverId: PropTypes.string,

+     supportedCiphers: PropTypes.array,

+     enabledCiphers: PropTypes.array,

+     cipherPref: PropTypes.string,

+     addNotification: PropTypes.func,

+ };

+ 

+ Ciphers.defaultProps = {

+     serverId: "",

+     supportedCiphers: [],

+     enabledCiphers: [],

+     cipherPref: "",

+     addNotification: noop,

+ };

+ 

+ export default Ciphers;

@@ -0,0 +1,689 @@ 

+ import React from "react";

+ import {

+     Modal,

+     Row,

+     Col,

+     ControlLabel,

+     Checkbox,

+     FormControl,

+     Icon,

+     Button,

+     Form,

+     Spinner,

+     noop

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

+ import "../../css/ds.css";

+ 

+ export class SecurityAddCACertModal extends React.Component {

+     render() {

+         const {

+             showModal,

+             closeHandler,

+             handleChange,

+             saveHandler,

+             spinning,

+             error

+         } = this.props;

+ 

+         let spinner = "";

+         if (spinning) {

+             spinner =

+                 <Row>

+                     <div className="ds-modal-spinner">

+                         <Spinner loading inline size="lg" />Adding CA certificate...

+                     </div>

+                 </Row>;

+         }

+ 

+         return (

+             <Modal show={showModal} onHide={closeHandler}>

+                 <div className="ds-no-horizontal-scrollbar">

+                     <Modal.Header>

+                         <button

+                             className="close"

+                             onClick={closeHandler}

+                             aria-hidden="true"

+                             aria-label="Close"

+                         >

+                             <Icon type="pf" name="close" />

+                         </button>

+                         <Modal.Title>

+                             Add Certificate Authority

+                         </Modal.Title>

+                     </Modal.Header>

+                     <Modal.Body>

+                         <Form horizontal autoComplete="off">

+                             <h4>

+                                 Add CA certificate to the security database.

+                             </h4>

+                             <hr />

+                             <Row title="Enter full path to and and including certificate file name">

+                                 <Col sm={4}>

+                                     <ControlLabel>Certificate File</ControlLabel>

+                                 </Col>

+                                 <Col sm={8}>

+                                     <FormControl

+                                         type="text"

+                                         id="certFile"

+                                         className={error.certFile ? "ds-input-bad" : ""}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row title="Enter name/nickname of the certificate">

+                                 <Col sm={4}>

+                                     <ControlLabel>Certificate Nickname</ControlLabel>

+                                 </Col>

+                                 <Col sm={8}>

+                                     <FormControl

+                                         type="text"

+                                         id="certName"

+                                         className={error.certName ? "ds-input-bad" : ""}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             {spinner}

+                         </Form>

+                     </Modal.Body>

+                     <Modal.Footer>

+                         <Button

+                             bsStyle="default"

+                             className="btn-cancel"

+                             onClick={closeHandler}

+                         >

+                             Cancel

+                         </Button>

+                         <Button

+                             bsStyle="primary"

+                             onClick={saveHandler}

+                         >

+                             Add Certificate

+                         </Button>

+                     </Modal.Footer>

+                 </div>

+             </Modal>

+         );

+     }

+ }

+ 

+ export class SecurityAddCertModal extends React.Component {

+     render() {

+         const {

+             showModal,

+             closeHandler,

+             handleChange,

+             saveHandler,

+             spinning,

+             error

+         } = this.props;

+ 

+         let spinner = "";

+         if (spinning) {

+             spinner =

+                 <Row>

+                     <div className="ds-modal-spinner">

+                         <Spinner loading inline size="lg" />Adding certificate...

+                     </div>

+                 </Row>;

+         }

+ 

+         return (

+             <Modal show={showModal} onHide={closeHandler}>

+                 <div className="ds-no-horizontal-scrollbar">

+                     <Modal.Header>

+                         <button

+                             className="close"

+                             onClick={closeHandler}

+                             aria-hidden="true"

+                             aria-label="Close"

+                         >

+                             <Icon type="pf" name="close" />

+                         </button>

+                         <Modal.Title>

+                             Add Certificate

+                         </Modal.Title>

+                     </Modal.Header>

+                     <Modal.Body>

+                         <Form horizontal autoComplete="off">

+                             <h4>

+                                 Add certificate to the security database.

+                             </h4>

+                             <hr />

+                             <Row title="Enter full path to and and including certificate file name">

+                                 <Col sm={4}>

+                                     <ControlLabel>Certificate File</ControlLabel>

+                                 </Col>

+                                 <Col sm={8}>

+                                     <FormControl

+                                         type="text"

+                                         id="certFile"

+                                         className={error.certFile ? "ds-input-bad" : ""}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row title="Enter name/nickname of the certificate">

+                                 <Col sm={4}>

+                                     <ControlLabel>Certificate Nickname</ControlLabel>

+                                 </Col>

+                                 <Col sm={8}>

+                                     <FormControl

+                                         type="text"

+                                         id="certName"

+                                         className={error.certName ? "ds-input-bad" : ""}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             {spinner}

+                         </Form>

+                     </Modal.Body>

+                     <Modal.Footer>

+                         <Button

+                             bsStyle="default"

+                             className="btn-cancel"

+                             onClick={closeHandler}

+                         >

+                             Cancel

+                         </Button>

+                         <Button

+                             bsStyle="primary"

+                             onClick={saveHandler}

+                         >

+                             Add Certificate

+                         </Button>

+                     </Modal.Footer>

+                 </div>

+             </Modal>

+         );

+     }

+ }

+ 

+ export class SecurityEnableModal extends React.Component {

+     render() {

+         const {

+             showModal,

+             closeHandler,

+             handleChange,

+             saveHandler,

+             primaryName,

+             certs,

+             spinning

+         } = this.props;

+ 

+         // Build list of cert names for the select list

+         let certNames = [];

+         for (let cert of certs) {

+             certNames.push(cert.attrs['nickname']);

+         }

+         let certNameOptions = certNames.map((name) =>

+             <option key={name} value={name}>{name}</option>

+         );

+         let spinner = "";

+         if (spinning) {

+             spinner =

+                 <Row>

+                     <div className="ds-modal-spinner">

+                         <Spinner loading inline size="lg" />Enabling security...

+                     </div>

+                 </Row>;

+         }

+ 

+         return (

+             <Modal show={showModal} onHide={closeHandler}>

+                 <div className="ds-no-horizontal-scrollbar">

+                     <Modal.Header>

+                         <button

+                             className="close"

+                             onClick={closeHandler}

+                             aria-hidden="true"

+                             aria-label="Close"

+                         >

+                             <Icon type="pf" name="close" />

+                         </button>

+                         <Modal.Title>

+                             Enable Security

+                         </Modal.Title>

+                     </Modal.Header>

+                     <Modal.Body>

+                         <Form horizontal autoComplete="off">

+                             <h4>

+                                 You are choosing to enable security for the Directory Server which

+                                 allows the server to accept incoming client TLS connections.  Please

+                                 select which certificate the server should use.

+                             </h4>

+                             <hr />

+                             <Row className="ds-margin-top" title="The server certificate the Directory Server will use">

+                                 <Col sm={4}>

+                                     <ControlLabel>Available Certificates</ControlLabel>

+                                 </Col>

+                                 <Col sm={8}>

+                                     <select id="certNameSelect" onChange={handleChange} defaultValue={primaryName}>

+                                         {certNameOptions}

+                                     </select>

+                                 </Col>

+                             </Row>

+                             <p />

+                             {spinner}

+                         </Form>

+                     </Modal.Body>

+                     <Modal.Footer>

+                         <Button

+                             bsStyle="default"

+                             className="btn-cancel"

+                             onClick={closeHandler}

+                         >

+                             Cancel

+                         </Button>

+                         <Button

+                             bsStyle="primary"

+                             onClick={saveHandler}

+                         >

+                             Enable Security

+                         </Button>

+                     </Modal.Footer>

+                 </div>

+             </Modal>

+         );

+     }

+ }

+ 

+ export class EditCertModal extends React.Component {

+     render() {

+         const {

+             showModal,

+             closeHandler,

+             handleChange,

+             saveHandler,

+             flags,

+             spinning

+         } = this.props;

+ 

+         let spinner = "";

+         if (spinning) {

+             spinner =

+                 <Row>

+                     <div className="ds-modal-spinner">

+                         <Spinner loading inline size="lg" />Saving certificate...

+                     </div>

+                 </Row>;

+         }

+ 

+         // Process the cert flags

+         let CSSLChecked = false;

+         let CEmailChecked = false;

+         let COSChecked = false;

+         let TSSLChecked = false;

+         let TEmailChecked = false;

+         let TOSChecked = false;

+         let cSSLChecked = false;

+         let cEmailChecked = false;

+         let cOSChecked = false;

+         let PSSLChecked = false;

+         let PEmailChecked = false;

+         let POSChecked = false;

+         let pSSLChecked = false;

+         let pEmailChecked = false;

+         let pOSChecked = false;

+         let uSSLChecked = false;

+         let uEmailChecked = false;

+         let uOSChecked = false;

+         let SSLFlags = '';

+         let EmailFlags = '';

+         let OSFlags = '';

+         if (flags != "") {

+             [SSLFlags, EmailFlags, OSFlags] = flags.split(',');

+             if (SSLFlags.includes('T')) {

+                 TSSLChecked = true;

+             }

+             if (EmailFlags.includes('T')) {

+                 TEmailChecked = true;

+             }

+             if (OSFlags.includes('T')) {

+                 TOSChecked = true;

+             }

+             if (SSLFlags.includes('C')) {

+                 CSSLChecked = true;

+             }

+             if (EmailFlags.includes('C')) {

+                 CEmailChecked = true;

+             }

+             if (OSFlags.includes('C')) {

+                 COSChecked = true;

+             }

+             if (SSLFlags.includes('c')) {

+                 cSSLChecked = true;

+             }

+             if (EmailFlags.includes('c')) {

+                 cEmailChecked = true;

+             }

+             if (OSFlags.includes('c')) {

+                 cOSChecked = true;

+             }

+             if (SSLFlags.includes('P')) {

+                 PSSLChecked = true;

+             }

+             if (EmailFlags.includes('P')) {

+                 PEmailChecked = true;

+             }

+             if (OSFlags.includes('P')) {

+                 POSChecked = true;

+             }

+             if (SSLFlags.includes('p')) {

+                 pSSLChecked = true;

+             }

+             if (EmailFlags.includes('p')) {

+                 pEmailChecked = true;

+             }

+             if (OSFlags.includes('p')) {

+                 pOSChecked = true;

+             }

+             if (SSLFlags.includes('u')) {

+                 uSSLChecked = true;

+             }

+             if (EmailFlags.includes('u')) {

+                 uEmailChecked = true;

+             }

+             if (OSFlags.includes('u')) {

+                 uOSChecked = true;

+             }

+         }

+ 

+         return (

+             <Modal show={showModal} onHide={closeHandler}>

+                 <div className="ds-no-horizontal-scrollbar">

+                     <Modal.Header>

+                         <button

+                             className="close"

+                             onClick={closeHandler}

+                             aria-hidden="true"

+                             aria-label="Close"

+                         >

+                             <Icon type="pf" name="close" />

+                         </button>

+                         <Modal.Title>

+                             Edit Certificate Trust Flags

+                         </Modal.Title>

+                     </Modal.Header>

+                     <Modal.Body>

+                         <Form horizontal autoComplete="off">

+                             <Row className="ds-margin-top">

+                                 <Col sm={4}>

+                                     <ControlLabel>Flags</ControlLabel>

+                                 </Col>

+                                 <Col sm={2}>

+                                     <ControlLabel>SSL</ControlLabel>

+                                 </Col>

+                                 <Col sm={2}>

+                                     <ControlLabel>Email</ControlLabel>

+                                 </Col>

+                                 <Col sm={3}>

+                                     <ControlLabel>Object Signing</ControlLabel>

+                                 </Col>

+                             </Row>

+                             <hr />

+                             <Row>

+                                 <Col sm={4} title="Trusted CA (flag 'C', also implies 'c' flag)">

+                                     (C) - Trusted CA

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="CflagSSL"

+                                         checked={CSSLChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="CflagEmail"

+                                         checked={CEmailChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="CflagOS"

+                                         checked={COSChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row>

+                                 <Col sm={4} title="Trusted CA for client authentication (flag 'T')">

+                                     (T) - Trusted CA Client Auth

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="TflagSSL"

+                                         checked={TSSLChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="TflagEmail"

+                                         checked={TEmailChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="TflagOS"

+                                         checked={TOSChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row>

+                                 <Col sm={4} title="Valid CA (flag 'c')">

+                                     (c) - Valid CA

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="cflagSSL"

+                                         checked={cSSLChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="cflagEmail"

+                                         checked={cEmailChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="cflagOS"

+                                         checked={cOSChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row>

+                                 <Col sm={4} title="Trusted Peer (flag 'P', implies flag 'p')">

+                                     (P) - Trusted Peer

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="PflagSSL"

+                                         checked={PSSLChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="PflagEmail"

+                                         checked={PEmailChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="PflagOS"

+                                         checked={POSChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             <Row>

+                                 <Col sm={4} title="Valid Peer (flag 'p')">

+                                     (p) - Valid Peer

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="pflagSSL"

+                                         checked={pSSLChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="pflagEmail"

+                                         checked={pEmailChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="pflagOS"

+                                         checked={pOSChecked}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </Row>

+                             <hr />

+                             <Row>

+                                 <Col sm={4} title="A private key is associated with the certificate. This is a dynamic flag and you cannot adjust it.">

+                                     (u) - Private Key

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="uflagSSL"

+                                         checked={uSSLChecked}

+                                         disabled

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="uflagEmail"

+                                         checked={uEmailChecked}

+                                         disabled

+                                     />

+                                 </Col>

+                                 <Col sm={2}>

+                                     <Checkbox

+                                         id="uflagOS"

+                                         checked={uOSChecked}

+                                         disabled

+                                     />

+                                 </Col>

+                             </Row>

+                             <p />

+                             {spinner}

+                         </Form>

+                     </Modal.Body>

+                     <Modal.Footer>

+                         <Button

+                             bsStyle="default"

+                             className="btn-cancel"

+                             onClick={closeHandler}

+                         >

+                             Cancel

+                         </Button>

+                         <Button

+                             bsStyle="primary"

+                             onClick={saveHandler}

+                         >

+                             Save

+                         </Button>

+                     </Modal.Footer>

+                 </div>

+             </Modal>

+         );

+     }

+ }

+ 

+ SecurityEnableModal.propTypes = {

+     showModal: PropTypes.bool,

+     closeHandler: PropTypes.func,

+     handleChange: PropTypes.func,

+     saveHandler: PropTypes.func,

+     primaryName: PropTypes.string,

+     certs: PropTypes.array,

+     spinning: PropTypes.bool,

+ };

+ 

+ SecurityEnableModal.defaultProps = {

+     showModal: false,

+     closeHandler: noop,

+     handleChange: noop,

+     saveHandler: noop,

+     primaryName: "",

+     certs: [],

+     spinning: false,

+ };

+ 

+ EditCertModal.propTypes = {

+     showModal: PropTypes.bool,

+     closeHandler: PropTypes.func,

+     handleChange: PropTypes.func,

+     saveHandler: PropTypes.func,

+     flags: PropTypes.string,

+     spinning: PropTypes.bool,

+ };

+ 

+ EditCertModal.defaultProps = {

+     showModal: false,

+     closeHandler: noop,

+     handleChange: noop,

+     saveHandler: noop,

+     flags: "",

+     spinning: false,

+ };

+ 

+ SecurityAddCertModal.propTypes = {

+     showModal: PropTypes.bool,

+     closeHandler: PropTypes.func,

+     handleChange: PropTypes.func,

+     saveHandler: PropTypes.func,

+     spinning: PropTypes.bool,

+     error: PropTypes.object,

+ };

+ 

+ SecurityAddCertModal.defaultProps = {

+     showModal: false,

+     closeHandler: noop,

+     handleChange: noop,

+     saveHandler: noop,

+     spinning: false,

+     error: {},

+ };

+ 

+ SecurityAddCACertModal.propTypes = {

+     showModal: PropTypes.bool,

+     closeHandler: PropTypes.func,

+     handleChange: PropTypes.func,

+     saveHandler: PropTypes.func,

+     spinning: PropTypes.bool,

+     error: PropTypes.object,

+ };

+ 

+ SecurityAddCACertModal.defaultProps = {

+     showModal: false,

+     closeHandler: noop,

+     handleChange: noop,

+     saveHandler: noop,

+     spinning: false,

+     error: {},

+ };

@@ -0,0 +1,454 @@ 

+ import React from "react";

+ import {

+     // Button,

+     DropdownButton,

+     MenuItem,

+     actionHeaderCellFormatter,

+     sortableHeaderCellFormatter,

+     tableCellFormatter,

+     noop

+ } from "patternfly-react";

+ import { DSTable } from "../dsTable.jsx";

+ import PropTypes from "prop-types";

+ import "../../css/ds.css";

+ 

+ class CertTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             rowKey: "nickname",

+             columns: [

+                 {

+                     property: "nickname",

+                     header: {

+                         label: "Certificate Name",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "subject",

+                     header: {

+                         label: "Subject DN",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "issuer",

+                     header: {

+                         label: "Issued By",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "flags",

+                     header: {

+                         label: "Trust Flags",

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "expires",

+                     header: {

+                         label: "Expiration Date",

+                         props: {

+                             index: 4,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 4

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "action",

+                     header: {

+                         label: "",

+                         props: {

+                             index: 5,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 5

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.nickname[0]}>

+                                         <DropdownButton id={rowData.nickname[0]}

+                                             bsStyle="default" title="Actions">

+                                             <MenuItem eventKey="1" onClick={() => {

+                                                 this.props.editCert(rowData);

+                                             }}

+                                             >

+                                                 Edit Trust Flags

+                                             </MenuItem>

+                                             <MenuItem divider />

+                                             <MenuItem eventKey="2" onClick={() => {

+                                                 this.props.delCert(rowData);

+                                             }}

+                                             >

+                                                 Delete Certificate

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ]

+         };

+         this.getColumns = this.getColumns.bind(this);

+         this.getSingleColumn = this.getSingleColumn.bind(this);

+     }

+ 

+     getSingleColumn () {

+         return [

+             {

+                 property: "msg",

+                 header: {

+                     label: "Certificates",

+                     props: {

+                         index: 0,

+                         rowSpan: 1,

+                         colSpan: 1,

+                         sort: true

+                     },

+                     transforms: [],

+                     formatters: [],

+                     customFormatters: [sortableHeaderCellFormatter]

+                 },

+                 cell: {

+                     props: {

+                         index: 0

+                     },

+                     formatters: [tableCellFormatter]

+                 }

+             },

+         ];

+     }

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     render() {

+         let certRows = [];

+         let serverTable;

+         for (let cert of this.props.certs) {

+             let obj = {

+                 'nickname': [cert.attrs['nickname']],

+                 'subject': [cert.attrs['subject']],

+                 'issuer': [cert.attrs['issuer']],

+                 'expires': [cert.attrs['expires']],

+                 'flags': [cert.attrs['flags']],

+             };

+             certRows.push(obj);

+         }

+ 

+         if (certRows.length == 0) {

+             serverTable = <DSTable

+                 getColumns={this.getSingleColumn}

+                 rowKey={"msg"}

+                 rows={[{msg: "No Certificates"}]}

+                 key={"nocerts"}

+             />;

+         } else {

+             serverTable = <DSTable

+                 getColumns={this.getColumns}

+                 rowKey={this.state.rowKey}

+                 rows={certRows}

+                 key={certRows}

+                 disableLoadingSpinner

+             />;

+         }

+ 

+         return (

+             <div>

+                 {serverTable}

+             </div>

+         );

+     }

+ }

+ 

+ // Future - https://pagure.io/389-ds-base/issue/50491

+ class CRLTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             rowKey: "name",

+             columns: [

+                 {

+                     property: "name",

+                     header: {

+                         label: "Issued By",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "effective",

+                     header: {

+                         label: "Effective Date",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nextUpdate",

+                     header: {

+                         label: "Next Updateo",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+ 

+                 {

+                     property: "type",

+                     header: {

+                         label: "Type",

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "action",

+                     header: {

+                         label: "",

+                         props: {

+                             index: 4,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 4

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.name[0]}>

+                                         <DropdownButton id={rowData.name[0]}

+                                             bsStyle="default" title="Actions">

+                                             <MenuItem eventKey="1" onClick={() => {

+                                                 this.props.editIndex(rowData);

+                                             }}

+                                             >

+                                                 View CRL

+                                             </MenuItem>

+                                             <MenuItem eventKey="2" onClick={() => {

+                                                 this.props.reindexIndex(rowData);

+                                             }}

+                                             >

+                                                 Delete CRL

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ]

+         };

+         this.getColumns = this.getColumns.bind(this);

+     }

+ 

+     getSingleColumn () {

+         return [

+             {

+                 property: "msg",

+                 header: {

+                     label: "Certificate Revocation Lists",

+                     props: {

+                         index: 0,

+                         rowSpan: 1,

+                         colSpan: 1,

+                         sort: true

+                     },

+                     transforms: [],

+                     formatters: [],

+                     customFormatters: [sortableHeaderCellFormatter]

+                 },

+                 cell: {

+                     props: {

+                         index: 0

+                     },

+                     formatters: [tableCellFormatter]

+                 }

+             },

+         ];

+     }

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     render() {

+         let crlTable;

+         if (this.props.rows.length == 0) {

+             crlTable = <DSTable

+                 getColumns={this.getSingleColumn}

+                 rowKey={"msg"}

+                 rows={[{msg: "None"}]}

+             />;

+         } else {

+             crlTable = <DSTable

+                 getColumns={this.getColumns}

+                 rowKey={this.state.rowKey}

+                 rows={this.props.rows}

+                 disableLoadingSpinner

+             />;

+         }

+         return (

+             <div>

+                 {crlTable}

+             </div>

+         );

+     }

+ }

+ 

+ // Props and defaults

+ 

+ CertTable.propTypes = {

+     // serverId: PropTypes.string,

+     certs: PropTypes.array,

+     editCert: PropTypes.func,

+     delCert: PropTypes.func,

+ };

+ 

+ CertTable.defaultProps = {

+     // serverId: "",

+     certs: [],

+     editCert: noop,

+     delCert: noop,

+ };

+ 

+ export {

+     CertTable,

+     CRLTable

+ };

@@ -1,8 +1,8 @@ 

  export function searchFilter(searchFilterValue, columnsToSearch, rows) {

      if (searchFilterValue && rows && rows.length) {

-         const filteredRows = [];

+         let filteredRows = [];

          rows.forEach(row => {

-             var rowToSearch = [];

+             let rowToSearch = [];

              if (columnsToSearch && columnsToSearch.length) {

                  columnsToSearch.forEach(column =>

                      rowToSearch.push(row[column])
@@ -27,18 +27,18 @@ 

  

  export function log_cmd(js_func, desc, cmd_array) {

      if (console) {

-         var pw_args = ["--passwd", "--bind-pw"];

-         var cmd_list = [];

-         var converted_pw = false;

+         let pw_args = ["--passwd", "--bind-pw"];

+         let cmd_list = [];

+         let converted_pw = false;

  

-         for (var idx in cmd_array) {

-             var cmd = cmd_array[idx];

+         for (let idx in cmd_array) {

+             let cmd = cmd_array[idx];

              converted_pw = false;

              for (var arg_idx in pw_args) {

                  if (cmd.startsWith(pw_args[arg_idx])) {

                      // We are setting a password, if it has a value we need to hide it

-                     var arg_len = cmd.indexOf("=");

-                     var arg = cmd.substring(0, arg_len);

+                     let arg_len = cmd.indexOf("=");

+                     let arg = cmd.substring(0, arg_len);

                      if (cmd.length != arg_len + 1) {

                          // We are setting a password value...

                          cmd_list.push(arg + "=**********");

@@ -1,502 +0,0 @@ 

- 

-   <div id="sec-config" class="security-ctrl all-pages" hidden>

-     <h3 class="ds-config-header">Security Configuration</h3>

- 

-     <input type="checkbox" class="ds-config-checkbox" id="nsslapd-security"><label

-       for="nsslapd-security" class="ds-label" title="Enable security in the server (nsslapd-security)."> Enable Security</label>

-     <hr class="">

-     <div class="ds-expired-div" id="cert-attrs">

- 

-       <div class="ds-container">

-         <div class="ds-inline">

-           <div>

-             <label for="nsslapd-secureport" class="ds-config-label" title="The server's secure port number (nsslapd-secureport).">Server Secure Port</label><input

-               class="ds-input" type="text" id="nsslapd-secureport" size="20"/>

-           </div>

-           <div>

-             <label for="nsslapd-securelistenhost" 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).  This parameter specifically sets what interface to use for TLS traffic.  Requires restart. (nsslapd-securelistenhost).">

-               Secure Listen Host Address</label><input class="ds-input" type="text" id="nsslapd-securelistenhost" size="20"/>

-           </div>

-           <div>

-             <label for="sec-sslmin" class="ds-config-label" title="The minimum SSL/TLS version the server will accept (sslversionmin).">Minimum SSL/TLS Version </label><select

-               class="btn btn-default dropdown ds-sec-dropdown" id="sec-sslmin">

-                 <option>TLS1.3</option>

-                 <option>TLS1.2</option>

-                 <option>TLS1.1</option>

-                 <option>TLS1.0</option>

-                 <option>SSL3</option>

-               </select>

-           </div>

-           <div>

-             <label for="sec-sslmax" class="ds-config-label" title="The maximum SSL/TLS version the server will accept (sslversionmax)."> Maximum SSL/TLS Version</label><select

-               class="btn btn-default dropdown ds-sec-dropdown" id="sec-sslmax">

-                 <option>TLS1.3</option>

-                 <option>TLS1.2</option>

-                 <option>TLS1.1</option>

-                 <option>TLS1.0</option>

-                 <option>SSL3</option>

-               </select>

-           </div>

-           <div>

-             <label for="sec-clientauth" class="ds-config-label" title="shows how the Directory Server enforces client authentication (nsSSLClientAuth)."> Client Authentication</label><select

-               class="btn btn-default dropdown ds-sec-dropdown" id="sec-clientauth">

-                 <option>Off</option>

-                 <option>Allowed</option>

-                 <option>Required</option>

-               </select>

-           </div>

-           <div>

-             <label for="sec-validate" class="ds-config-label" title="Validate server's certificate expiration date (nsslapd-validate-cert)."> Validate Certificate Expiration</label><select

-               class="btn btn-default dropdown ds-sec-dropdown" id="sec-validate">

-                 <option>Warn</option>

-                 <option>On</option>

-                 <option>Off</option>

-               </select>

-           </div>

-         </div>

-         <div class="ds-divider"></div>

-         <div class="ds-divider"></div>

-         <div class="ds-divider"></div>

-         <div class="ds-line">

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-require-secure-binds"><label

-               for="nsslapd-require-secure-binds" class="ds-label" title="Require all connections use TLS (nsslapd-require-secure-binds)."> Require Secure Connections</label>

-           </div>

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ssl-check-hostname"><label

-               for="nsslapd-ssl-check-hostname" class="ds-label" title="Verify authenticity of a request by matching the host name against the value assigned to the common name (cn) attribute of the subject name (subjectDN field) in the certificate being presented. (nsslapd-ssl-check-hostname)."> Verify Certificate Subject Hostname</label>

-           </div>

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="allowWeakCipher"><label

-               for="allowWeakCipher" class="ds-label" title="Allow weak ciphers (allowWeakCipher)."> Allow Weak Ciphers</label>

-           </div>

-           <div class="ds-first">

-             <button class="btn btn-default" id="set-sec-passwd-btn" title="Change the Security Database password">Set Security Password</button>

-           </div>

-         </div>

-       </div> 

-       <hr>

- 

-       <h4>Allowed Ciphers</h4>

-       <div class="ds-indent">

-         <input type="checkbox" class="ds-config-checkbox" id="cipher-default-state"><label

-           for="cipher-default-state" class="ds-label" title="Use the preferred default ciphers, as opposed to allowing all the ciphers">Use Default Ciphers</label>

-       </div>

-       <div id="cipher-table">

-         <table class="table table-striped table-bordered table-hover ds-loglevel-table" id="allowed-cipher-table">

-           <thead>

-             <tr>

-               <th class="ds-table-btn">State</th>

-               <th>Cipher</th>

-             </tr>

-           </thead>

-           <tbody>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown" id="cipher-all-state">

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td id="cipher-all">All</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown">

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown">

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_RSA_WITH_AES_256_GCM_SHA384</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_RSA_WITH_AES_128_GCM_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_RSA_WITH_SEED_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_RSA_WITH_CAMELLIA_256_CBC_SHA</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</td>

-             </tr>

-             <tr class="cipher-row">

-               <td class="ds-table-btn"> <select class="btn btn-default dropdown ds-cipher-dropdown" >

-                 <option></option>

-                 <option>Enabled</option>

-                 <option>Disabled</option>

-               </select>

-               </td>

-               <td>TLS_DHE_DSS_WITH_RC4_128_SHA</td>

-             </tr>

-           </tbody>

-         </table>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

- 

-   <div class="security-ctrl all-pages" id="sec-ciphers-page" hidden>

-     <h3 class="ds-config-header">Supported Ciphers</h3>

-     <table id="nssslsupportedciphers" class="display ds-table" cellspacing="0" width="100%">

-       <thead>

-         <tr class="ds-table-header">

-           <th>Cipher Name</th>

-           <th>Symmetric Cipher Name</th>

-           <th>Mac Algorithm Name</th>

-           <th>Symmetric Key Bits</th>

-         </tr>

-       </thead>

-       <tbody id="cipher-body">

-         <tr>

-           <td>TLS_RSA_WITH_AES_256_CBC_SHA256</td>

-           <td>AES</td>

-           <td>SHA256</td>

-           <td>256</td>

-         </tr>

-         <tr>

-           <td>TLS_DHE_DSS_WITH_DES_CBC_SHA</td>

-           <td>AES</td>

-           <td>SHA256</td>

-           <td>256</td>

-         </tr>

-       </tbody>

-     </table>

-   </div>

- 

-   <div id="sec-cacert-page" class="all-pages" hidden>

-     <h3 class="ds-config-header">CA Certificates</h3>

-     <table id="ca-cert-table" class="display ds-repl-table" cellspacing="0" width="100%">

-       <thead>

-         <tr class="ds-table-header">

-           <th>CA Certificate Name</th>

-           <th>Trust Attributes</th>

-           <th>Expiration Date</th>

-           <th>Actions</th>

-         </tr>

-       </thead>

-       <tbody id="cipher-body">

-         <tr>

-           <td>CA Certificate</td>

-           <td>CTu,u,u</td>

-           <td>2020/12/31</td>

-           <td>

-             <div class="dropdown">

-               <button class="btn btn-default dropdown-toggle ds-agmt-dropdown-button" type="button" id="menu1" data-toggle="dropdown">Choose Action...

-                 <span class="caret"></span></button>

-               <ul id="cert-dropdown" class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="menu1">

-                 <li role="certificate"><a role="menuitem" class="ca-cert-dropdown" href="#">View Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="ca-cert-dropdown" href="#">Edit Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="ca-cert-dropdown" href="#">Verify Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="ca-cert-dropdown" href="#">Export Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="ca-cert-dropdown" href="#">Delete Certificate</a></li>

-               </ul>

-             </div>

-           </td>

-         </tr>

-       </tbody>

-     </table>

-     <button class="btn btn-primary" id="import-ca-cert" data-toggle="modal" data-target="#import-cacert-form">Import CA Certificate</button>

-   </div>

- 

-   <div id="sec-svrcert-page" class="all-pages" hidden>

-     <h3 class="ds-config-header">Server Certificates</h3>

-     <table id="server-cert-table" class="display ds-repl-table" cellspacing="0" width="100%">

-       <thead>

-         <tr class="ds-table-header">

-           <th>Server Certificate Name</th>

-           <th>Trust Attributes</th>

-           <th>Issued To</th>

-           <th>Issued By</th>

-           <th>Expiration Date</th>

-           <th>Actions</th>

-         </tr>

-       </thead>

-       <tbody id="cipher-body">

-         <tr>

-           <td>Server-Cert</td>

-           <td>u,u,Pu</td>

-           <td>localhost.localdomain</td>

-           <td>Mark's CA Cert</td>

-           <td>2020/11/22</td>

-           <td>

-             <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="cert-dropdown" class="dropdown-menu" role="menu" aria-labelledby="menu1">

-                 <li role="certificate"><a role="menuitem" class="server-cert-dropdown" href="#">View Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="server-cert-dropdown" href="#">Verify Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="server-cert-dropdown" href="#">Renew Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="server-cert-dropdown" href="#">Export Certificate</a></li>

-                 <li role="certificate"><a role="menuitem" class="server-cert-dropdown" href="#">Delete Certificate</a></li>

-               </ul>

-             </div>

-           </td>

-         </tr>

-       </tbody>

-     </table>

-     <button class="btn btn-default ds-spacing-sm" id="import-server-cert" data-toggle="modal" data-target="#import-cert-form">Import Certificate</button>

-     <button class="btn btn-default" id="import-server-cert">Request Certificate</button>

-   </div>

-       

-   <div id="sec-revoked-page" class="all-pages" hidden>

-     <h3 class="ds-config-header">Revoked Certificates</h3> 

-     <table id="revoked-cert-table" class="display ds-repl-table" cellspacing="0" width="100%">

-       <thead>

-         <tr class="ds-table-header">

-           <th>Issued By</th>

-           <th>Effective Date</th>

-           <th>Next Update</th>

-           <th>Type</th>

-           <th>Actions</th>

-         </tr>

-       </thead>

-       <tbody id="cipher-body">

-         <tr>

-           <td>Mark's CA Cert2</td>

-           <td>2018/11/22</td>

-           <td>2019/11/22</td>

-           <td>CRL</td>

-           <td>

-             <div class="dropdown">

-               <button class="btn btn-default dropdown-toggle ds-agmt-dropdown-button" type="button" id="menu1" data-toggle="dropdown">Choose Action...

-                 <span class="caret"></span></button>

-               <ul id="cert-dropdown" class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="menu1">

-                 <li role="certificate"><a role="menuitem" class="revoked-cert-dropdown" href="#">View</a></li>

-                 <li role="certificate"><a role="menuitem" class="revoked-cert-dropdown" href="#">Delete</a></li>

-               </ul>

-             </div>

-           </td>

-         </tr>

-       </tbody>

-     </table>

-    <button class="btn btn-default ds-spacing-sm" id="add-revoked-btn" data-toggle="modal" data-target="#revoked-form">Add CRL/CKL</button>

-   </div>

- 

-   <!-- Modals/Popups/Wizards  -->

- 

- 

- 

-   <div class="modal fade" id="import-cert-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="import-cert-label" aria-hidden="true">

-     <div class="modal-dialog ds-modal-wide">

-       <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="import-cert-label">Import Server Certificate</h4>

-         </div>

-         <div class="modal-body">

-           <form class="form-horizontal">

-             <div class="ds-inline">

-               <label for="import-cert-file" class="" title="The name of the database link.">Certificate File</label><input

-                 class="ds-input ds-left-margin" type="text" id="import-cert-file" name="name" size="40">

-             </div>

-           </form>

-         </div>

-         <div class="modal-footer ds-modal-footer">

-           <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

-           <button type="button" class="btn btn-primary" id="import-cert-btn" data-dismiss="modal">Import Certificate</button>

-         </div>

-       </div>

-     </div>

-   </div>

- 

-   <div class="modal fade" data-backdrop="static" id="import-cacert-form" tabindex="-1" role="dialog" aria-labelledby="import-cacert-label" aria-hidden="true">

-     <div class="modal-dialog ds-modal-wide">

-       <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="import-cacert-label">Import CA Certificate</h4>

-         </div>

-         <div class="modal-body">

-           <form class="form-horizontal">

-             <div class="ds-inline">

-               <label for="import-cert-file" class="" title="The name of the database link.">Certificate File</label><input

-                 class="ds-input ds-left-margin" type="text" id="import-cacert-file" name="name" size="40">

-             </div>

-           </form>

-         </div>

-         <div class="modal-footer ds-modal-footer">

-           <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

-           <button type="button" class="btn btn-primary" id="import-cacert-btn" data-dismiss="modal">Import Certificate</button>

-         </div>

-       </div>

-     </div>

-   </div>

- 

- 

-   <div class="modal fade" id="revoked-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="revoked-label" aria-hidden="true">

-     <div class="modal-dialog ds-modal-wide">

-       <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="revoked-label">Add Certificate Revocation List/Compromised Key List</h4>

-         </div>

-         <div class="modal-body">

-           <form class="form-horizontal">

-             <div class="ds-inline">

-               <label for="revoked-file">CRL/CKL PEM File</label><input

-                 class="ds-input ds-left-margin" type="text" id="revoked-file" name="name" size="40">

-             </div>

-           </form>

-         </div>

-         <div class="modal-footer ds-modal-footer">

-           <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

-           <button type="button" class="btn btn-primary" id="add-crl-btn" data-dismiss="modal">Add</button>

-         </div>

-       </div>

-     </div>

-   </div>

- 

- 

- 

- 

-   <div id="export-cert" class="modal">

-     <form class="modal-content animate">

-       <div class="container">

-         <h3 id=""><b>Export Certificate</b> <span class="close" id="export-cert-close">&times;</span></h3>

-         

-         <div class="clearfix ds-container">

-           <div class="ds-panel-left">

-             <button type="button" id="export-cert-cancel" class="ds-button-left">Cancel</button>

-           </div>

-           <div class="ds-panel-right">

-             <button type="submit" id="export-cert-save" class="ds-button-right">Export Certificate</button>

-           </div>

-         </div>

-       </div>

-     </form>

-   </div>

- 

- 

@@ -1,137 +0,0 @@ 

- 

- 

- // TODO clear form functions

- 

- 

- $(document).ready( function() {

-   $("#security-content").load("security.html", function () {

-     // default setting

-     $('#cert-attrs *').attr('disabled', true);

- 

-     $(".dropdown").on("change", function() {

-       // Refreshes dropdown on Chrome

-       $(this).blur();

-     });

- 

-     $("#sec-config-btn").on("click", function() {

-       $(".all-pages").hide();

-       $("#security-content").show();

-       $("#sec-config").show();

-     });

- 

-     $("#sec-cacert-btn").on("click", function() {

-       $(".all-pages").hide();

-       $("#security-content").show();

-       $("#sec-cacert-page").show();

-     });

- 

-     $("#sec-srvcert-btn").on("click", function() {

-       $(".all-pages").hide();

-       $("#security-content").show();

-       $("#sec-svrcert-page").show();

-     });

-     $("#sec-revoked-btn").on("click", function() {

-       $(".all-pages").hide();

-       $("#security-content").show();

-       $("#sec-revoked-page").show();

-     });

-     $("#sec-ciphers-btn").on("click", function() {

-       $(".all-pages").hide();

-       $("#security-content").show();

-       $("#sec-ciphers-page").show();

-     });

- 

-     $("#sec-config").show();

- 

-     // Clear forms as theyare clicked

- 

-     $("#add-revoked-btn").on('click', function () {

-       // TODO Clear form

- 

-     });

- 

-     $("#add-crl-btn").on('click', function () {

-       // Add CRL/CKL

- 

-       // Close form

-       $("#revoked-form").modal("toggle");

-     });

- 

-     $('#nssslsupportedciphers').DataTable( {

-       "paging": true,

-       "bAutoWidth": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No ciphers!"

-       }

-     });

- 

-     $("#nsslapd-security").change(function() {

-       if(this.checked) {

-         $('#cert-attrs *').attr('disabled', false);

-       } else {

-         $('#cert-attrs *').attr('disabled', true);

-       }

-     });

- 

-     $("#cipher-default-state").change(function() {

-       if(this.checked) {

-         $("#cipher-table").hide();

-       } else {

-         $("#cipher-table").show();

-       }

-     });

- 

-     // Set up ca cert table

-     $('#ca-cert-table').DataTable( {

-       "paging": false,

-       "bAutoWidth": false,

-       "searching": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No Certificates In Database"

-       },

-       "columnDefs": [ {

-         "targets": 3,

-         "orderable": false

-       } ]

-     });

- 

-     // Set up server cert table

-     $('#server-cert-table').DataTable( {

-       "paging": false,

-       "bAutoWidth": false,

-       "searching": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No Certificates In Database"

-       },

-       "columnDefs": [ {

-         "targets": 5,

-         "orderable": false

-       } ]

-     });

- 

-     // Set up revoked cert table

-     $('#revoked-cert-table').DataTable( {

-       "paging": false,

-       "bAutoWidth": false,

-       "searching": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No Certificates In Database"

-       },

-       "columnDefs": [ {

-         "targets": 4,

-         "orderable": false

-       } ]

-     });

-     // Page is loaded, mark it as so...

-     security_page_loaded = 1;

-   });

- });

- 

@@ -0,0 +1,853 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

+ import Switch from "react-switch";

+ import { NotificationController, ConfirmPopup } from "./lib/notifications.jsx";

+ import { log_cmd } from "./lib/tools.jsx";

+ import { 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 {

+     Nav,

+     NavItem,

+     TabContainer,

+     TabContent,

+     TabPane,

+     Col,

+     Row,

+     ControlLabel,

+     Button,

+     Checkbox,

+     Spinner

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

+ import "./css/ds.css";

+ 

+ export class Security extends React.Component {

+     constructor (props) {

+         super(props);

+         this.state = {

+             loaded: false,

+             saving: false,

+             notifications: [],

+             activeKey: 1,

+ 

+             errObj: {},

+             showConfirmDisable: false,

+             showSecurityEnableModal: false,

+             primaryCertName: '',

+             serverCertNames: [],

+             serverCerts: [],

+             // Ciphers

+             supportedCiphers: [],

+             enabledCiphers: [],

+             // Config settings

+             securityEnabled: false,

+             requireSecureBinds: false,

+             secureListenhost: false,

+             securePort: '636',

+             clientAuth: false,

+             checkHostname: false,

+             validateCert: '',

+             sslVersionMin: '',

+             sslVersionMax: '',

+             allowWeakCipher: false,

+             nssslpersonalityssl: '',

+             // Original config Settings

+             _securityEnabled: false,

+             _requireSecureBinds: false,

+             _secureListenhost: false,

+             _securePort: '636',

+             _clientAuth: false,

+             _checkHostname: false,

+             _validateCert: '',

+             _sslVersionMin: '',

+             _sslVersionMax: '',

+             _allowWeakCipher: false,

+             _nssslpersonalityssl: '',

+         };

+ 

+         this.handleChange = this.handleChange.bind(this);

+         this.addNotification = this.addNotification.bind(this);

+         this.removeNotification = this.removeNotification.bind(this);

+         this.handleNavSelect = this.handleNavSelect.bind(this);

+         this.handleSwitchChange = this.handleSwitchChange.bind(this);

+         this.handleTypeaheadChange = this.handleTypeaheadChange.bind(this);

+         this.loadSecurityConfig = this.loadSecurityConfig.bind(this);

+         this.loadEnabledCiphers = this.loadEnabledCiphers.bind(this);

+         this.loadSupportedCiphers = this.loadSupportedCiphers.bind(this);

+         this.loadCerts = this.loadCerts.bind(this);

+         this.loadCACerts = this.loadCACerts.bind(this);

+         this.closeConfirmDisable = this.closeConfirmDisable.bind(this);

+         this.enableSecurity = this.enableSecurity.bind(this);

+         this.disableSecurity = this.disableSecurity.bind(this);

+         this.saveSecurityConfig = this.saveSecurityConfig.bind(this);

+         this.closeSecurityEnableModal = this.closeSecurityEnableModal.bind(this);

+     }

+ 

+     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

+             )

+         });

+     }

+ 

+     componentWillMount () {

+         if (!this.state.loaded) {

+             this.setState({securityEnabled: true}, this.setState({securityEnabled: false}));

+             this.loadSecurityConfig();

+         }

+     }

+ 

+     loadSupportedCiphers () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ciphers", "list", "--supported"

+         ];

+         log_cmd("loadSupportedCiphers", "Load the security configuration", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const config = JSON.parse(content);

+                     this.setState({

+                         supportedCiphers: config.items

+                     }, this.loadEnabledCiphers);

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading security configuration - ${msg}`

+                     );

+                 });

+     }

+ 

+     loadEnabledCiphers () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ciphers", "list", "--enabled"

+         ];

+         log_cmd("loadEnabledCiphers", "Load the security configuration", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const config = JSON.parse(content);

+                     this.setState({

+                         enabledCiphers: config.items,

+                     }, this.loadCerts);

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading security configuration - ${msg}`

+                     );

+                 });

+     }

+ 

+     loadCACerts () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "ca-certificate", "list",

+         ];

+         log_cmd("loadCACerts", "Load certificates", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     let certs = JSON.parse(content);

+                     this.setState(() => (

+                         {

+                             CACerts: certs,

+                             loaded: true

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading CA certificates - ${msg}`

+                     );

+                 });

+     }

+ 

+     loadCerts () {

+         // Set loaded: true

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "certificate", "list",

+         ];

+         log_cmd("loadCerts", "Load certificates", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const certs = JSON.parse(content);

+                     let certNames = [];

+                     for (let cert of certs) {

+                         certNames.push(cert.attrs['nickname']);

+                     }

+                     this.setState(() => (

+                         {

+                             serverCerts: certs,

+                             serverCertNames: certNames,

+                         }), this.loadCACerts

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading server certificates - ${msg}`

+                     );

+                 });

+     }

+ 

+     loadRSAConfig() {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "rsa", "get"

+         ];

+         log_cmd("loadRSAConfig", "Load the RSA configuration", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const config = JSON.parse(content);

+                     const nickname = config.items['nssslpersonalityssl'];

+                     this.setState(() => (

+                         {

+                             nssslpersonalityssl: nickname,

+                             _nssslpersonalityssl: nickname,

+                         }

+                     ), this.loadSupportedCiphers);

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading security RSA configuration - ${msg}`

+                     );

+                 });

+     }

+ 

+     loadSecurityConfig(saving) {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "get"

+         ];

+         log_cmd("loadSecurityConfig", "Load the security configuration", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const config = JSON.parse(content);

+                     const attrs = config.items;

+                     let secEnabled = false;

+                     let secReqSecBinds = false;

+                     let clientAuth = "allowed";

+                     let validateCert = "warn";

+                     let cipherPref = "default";

+                     let allowWeak = false;

+ 

+                     if ('nsslapd-security' in attrs) {

+                         if (attrs['nsslapd-security'].toLowerCase() == "on") {

+                             secEnabled = true;

+                         }

+                     }

+                     if ('nsslapd-require-secure-binds' in attrs) {

+                         if (attrs['nsslapd-require-secure-binds'].toLowerCase() == "on") {

+                             secReqSecBinds = true;

+                         }

+                     }

+                     if ('nssslclientauth' in attrs) {

+                         if (attrs['nssslclientauth'] != "") {

+                             clientAuth = attrs['nssslclientauth'];

+                         }

+                     }

+                     if ('nsslapd-validate-cert' in attrs) {

+                         if (attrs['nsslapd-validate-cert'] != "") {

+                             validateCert = attrs['nsslapd-validate-cert'].toLowerCase();

+                         }

+                     }

+                     if ('allowweakcipher' in attrs) {

+                         if (attrs['allowweakcipher'].toLowerCase() == "on") {

+                             allowWeak = true;

+                         }

+                     }

+                     if ('nsssl3ciphers' in attrs) {

+                         if (attrs['nsssl3ciphers'] != "") {

+                             cipherPref = attrs['nsssl3ciphers'];

+                         }

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             securityEnabled: secEnabled,

+                             requireSecureBinds: secReqSecBinds,

+                             secureListenhost: attrs['nsslapd-securelistenhost'],

+                             securePort: attrs['nsslapd-secureport'],

+                             clientAuth: clientAuth,

+                             checkHostname: attrs['nsslapd-ssl-check-hostname'],

+                             validateCert: validateCert,

+                             sslVersionMin: attrs['sslversionmin'],

+                             sslVersionMax: attrs['sslversionmax'],

+                             allowWeakCipher: allowWeak,

+                             cipherPref: cipherPref,

+                             _securityEnabled: secEnabled,

+                             _requireSecureBinds: secReqSecBinds,

+                             _secureListenhost: attrs['nsslapd-securelistenhost'],

+                             _securePort: attrs['nsslapd-secureport'],

+                             _clientAuth: clientAuth,

+                             _checkHostname: attrs['nsslapd-ssl-check-hostname'],

+                             _validateCert: validateCert,

+                             _sslVersionMin: attrs['sslversionmin'],

+                             _sslVersionMax: attrs['sslversionmax'],

+                             _allowWeakCipher: allowWeak,

+                         }

+                     ), function() {

+                         if (!saving) {

+                             this.loadRSAConfig();

+                         }

+                     });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error loading security configuration - ${msg}`

+                     );

+                 });

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({

+             activeKey: key

+         });

+     }

+ 

+     handleSwitchChange(value) {

+         if (!value) {

+             // We are disabling security, ask for confirmation

+             this.setState({showConfirmDisable: true});

+         } else {

+             // Check if we have certs, if we do make the user choose one from dropdown list, otherwise reject the

+             // enablement

+             if (this.state.serverCerts.length > 0) {

+                 this.setState({

+                     primaryCertName: this.state.nssslpersonalityssl,

+                     showSecurityEnableModal: true,

+                 });

+             } else {

+                 this.addNotification(

+                     "error",

+                     `There must be at least one server certificate present in the security database to enable security`

+                 );

+             }

+         }

+     }

+ 

+     closeSecurityEnableModal () {

+         this.setState({

+             showSecurityEnableModal: false,

+         });

+     }

+ 

+     handleSecEnableChange (e) {

+         const value = e.target.value.trim();

+         this.setState({

+             primaryCertName: value,

+         });

+     }

+ 

+     closeConfirmDisable () {

+         this.setState({

+             showConfirmDisable: false

+         });

+     }

+ 

+     enableSecurity () {

+         /* start the spinner */

+         this.setState({

+             secEnableSpinner: true

+         });

+ 

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "enable",

+         ];

+         log_cmd("enableSecurity", "Enable security", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.addNotification(

+                         "success",

+                         `Successfully enabled security.  You must restart the server for this to take effect.`

+                     );

+                     this.setState({

+                         securityEnabled: true,

+                         secEnableSpinner: false,

+                         showSecurityEnableModal: false,

+                     });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error enabling security - ${msg}`

+                     );

+                     this.setState({

+                         secEnableSpinner: false,

+                         showSecurityEnableModal: false,

+                     });

+                 });

+     }

+ 

+     disableSecurity () {

+         const cmd = [

+             "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "security", "disable",

+         ];

+         log_cmd("disableSecurity", "Disable security", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(() => {

+                     this.addNotification(

+                         "success",

+                         `Successfully disabled security.  You must restart the server for this to take effect.`

+                     );

+                     this.setState({

+                         securityEnabled: false,

+                     });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     let msg = errMsg.desc;

+                     if ('info' in errMsg) {

+                         msg = errMsg.desc + " - " + errMsg.info;

+                     }

+                     this.addNotification(

+                         "error",

+                         `Error disabling security - ${msg}`

+                     );

+                 });

+     }

+ 

+     saveSecurityConfig () {

+         let cmd = [

+             'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',

+             'security', 'set'

+         ];

+ 

+         if (this.state._validateCert != this.state.validateCert) {

+             cmd.push("--verify-cert-chain-on-startup=" + this.state.validateCert);

+         }

+         if (this.state._sslVersionMin != this.state.sslVersionMin) {

+             cmd.push("--tls-protocol-min=" + this.state.sslVersionMin);

+         }

+         if (this.state._sslVersionMax != this.state.sslVersionMax) {

+             cmd.push("--tls-protocol-max=" + this.state.sslVersionMax);

+         }

+         if (this.state._clientAuth != this.state.clientAuth) {

+             cmd.push("--tls-client-auth=" + this.state.clientAuth);

+         }

+         if (this.state._securePort != this.state.securePort) {

+             cmd.push("--secure-port=" + this.state.securePort);

+         }

+         if (this.state._secureListenhost != this.state.secureListenhost) {

+             cmd.push("--listen-host=" + this.state.secureListenhost);

+         }

+         if (this.state._allowWeakCipher != this.state.allowWeakCipher) {

+             let val = "off";

+             if (this.state.allowWeakCipher) {

+                 val = "on";

+             }

+             cmd.push("--allow-insecure-ciphers=" + val);

+         }

+         if (this.state._checkHostname != this.state.checkHostname) {

+             let val = "off";

+             if (this.state.checkHostname) {

+                 val = "on";

+             }

+             cmd.push("--check-hostname=" + val);

+         }

+         if (this.state._requireSecureBinds != this.state.requireSecureBinds) {

+             let val = "off";

+             if (this.state.requireSecureBinds) {

+                 val = "on";

+             }

+             cmd.push("--require-secure-authentication=" + val);

+         }

+ 

+         if (cmd.length > 5) {

+             log_cmd("saveSecurityConfig", "Applying security config change", cmd);

+             let msg = "Successfully updated security configuration.  You must restart the server for these changes to take effect.";

+ 

+             this.setState({

+                 // Start the spinner

+                 saving: true

+             });

+ 

+             cockpit

+                     .spawn(cmd, {superuser: true, "err": "message"})

+                     .done(content => {

+                         this.loadSecurityConfig(1);

+                         this.addNotification(

+                             "success",

+                             msg

+                         );

+                         this.setState({

+                             saving: false

+                         });

+                     })

+                     .fail(err => {

+                         let errMsg = JSON.parse(err);

+                         this.loadSecurityConfig();

+                         this.setState({

+                             saving: false

+                         });

+                         let msg = errMsg.desc;

+                         if ('info' in errMsg) {

+                             msg = errMsg.desc + " - " + errMsg.info;

+                         }

+                         this.addNotification(

+                             "error",

+                             `Error updating security configuration - ${msg}`

+                         );

+                     });

+         }

+     }

+ 

+     handleTypeaheadChange(value) {

+         if (value.length == 0) {

+             return;

+         }

+         this.setState({

+             nssslpersonalityssl: value[0],

+         });

+     }

+ 

+     handleChange(e) {

+         const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         this.setState({

+             [e.target.id]: value,

+         });

+     }

+ 

+     handleLoginModal(e) {

+         const value = e.target.value.trim();

+         let valueErr = false;

+         let errObj = this.state.errObj;

+         if (value == "") {

+             valueErr = true;

+         }

+         errObj[e.target.id] = valueErr;

+         this.setState({

+             [e.target.id]: value,

+             errObj: errObj

+         });

+     }

+ 

+     render() {

+         let securityPage = "";

+         let serverCert = [this.state.nssslpersonalityssl];

+ 

+         if (this.state.loaded && !this.state.saving) {

+             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={2}>

+                                 Server Secure Port

+                             </Col>

+                             <Col sm={4}>

+                                 <input id="securePort" className="ds-input-auto" onChange={this.handleChange} type="text" defaultValue={this.state.securePort} />

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address).  This parameter specifically sets what interface to use for TLS traffic.  Requires restart. (nsslapd-securelistenhost).">

+                             <Col componentClass={ControlLabel} sm={2}>

+                                 Secure Listen Host

+                             </Col>

+                             <Col sm={4}>

+                                 <input id="secureListenhost" className="ds-input-auto" type="text" onChange={this.handleChange} defaultValue={this.state.secureListenhost} />

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="The name, or nickname, of the server certificate inthe NSS datgabase the server should use (nsSSLPersonalitySSL).">

+                             <Col className="ds-no-padding" sm={2}>

+                                 <ControlLabel>Server Certificate Name</ControlLabel>

+                             </Col>

+                             <Col sm={4}>

+                                 <Typeahead

+                                     id="serverCertNameTypeahead"

+                                     onChange={this.handleTypeaheadChange}

+                                     selected={serverCert}

+                                     emptyLabel="No matching certificates found"

+                                     options={this.state.serverCertNames}

+                                     newSelectionPrefix="Select a server certificate"

+                                     placeholder="Type a sever certificate nickname..."

+                                 />

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="The minimum SSL/TLS version the server will accept (sslversionmin).">

+                             <Col componentClass={ControlLabel} sm={2}>

+                                 Minimum TLS Version

+                             </Col>

+                             <Col sm={4}>

+                                 <select id="sslVersionMin" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.sslVersionMin}>

+                                     <option />

+                                     <option>TLS1.3</option>

+                                     <option>TLS1.2</option>

+                                     <option>TLS1.1</option>

+                                     <option>TLS1.0</option>

+                                     <option>SSL3</option>

+                                 </select>

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="The maximum SSL/TLS version the server will accept (sslversionmax).">

+                             <Col componentClass={ControlLabel} sm={2}>

+                                 Maximum TLS Version

+                             </Col>

+                             <Col sm={4}>

+                                 <select id="sslVersionMax" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.sslVersionMax}>

+                                     <option />

+                                     <option>TLS1.3</option>

+                                     <option>TLS1.2</option>

+                                     <option>TLS1.1</option>

+                                     <option>TLS1.0</option>

+                                     <option>SSL3</option>

+                                 </select>

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="Sets how the Directory Server enforces TLS client authentication (nsSSLClientAuth).">

+                             <Col componentClass={ControlLabel} sm={2}>

+                                 Client Authentication

+                             </Col>

+                             <Col sm={4}>

+                                 <select id="clientAuth" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.clientAuth}>

+                                     <option>off</option>

+                                     <option>allowed</option>

+                                     <option>required</option>

+                                 </select>

+                             </Col>

+                         </Row>

+                         <Row className="ds-margin-top" title="Validate server's certificate expiration date (nsslapd-validate-cert).">

+                             <Col componentClass={ControlLabel} sm={2}>

+                                 Validate Certificate

+                             </Col>

+                             <Col sm={4}>

+                                 <select id="validateCert" className="btn btn-default dropdown ds-select" onChange={this.handleChange} defaultValue={this.state.validateCert}>

+                                     <option>warn</option>

+                                     <option>on</option>

+                                     <option>off</option>

+                                 </select>

+                             </Col>

+                         </Row>

+                         <p />

+                         <Row>

+                             <Col sm={5}>

+                                 <Checkbox

+                                     id="requireSecureBinds"

+                                     defaultChecked={this.state.requireSecureBinds}

+                                     onChange={this.handleChange}

+                                     title="Require all connections use TLS (nsslapd-require-secure-binds)."

+                                 >

+                                     Require Secure Connections

+                                 </Checkbox>

+                             </Col>

+                         </Row>

+                         <Row>

+                             <Col sm={5}>

+                                 <Checkbox

+                                     id="checkHostname"

+                                     defaultChecked={this.state.checkHostname}

+                                     onChange={this.handleChange}

+                                     title="Verify authenticity of a request by matching the host name against the value assigned to the common name (cn) attribute of the subject name (subjectDN field) in the certificate being presented. (nsslapd-ssl-check-hostname)."

+                                 >

+                                     Verify Certificate Subject Hostname

+                                 </Checkbox>

+                             </Col>

+                         </Row>

+                         <Row>

+                             <Col sm={5}>

+                                 <Checkbox

+                                     id="allowWeakCipher"

+                                     defaultChecked={this.state.allowWeakCipher}

+                                     onChange={this.handleChange}

+                                     title="Allow weak ciphers (allowWeakCipher)."

+                                 >

+                                     Allow Weak Ciphers

+                                 </Checkbox>

+                             </Col>

+                         </Row>

+                         <p />

+                         <Button

+                             bsStyle="primary"

+                             className="ds-margin-top-med"

+                             onClick={() => {

+                                 this.saveSecurityConfig();

+                             }}

+                         >

+                             Save Configuration

+                         </Button>

+                     </div>;

+             }

+ 

+             securityPage =

+                 <div className="container-fluid">

+                     <NotificationController

+                         notifications={this.state.notifications}

+                         removeNotificationAction={this.removeNotification}

+                     />

+                     <div className="ds-tab-table">

+                         <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

+                             <div>

+                                 <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                                     <NavItem eventKey={1}>

+                                         <div dangerouslySetInnerHTML={{__html: 'Security Configuration'}} />

+                                     </NavItem>

+                                     <NavItem eventKey={2}>

+                                         <div dangerouslySetInnerHTML={{__html: 'Certificate Management'}} />

+                                     </NavItem>

+                                     <NavItem eventKey={3}>

+                                         <div dangerouslySetInnerHTML={{__html: 'Cipher Preferences'}} />

+                                     </NavItem>

+                                 </Nav>

+                                 <TabContent>

+                                     <TabPane eventKey={1}>

+                                         <div className="ds-margin-top-xlg ds-indent">

+                                             <Row>

+                                                 <Col componentClass={ControlLabel} sm={2}>

+                                                     Security Enabled

+                                                 </Col>

+                                                 <Col sm={2}>

+                                                     <Switch

+                                                         onChange={this.handleSwitchChange}

+                                                         checked={this.state.securityEnabled}

+                                                         height={20}

+                                                     />

+                                                 </Col>

+                                             </Row>

+                                             <hr />

+                                             {configPage}

+                                         </div>

+                                     </TabPane>

+ 

+                                     <TabPane eventKey={2}>

+                                         <div className="ds-margin-top-lg">

+                                             <CertificateManagement

+                                                 serverId={this.props.serverId}

+                                                 CACerts={this.state.CACerts}

+                                                 ServerCerts={this.state.serverCerts}

+                                                 addNotification={this.addNotification}

+                                             />

+                                         </div>

+                                     </TabPane>

+ 

+                                     <TabPane eventKey={3}>

+                                         <div className="ds-indent ds-tab-table">

+                                             <Ciphers

+                                                 serverId={this.props.serverId}

+                                                 supportedCiphers={this.state.supportedCiphers}

+                                                 cipherPref={this.state.cipherPref}

+                                                 enabledCiphers={this.state.enabledCiphers}

+                                                 addNotification={this.addNotification}

+                                             />

+                                         </div>

+                                     </TabPane>

+                                 </TabContent>

+                             </div>

+                         </TabContainer>

+                     </div>

+                 </div>;

+         } else if (this.state.saving) {

+             securityPage =

+                 <div className="ds-loading-spinner ds-center">

+                     <p />

+                     <h4>Saving security information ...</h4>

+                     <Spinner loading size="md" />

+                 </div>;

+         } else {

+             securityPage =

+                 <div className="ds-loading-spinner ds-center">

+                     <p />

+                     <h4>Loading security information ...</h4>

+                     <Spinner loading size="md" />

+                 </div>;

+         }

+         return (

+             <div>

+                 {securityPage}

+                 <ConfirmPopup

+                     showModal={this.state.showConfirmDisable}

+                     closeHandler={this.closeConfirmDisable}

+                     actionFunc={this.disableSecurity}

+                     msg="Are you sure you want to disable security?"

+                     msgContent="Attention: this requires the server to be restarted to take effect."

+                 />

+                 <SecurityEnableModal

+                     showModal={this.state.showSecurityEnableModal}

+                     closeHandler={this.closeSecurityEnableModal}

+                     handleChange={this.handleSecEnableChange}

+                     saveHandler={this.enableSecurity}

+                     primaryName={this.state.primaryCertName}

+                     certs={this.state.serverCerts}

+                     spinning={this.state.secEnableSpinner}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ // Props and defaultProps

+ 

+ Security.propTypes = {

+     serverId: PropTypes.string,

+ };

+ 

+ Security.defaultProps = {

+     serverId: "",

+ };

+ 

+ export default Security;

@@ -34,8 +34,6 @@ 

          "replication.js",

          "schema.html",

          "schema.js",

-         "security.html",

-         "security.js",

          "servers.html",

          "servers.js",

          "static",
@@ -131,7 +129,18 @@ 

              {

                  exclude: /node_modules/,

                  loader: "babel-loader",

-                 test: /\.jsx$/

+                 test: /\.jsx$/,

+                 options: {

+                     presets: [

+                         '@babel/preset-env',

+                         '@babel/preset-react',

+                         {

+                             plugins: [

+                                 '@babel/plugin-proposal-class-properties'

+                             ]

+                         }

+                     ]

+                 },

              },

              {

                  exclude: /node_modules/,

file modified
-3
@@ -11,14 +11,11 @@ 

  # PYTHON_ARGCOMPLETE_OK

  

  import argparse, argcomplete

- import logging

  import ldap

  import sys

  import signal

  import json

  import ast

- from lib389 import DirSrv

- from lib389._constants import DN_CONFIG, DN_DM

  from lib389.cli_conf import config as cli_config

  from lib389.cli_conf import backend as cli_backend

  from lib389.cli_conf import directory_manager as cli_directory_manager

file modified
+4 -23
@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2015 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # Copyright (C) 2019 William Brown <william@blackhats.net.au>

  # All rights reserved.

  #
@@ -19,40 +19,28 @@ 

      TODO: reorganize method parameters according to SimpleLDAPObject

          naming: filterstr, attrlist

  """

- try:

-     from subprocess import Popen, PIPE, STDOUT

-     HASPOPEN = True

- except ImportError:

-     import popen2

-     HASPOPEN = False

- 

- import io

+ 

  import sys

  import os

  import stat

  import pwd

  import grp

  import os.path

- import base64

  import socket

  import ldif

  import re

  import ldap

  import ldapurl

  import time

- import operator

  import shutil

  from datetime import datetime

  import logging

- import decimal

  import glob

  import tarfile

  import subprocess

  from collections.abc import Callable

  import signal

  import errno

- import pwd

- import grp

  import uuid

  import json

  from shutil import copy2
@@ -63,25 +51,18 @@ 

  import inspect

  

  from ldap.ldapobject import SimpleLDAPObject

- from ldap.cidict import cidict

- from ldap import LDAPError

  # file in this package

  

  from lib389._constants import *

  from lib389.properties import *

  from lib389._entry import Entry

- from lib389._replication import CSN, RUV

  from lib389._ldifconn import LDIFConn

  from lib389.tools import DirSrvTools

- from lib389.mit_krb5 import MitKrb5

  from lib389.utils import (

      ds_is_older,

      isLocalHost,

-     is_a_dn,

      normalizeDN,

-     suffixfilt,

      escapeDNValue,

-     update_newhost_with_fqdn,

      formatInfData,

      ensure_bytes,

      ensure_str,
@@ -765,7 +746,7 @@ 

              for pi in potential_inst:

                  pi_dse_ldif = os.path.join(pi, 'dse.ldif')

                  # Takes /etc/dirsrv/slapd-instance -> slapd-instance -> instance

-                 pi_name = pi.split('/')[-1].split('-')[-1]

+                 pi_name = pi.split('/')[-1].split('slapd-')[-1]

                  # parse + append

                  if os.path.exists(pi_dse_ldif):

                      instances.append(_parse_configfile(pi_dse_ldif, pi_name))
@@ -3094,7 +3075,7 @@ 

          ]

  

          try:

-             result = subprocess.check_output(cmd, encoding='utf-8')

+             subprocess.check_output(cmd, encoding='utf-8')

          except subprocess.CalledProcessError as e:

              self.log.debug("Command: %s failed with the return code %s and the error %s",

                             format_cmd_list(cmd), e.returncode, e.output)

@@ -8,7 +8,7 @@ 

  

  from collections import OrderedDict, namedtuple

  import json

- 

+ import os

  from lib389.config import Config, Encryption, RSA

  from lib389.nss_ssl import NssSsl

  
@@ -27,45 +27,48 @@ 

      ('secure-port', Props(Config, 'nsslapd-securePort',

                            'Port for LDAPS to listen on',

                            range(1, 65536))),

-     ('tls-client-auth', Props(Config, 'nsSSLClientAuth',

-                           'Client authentication requirement',

-                           ('off', 'allowed', 'required'))),

+     ('tls-client-auth', Props(Encryption, 'nsSSLClientAuth',

+                               'Client authentication requirement',

+                               ('off', 'allowed', 'required'))),

      ('require-secure-authentication', Props(Config, 'nsslapd-require-secure-binds',

-                                    'Require binds over LDAPS, StartTLS, or SASL',

-                                    onoff)),

+                                             'Require binds over LDAPS, StartTLS, or SASL',

+                                             onoff)),

      ('check-hostname', Props(Config, 'nsslapd-ssl-check-hostname',

                               'Check Subject of remote certificate against the hostname',

                               onoff)),

      ('verify-cert-chain-on-startup', Props(Config, 'nsslapd-validate-cert',

-                                 'Validate server certificate during startup',

-                                 ('warn', *onoff))),

+                                            'Validate server certificate during startup',

+                                            ('warn', *onoff))),

      ('session-timeout', Props(Encryption, 'nsSSLSessionTimeout',

                                'Secure session timeout',

                                int)),

      ('tls-protocol-min', Props(Encryption, 'sslVersionMin',

-                            'Secure protocol minimal allowed version',

-                            protocol_versions)),

+                                'Secure protocol minimal allowed version',

+                                protocol_versions)),

      ('tls-protocol-max', Props(Encryption, 'sslVersionMax',

-                            'Secure protocol maximal allowed version',

-                            protocol_versions)),

+                                'Secure protocol maximal allowed version',

+                                protocol_versions)),

      ('allow-insecure-ciphers', Props(Encryption, 'allowWeakCipher',

-                                 'Allow weak ciphers for legacy use',

-                                 onoff)),

+                                      'Allow weak ciphers for legacy use',

+                                      onoff)),

      ('allow-weak-dh-param', Props(Encryption, 'allowWeakDHParam',

                                    'Allow short DH params for legacy use',

                                    onoff)),

+     ('cipher-pref', Props(Encryption, 'nsSSL3Ciphers',

+                           'Use this command to directly set nsSSL3Ciphers attribute. It is a comma separated list '

+                           'of cipher names (prefixed with + or -), optionally including +all or -all. The attribute '

+                           'may optionally be prefixed by keyword default. Please refer to documentation of '

+                           'the attribute for a more detailed description.',

+                           onoff)),

  ])

  

  RSA_ATTRS_MAP = OrderedDict([

      ('tls-allow-rsa-certificates', Props(RSA, 'nsSSLActivation',

-                              'Activate use of RSA certificates',

-                              onoff)),

+                                          'Activate use of RSA certificates', onoff)),

      ('nss-cert-name', Props(RSA, 'nsSSLPersonalitySSL',

-                           'Server certificate name in NSS DB',

-                           str)),

+                             'Server certificate name in NSS DB', str)),

      ('nss-token', Props(RSA, 'nsSSLToken',

-                     'Security token name (module of NSS DB)',

-                     str))

+                         'Security token name (module of NSS DB)', str))

  ])

  

  
@@ -73,7 +76,9 @@ 

      result = {}

      for attr, props in attrs_map.items():

          val = props.cls(inst).get_attr_val_utf8(props.attr)

-         result[props.attr] = val

+         if val is None:

+             val = ""

+         result[props.attr.lower()] = val

      if args.json:

          print(json.dumps({'type': 'list', 'items': result}))

      else:
@@ -126,14 +131,22 @@ 

  

      return list(map(add_parser, ('Enable', 'Disable'), ('on', 'off')))

  

- 

  def security_enable(inst, basedn, log, args):

      dbpath = inst.get_cert_dir()

      tlsdb = NssSsl(dbpath=dbpath)

-     if not tlsdb._db_exists(even_partial=True):  # we want to be very careful

-         log.info(f'Secure database does not exist. Creating a new one in {dbpath}.')

-         tlsdb.reinit()

- 

+     certs = tlsdb.list_certs()

+     if len(certs) == 0:

+         raise ValueError('There are no server certificates in the security ' +

+                          'database, security can not be enabled.')

+ 

+     if len(certs) == 1:

+         # If there is only cert make sure it is set as the server certificate

+         RSA(inst).set('nsSSLPersonalitySSL', certs[0][0])

+     elif args.cert_name is not None:

+         # A certificate nickname was provided, set it as the server certificate

+         RSA(inst).set('nsSSLPersonalitySSL', args.cert_name)

+ 

+     # it should now be safe to enable security

      Config(inst).set('nsslapd-security', 'on')

  

  
@@ -184,29 +197,246 @@ 

              print(*lst, sep='\n')

  

  

+ def cert_add(inst, basedn, log, args):

+     """Add server certificate

+     """

+     # Verify file and certificate name

+     os.path.isfile(args.file)

+     tlsdb = NssSsl(dirsrv=inst)

+     if not tlsdb._db_exists(even_partial=True):  # we want to be very careful

+         log.info('Security database does not exist. Creating a new one in {}.'.format(inst.get_cert_dir()))

+         tlsdb.reinit()

+ 

+     try:

+         tlsdb.get_cert_details(args.name)

+         raise ValueError("Certificate already exists with the same name")

+     except ValueError:

+         pass

+ 

+     if args.primary_cert:

+         # This is the server's primary certificate, update RSA entry

+         RSA(inst).set('nsSSLPersonalitySSL', args.name)

+ 

+     # Add the cert

+     tlsdb.add_cert(args.name, args.file)

+ 

+ 

+ def cacert_add(inst, basedn, log, args):

+     """Add CA certificate

+     """

+     # Verify file and certificate name

+     os.path.isfile(args.file)

+     tlsdb = NssSsl(dirsrv=inst)

+     if not tlsdb._db_exists(even_partial=True):  # we want to be very careful

+         log.info('Security database does not exist. Creating a new one in {}.'.format(inst.get_cert_dir()))

+         tlsdb.reinit()

+ 

+     try:

+         tlsdb.get_cert_details(args.name)

+         raise ValueError("Certificate already exists with the same name")

+     except ValueError:

+         pass

+ 

+     # Add the cert

+     tlsdb.add_cert(args.name, args.file, ca=True)

+ 

+ 

+ def cert_list(inst, basedn, log, args):

+     """List all the server certificates

+     """

+     cert_list = []

+     tlsdb = NssSsl(dirsrv=inst)

+     certs = tlsdb.list_certs()

+     for cert in certs:

+         if args.json:

+             cert_list.append(

+                 {

+                     "type": "certificate",

+                     "attrs": {

+                                 'nickname': cert[0],

+                                 'subject': cert[1],

+                                 'issuer': cert[2],

+                                 'expires': cert[3],

+                                 'flags': cert[4],

+                             }

+                 }

+             )

+         else:

+             log.info('Certificate Name: {}'.format(cert[0]))

+             log.info('Subject DN: {}'.format(cert[1]))

+             log.info('Issuer DN: {}'.format(cert[2]))

+             log.info('Expires: {}'.format(cert[3]))

+             log.info('Trust Flags: {}\n'.format(cert[4]))

+     if args.json:

+         log.info(json.dumps(cert_list))

+ 

+ 

+ def cacert_list(inst, basedn, log, args):

+     """List all CA certs

+     """

+     cert_list = []

+     tlsdb = NssSsl(dirsrv=inst)

+     certs = tlsdb.list_certs(ca=True)

+     for cert in certs:

+         if args.json:

+             cert_list.append(

+                 {

+                     "type": "certificate",

+                     "attrs": {

+                                 'nickname': cert[0],

+                                 'subject': cert[1],

+                                 'issuer': cert[2],

+                                 'expires': cert[3],

+                                 'flags': cert[4],

+                             }

+                 }

+             )

+         else:

+             log.info('Certificate Name: {}'.format(cert[0]))

+             log.info('Subject DN: {}'.format(cert[1]))

+             log.info('Issuer DN: {}'.format(cert[2]))

+             log.info('Expires: {}'.format(cert[3]))

+             log.info('Trust Flags: {}\n'.format(cert[4]))

+     if args.json:

+         log.info(json.dumps(cert_list))

+ 

+ 

+ def cert_get(inst, basedn, log, args):

+     """Get the details about a server certificate

+     """

+     tlsdb = NssSsl(dirsrv=inst)

+     details = tlsdb.get_cert_details(args.name)

+     if args.json:

+         log.info(json.dumps(

+                 {

+                     "type": "certificate",

+                     "attrs": {

+                                 'nickname': details[0],

+                                 'subject': details[1],

+                                 'issuer': details[2],

+                                 'expires': details[3],

+                                 'flags': details[4],

+                             }

+                 }

+             )

+         )

+     else:

+         log.info('Certificate Name: {}'.format(details[0]))

+         log.info('Subject DN: {}'.format(details[1]))

+         log.info('Issuer DN: {}'.format(details[2]))

+         log.info('Expires: {}'.format(details[3]))

+         log.info('Trust Flags: {}'.format(details[4]))

+ 

+ 

+ def cert_edit(inst, basedn, log, args):

+     """Edit cert

+     """

+     tlsdb = NssSsl(dirsrv=inst)

+     tlsdb.edit_cert_trust(args.name, args.flags)

+ 

+ 

+ def cert_del(inst, basedn, log, args):

+     """Delete cert

+     """

+     tlsdb = NssSsl(dirsrv=inst)

+     tlsdb.del_cert(args.name)

+ 

+ 

  def create_parser(subparsers):

      security = subparsers.add_parser('security', help='Query and manipulate security options')

      security_sub = security.add_subparsers(help='security')

-     security_set = _security_generic_set_parser(security_sub, SECURITY_ATTRS_MAP, 'Set general security options',

+ 

+     # Core security management

+     _security_generic_set_parser(security_sub, SECURITY_ATTRS_MAP, 'Set general security options',

          ('Use this command for setting security related options located in cn=config and cn=encryption,cn=config.'

           '\n\nTo enable/disable security you can use enable and disable commands instead.'))

-     security_get = _security_generic_get_parser(security_sub, SECURITY_ATTRS_MAP, 'Get general security options')

+     _security_generic_get_parser(security_sub, SECURITY_ATTRS_MAP, 'Get general security options')

      security_enable_p = security_sub.add_parser('enable', help='Enable security', description=(

          'If missing, create security database, then turn on security functionality. Please note this is usually not'

-         ' enought for TLS connections to work - proper setup of CA and server certificate is necessary.'))

+         ' enough for TLS connections to work - proper setup of CA and server certificate is necessary.'))

+     security_enable_p.add_argument('--cert-name', default=None,

+         help='The name of the certificate the server should use')

      security_enable_p.set_defaults(func=security_enable)

      security_disable_p = security_sub.add_parser('disable', help='Disable security', description=(

          'Turn off security functionality. The rest of the configuration will be left untouched.'))

      security_disable_p.set_defaults(func=security_disable)

  

-     rsa = security_sub.add_parser('rsa', help='Query and mainpulate RSA security options')

+     # Server certificate management

+     certs = security_sub.add_parser('certificate', help='Manage TLS certificates')

+     certs_sub = certs.add_subparsers(help='certificate')

+     cert_add_parser = certs_sub.add_parser('add', help='Add a server certificate', description=(

+         'Add a server certificate to the NSS database'))

+     cert_add_parser.add_argument('--file', required=True,

+         help='The file name of the certificate')

+     cert_add_parser.add_argument('--name', required=True,

+         help='The name/nickname of the certificate')

+     cert_add_parser.add_argument('--primary-cert', action='store_true',

+                                  help="Set this certificate as the server's certificate")

+     cert_add_parser.set_defaults(func=cert_add)

+ 

+     cert_edit_parser = certs_sub.add_parser('set-trust-flags', help='Set the Trust flags',

+         description=('Change the trust flags of a server certificate'))

+     cert_edit_parser.add_argument('name', help='The name/nickname of the certificate')

+     cert_edit_parser.add_argument('--flags', required=True,

+         help='The trust flags for the server certificate')

+     cert_edit_parser.set_defaults(func=cert_edit)

+ 

+     cert_del_parser = certs_sub.add_parser('del', help='Delete a certificate',

+         description=('Delete a certificate from the NSS database'))

+     cert_del_parser.add_argument('name', help='The name/nickname of the certificate')

+     cert_del_parser.set_defaults(func=cert_del)

+ 

+     cert_get_parser = certs_sub.add_parser('get', help="Get a server certificate's information",

+         description=('Get detailed information about a certificate, like trust attributes, expiration dates, Subject and Issuer DNs '))

+     cert_get_parser.add_argument('name', help='The name/nickname of the certificate')

+     cert_get_parser.set_defaults(func=cert_get)

+ 

+     cert_list_parser = certs_sub.add_parser('list', help='List the server certificates',

+         description=('List the server certificates in the NSS database'))

+     cert_list_parser.set_defaults(func=cert_list)

+ 

+     # CA certificate management

+     cacerts = security_sub.add_parser('ca-certificate', help='Manage TLS Certificate Authorities')

+     cacerts_sub = cacerts.add_subparsers(help='ca-certificate')

+     cacert_add_parser = cacerts_sub.add_parser('add', help='Add a Certificate Authority', description=(

+         'Add a Certificate Authority to the NSS database'))

+     cacert_add_parser.add_argument('--file', required=True,

+         help='The file name of the CA certificate')

+     cacert_add_parser.add_argument('--name', required=True,

+         help='The name/nickname of the CA certificate')

+     cacert_add_parser.set_defaults(func=cacert_add)

+ 

+     cacert_edit_parser = cacerts_sub.add_parser('set-trust-flags', help='Set the Trust flags',

+         description=('Change the trust attributes of a CA certificate.  Certificate Authorities typically use "CT,,"'))

+     cacert_edit_parser.add_argument('name', help='The name/nickname of the CA certificate')

+     cacert_edit_parser.add_argument('--flags', required=True,

+         help='The trust flags for the CA certificate')

+     cacert_edit_parser.set_defaults(func=cert_edit)

+ 

+     cacert_del_parser = cacerts_sub.add_parser('del', help='Delete a certificate',

+         description=('Delete a CA certificate from the NSS database'))

+     cacert_del_parser.add_argument('name', help='The name/nickname of the CA certificate')

+     cacert_del_parser.set_defaults(func=cert_del)

+ 

+     cacert_get_parser = cacerts_sub.add_parser('get', help="Get a Certificate Authority's information",

+         description=('Get detailed information about a CA certificate, like trust attributes, expiration dates, Subject and Issuer DN'))

+     cacert_get_parser.add_argument('name', help='The name/nickname of the CA certificate')

+     cacert_get_parser.set_defaults(func=cert_get)

+ 

+     cacert_list_parser = cacerts_sub.add_parser('list', help='List the Certificate Authorities',

+         description=('List the CA certificates in the NSS database'))

+     cacert_list_parser.set_defaults(func=cacert_list)

+ 

+     # RSA management

+     rsa = security_sub.add_parser('rsa', help='Query and manipulate RSA security options')

      rsa_sub = rsa.add_subparsers(help='rsa')

-     rsa_set = _security_generic_set_parser(rsa_sub, RSA_ATTRS_MAP, 'Set RSA security options',

+     _security_generic_set_parser(rsa_sub, RSA_ATTRS_MAP, 'Set RSA security options',

          ('Use this command for setting RSA (private key) related options located in cn=RSA,cn=encryption,cn=config.'

           '\n\nTo enable/disable RSA you can use enable and disable commands instead.'))

-     rsa_get = _security_generic_get_parser(rsa_sub, RSA_ATTRS_MAP, 'Get RSA security options')

-     rsa_toggles = _security_generic_toggle_parsers(rsa_sub, RSA, 'nsSSLActivation', '{} RSA')

+     _security_generic_get_parser(rsa_sub, RSA_ATTRS_MAP, 'Get RSA security options')

+     _security_generic_toggle_parsers(rsa_sub, RSA, 'nsSSLActivation', '{} RSA')

  

+     # Cipher management

      ciphers = security_sub.add_parser('ciphers', help='Manage secure ciphers')

      ciphers_sub = ciphers.add_subparsers(help='ciphers')

  
@@ -226,7 +456,7 @@ 

  

      ciphers_set = ciphers_sub.add_parser('set', help='Set ciphers attribute', description=(

          'Use this command to directly set nsSSL3Ciphers attribute. It is a comma separated list '

-         'of cipher names (prefixed with + or -), optionaly including +all or -all. The attribute '

+         'of cipher names (prefixed with + or -), optionally including +all or -all. The attribute '

          'may optionally be prefixed by keyword default. Please refer to documentation of '

          'the attribute for a more detailed description.'))

      ciphers_set.set_defaults(func=security_ciphers_set)

file modified
+18 -10
@@ -20,9 +20,7 @@ 

  from lib389._constants import *

  from lib389 import Entry

  from lib389._mapped_object import DSLdapObject

- from lib389.dseldif import DSEldif

- from lib389.utils import ensure_bytes, ensure_str

- 

+ from lib389.utils import ensure_bytes, selinux_label_port,  selinux_present

  from lib389.lint import DSCLE0001, DSCLE0002, DSELE0001

  

  class Config(DSLdapObject):
@@ -37,7 +35,7 @@ 

          super(Config, self).__init__(instance=conn)

          self._dn = DN_CONFIG

          # self._instance = conn

-         # self.log = conn.log

+         self.log = conn.log

          config_compare_exclude = [

              'nsslapd-ldapifilepath',

              'nsslapd-accesslog',
@@ -65,6 +63,16 @@ 

      def rdn(self):

          return DN_CONFIG

  

+     def replace(self, key, value):

+         if key.lower() == 'nsslapd-secureport' and selinux_present():

+             # Get old port and remove label

+             old_port = self.get_attr_val_utf8(key)

+             self.log.debug("Removing old port's selinux label...")

+             selinux_label_port(old_port, remove_label=True)

+             self.log.debug("Setting new port's selinux label...")

+             selinux_label_port(value)

+         super(Config, self).replace(key,  value)

+ 

      def _alter_log_enabled(self, service, state):

          if service not in ('access', 'error', 'audit'):

              self._log.error('Attempted to enable invalid log service "%s"' % service)
@@ -245,7 +253,10 @@ 

          :returns: list of str

          """

          val = self.get_attr_val_utf8('nsSSL3Ciphers')

-         return val.split(',') if val else []

+         if val:

+             return val.split(',')

+         else:

+             return ['default']

  

      @ciphers.setter

      def ciphers(self, ciphers):
@@ -370,7 +381,7 @@ 

  

      def _parse_maps(self, maps):

          certmaps = {}

-         cur_map = None

+ 

          for l in maps:

              if l.startswith('certmap'):

                  # Line matches format of: certmap name issuer
@@ -457,10 +468,7 @@ 

      def __init__(self, conn):

          super(LDBMConfig, self).__init__(instance=conn)

          self._dn = DN_CONFIG_LDBM

-         config_compare_exclude = []

+         # config_compare_exclude = []

          self._rdn_attribute = 'cn'

          self._lint_functions = []

          self._protected = True

- 

- 

- 

file modified
+149 -7
@@ -10,9 +10,6 @@ 

  """

  

  import os

- import sys

- import random

- import string

  import re

  import socket

  import time
@@ -24,7 +21,7 @@ 

  from subprocess import check_output

  from lib389.passwd import password_generate

  

- from lib389.utils import ensure_str, ensure_bytes, format_cmd_list

+ from lib389.utils import ensure_str, format_cmd_list

  import uuid

  

  KEYBITS = 4096
@@ -362,8 +359,9 @@ 

          # Now make the lines usable

          cert_values = []

          for line in lines:

-             data = line.split()

-             cert_values.append((data[0], data[1]))

+             if line == '':

+                 continue

+             cert_values.append(re.match(r'^(.+[^\s])[\s]+([^\s]+)$', line.rstrip()).groups())

          return cert_values

  

      def _rsa_cert_key_exists(self, cert_tuple):
@@ -380,7 +378,6 @@ 

          result = ensure_str(check_output(cmd, stderr=subprocess.STDOUT))

  

          lines = result.split('\n')[1:-1]

-         key_list = []

          for line in lines:

              m = re.match('\<(?P<id>.*)\> (?P<type>\w+)\s+(?P<hash>\w+).*:(?P<name>.+)', line)

              if name == m.group('name'):
@@ -712,3 +709,148 @@ 

          crt_der_path = '%s/%s%s.der' % (self._certdb, USER_PREFIX, name)

          return {'ca': ca_path, 'key': key_path, 'crt': crt_path, 'crt_der_path': crt_der_path}

  

+     # Certificate helper functions

+     def del_cert(self,  nickname):

+         """Delete this certificate

+         """

+         cmd = [

+                 '/usr/bin/certutil',

+                 '-D',

+                 '-d', self._certdb,

+                 '-n', nickname,

+                 '-f',

+                 '%s/%s' % (self._certdb, PWD_TXT),

+             ]

+         self.log.debug("del_cert cmd: %s", format_cmd_list(cmd))

+         check_output(cmd, stderr=subprocess.STDOUT)

+ 

+     def edit_cert_trust(self, nickname,  trust_flags):

+         """Edit trust flags

+         """

+ 

+         # validate trust flags

+         flag_sections = trust_flags.split(',')

+         if len(flag_sections) != 3:

+             raise ValueError("Invalid trust flag format")

+ 

+         for section in flag_sections:

+             if len(section) > 6:

+                 raise ValueError("Invalid trust flag format, too many flags in a section")

+ 

+         for c in trust_flags:

+             if c not in ['p', 'P', 'c',  'C', 'T', 'u', ',']:

+                 raise ValueError("Invalid trust flag {}".format(c))

+ 

+         # Modify certificate flags

+         cmd = [

+             '/usr/bin/certutil',

+             '-M',

+             '-d', self._certdb,

+             '-n', nickname,

+             '-t',  trust_flags,

+             '-f',

+             '%s/%s' % (self._certdb, PWD_TXT),

+         ]

+         self.log.debug("edit_cert_trust cmd: %s", format_cmd_list(cmd))

+         check_output(cmd, stderr=subprocess.STDOUT)

+ 

+ 

+     def get_cert_details(self, nickname):

+         """Get the trust flags, subject DN, issuer, and expiration date

+ 

+         return a list:

+             0 - nickname

+             1 - subject

+             2 - issuer

+             3 - expire date

+             4 - trust_flags

+         """

+         all_certs = self._rsa_cert_list()

+         for cert in all_certs:

+             if cert[0] == nickname:

+                 trust_flags = cert[1]

+                 cmd = [

+                     '/usr/bin/certutil',

+                     '-d', self._certdb,

+                     '-n', nickname,

+                     '-L',

+                     '-f',

+                     '%s/%s' % (self._certdb, PWD_TXT),

+                 ]

+                 self.log.debug("get_cert_details cmd: %s", format_cmd_list(cmd))

+ 

+                 # Expiration date

+                 certdetails = check_output(cmd, stderr=subprocess.STDOUT, encoding='utf-8')

+                 end_date_str = certdetails.split("Not After : ")[1].split("\n")[0]

+                 date_format = '%a %b %d %H:%M:%S %Y'

+                 end_date = datetime.strptime(end_date_str, date_format)

+ 

+                 # Subject DN

+                 subject = ""

+                 for line in certdetails.splitlines():

+                     line = line.lstrip()

+                     if line.startswith("Subject: "):

+                         subject = line.split("Subject: ")[1].split("\n")[0]

+                     elif subject != "":

+                         if not line.startswith("Subject Public Key Info:"):

+                             subject += line

+                         else:

+                             # Done, strip off quotes

+                             subject = subject[1:-1]

+                             break

+ 

+                 # Issuer

+                 issuer = ""

+                 for line in certdetails.splitlines():

+                     line = line.lstrip()

+                     if line.startswith("Issuer: "):

+                         issuer = line.split("Issuer: ")[1].split("\n")[0]

+                     elif issuer != "":

+                         if not line.startswith("Validity:"):

+                             issuer += line

+                         else:

+                             issuer = issuer[1:-1]

+                             break

+ 

+                 return ([nickname,  subject, issuer, str(end_date), trust_flags])

+ 

+         # Did not find cert with that name

+         raise ValueError("Certificate '{}' not found in NSS database".format(nickname))

+ 

+ 

+     def list_certs(self, ca=False):

+         all_certs = self._rsa_cert_list()

+         certs = []

+         for cert in all_certs:

+             trust_flags = cert[1]

+             if (ca and "CT" in trust_flags) or (not ca and "CT" not in trust_flags):

+                 certs.append(self.get_cert_details(cert[0]))

+         return certs

+ 

+ 

+     def add_cert(self, nickname, input_file, ca=False):

+         """Add server or CA cert

+         """

+ 

+         # Verify input_file exists

+         if not os.path.exists(input_file):

+             raise ValueError("The certificate file ({}) does not exist".format(input_file))

+ 

+         if ca:

+             trust_flags = "CT,,"

+         else:

+             trust_flags = ",,"

+ 

+         cmd = [

+             '/usr/bin/certutil',

+             '-A',

+             '-d', self._certdb,

+             '-n', nickname,

+             '-t', trust_flags,

+             '-i', input_file,

+             '-a',

+             '-f',

+             '%s/%s' % (self._certdb, PWD_TXT),

+         ]

+         self.log.debug("add_cert cmd: %s", format_cmd_list(cmd))

+         check_output(cmd, stderr=subprocess.STDOUT)

file modified
+4 -4
@@ -234,7 +234,7 @@ 

      """

      Either set or remove an SELinux label(ldap_port_t) for a TCP port

  

-     :param port: The TCP port to be labelled

+     :param port: The TCP port to be labeled

      :type port: str

      :param remove_label: Set True if the port label should be removed

      :type remove_label: boolean
@@ -258,9 +258,10 @@ 

  

      # We only label ports that ARE NOT in the default policy that comes with

      # a RH based system.

+     port = int(port)

      selinux_default_ports = [389, 636, 3268, 3269, 7389]

      if port in selinux_default_ports:

-         log.debug('port %s already in %s, skipping port relabel' % (port, selinux_default_ports))

+         log.debug('port {} already in {}, skipping port relabel'.format(port, selinux_default_ports))

          return

  

      label_set = False
@@ -283,11 +284,10 @@ 

          elif not remove_label:

              # Port belongs to someone else (bad)

              # This is only an issue during setting a label, not removing a label

-             raise ValueError("Port {} was already labelled with: ({})  Please choose a different port number".format(port, policy['type']))

+             raise ValueError("Port {} was already labeled with: ({})  Please choose a different port number".format(port, policy['type']))

  

      if (remove_label and label_set) or (not remove_label and not label_set):

          for i in range(5):

- 

              try:

                  subprocess.check_call(["semanage", "port",

                                         "-d" if remove_label else "-a",

Description:

This updates the CLI and UI to handle a majority of the security configuration. It also adds support for PF dual list selection even though I ended up not using it.

Relates: https://pagure.io/389-ds-base/issue/50325

I've encountered a couple of issues:

Certificate Management -> ... -> Actions -> Delete
Cannot close the dialog with 'No' button nor the cross.
Cipher Preferences -> Cipher Suite -> No Ciphers
The cli command call fails.
Security Configuration -> Security Enabled
Turning to on, then confirming the dialog with "Enable Security" button, does not close the dialog.
Security Configuration - combo boxes
Changing value and saving sometimes :tm: does not issue a cli command call when it probably should. However, what the actual semantics should be is a bit difficult since the values are often applied only on reboot.
Security Configuration - toggle boxes
Changing the state of one and pressing Save Configuration does not issue any CLI command.

And one more general: top menu bar on narrow window does not show menu buttons at all. Also, after widening the window a bit the top menu buttons appear and are wrapped to two lines hiding the top submenu on the Security page.

rebased onto a1ff8943062c545e56c75686fb1fc441890c4e1d

4 years ago

Thanks @mhonek! Everything is fixed except for the sizing issue. It's not related to this ticket - perhaps it can be addressed once we convert everything to reactJS?

It is just commented without any additional comment like TODO, etc.
Do we plan to add it later? Maybe it makes sense to add an issue number in the comment here for tracking purposes...

If you don't need it, I think, we can just remove it from the list

If I go to Certificate Management->CA Certificates and try to Edit Trust Flags it does not update the content and it just says Success.

I understand that editing CA cert Trust flags on the DS instance machine may be not the intended behaviour. But then we should notify the user about it or something...

Another critical issue is:
- If I remove all flags and save - it removes the certificate from the list completely.

At the Certificate Management->Server Certificates we can see the trust flags u,u,u across the Server Cert. It is not editable thing but at least, maybe, we can describe them in the Edit Trust Flags modal window and mark them gray (not editable).

While adding a new certificate in Certificate Management->Server Certificates, it gives an error

Error adding certificate - Command '['/usr/bin/certutil', '-A', '-d', '/etc/dirsrv/slapd-localhost', '-n',
'sdf', '-t', ',,', '-i', '/etc/dirsrv/slapd-localhost/Server-Cert.cr', '-a', '-f', '/etc/dirsrv/slapd-
localhost/pwdfile.txt']' returned non-zero exit status 255.

Can we somehow validate the path first, so if the cert file format is not valid or not found - we send a proper error to UI and CLI?

If I go to Certificate Management->CA Certificates and try to Edit Trust Flags it does not update the content and it just says Success.
I understand that editing CA cert Trust flags on the DS instance machine may be not the intended behaviour. But then we should notify the user about it or something...

Actually what happens is that certutil is not applying the flags as requested. Some flags are contradicting. So you can try and set certain flags but certutil/NSS is overriding some of the flags.

Another critical issue is:
- If I remove all flags and save - it removes the certificate from the list completely.

If you remove the CA flags (CT), the certificate is moved out of the CA cert table, and into the server cert table. The only condition that makes a cert a CA cert is the flags so if you rmeove them it should be moved out of that table.

Maybe I can add a warning if you remove the CT flags that the cert will no longer function as a CA.

At the Certificate Management->Server Certificates we can see the trust flags u,u,u across the Server Cert. It is not editable thing but at least, maybe, we can describe them in the Edit Trust Flags modal window and mark them gray (not editable).

Okay I'll look into this.

Thanks,
Mark

1 new commit added

  • Fixed Simon's issues
4 years ago

@spichugi changes applied please review again

@mreynolds Sure, the sizing issue shall be resolved in a different ticket, just didn't know which. :/

Testing from ecbd1c024:

Security Configuration -> Server Certificate Name
The field's drop-down does not suggest certs with CTu,u,u. Although fairly discouraged, still a valid case. But low priority and postponable if that'd need bigger code changes.
Security Configuration -> Secure Port
Something for lib389 I guess but after setting port 6363 SELinux strikes again after restart rendering ns-slapd failing to start:
Jul 12 01:56:52 b audit[16797]: AVC avc:  denied  { name_bind } for  pid=16797 comm="ns-slapd" src=6363 scontext=system_u:system_r:dirsrv_t:s0 tcontext=system_u:object_r:repository_port_t:s0 tclass=tcp_socket permissive=0
Jul 12 01:56:52 b ns-slapd[16797]: [12/Jul/2019:01:56:52.890720266 +0000] - ERR - createprlistensockets - PR_Bind() on All Interfaces port 6363 failed: Netscape Portable Runtime error -5966 (Access Denied.)
Security Configuration -> Verify Certificate Subject Hostname
Changing and saving does request an CLI command for update.
Cipher Preferences
The three configuration fields do not seem to load the actual configuration after Restart Instance. Probably due to the fact the setting of the ciphers is now including double-quotes:
# sudo dsconf -j ldapi://%2fvar%2frun%2fslapd-b.socket security ciphers get
{'type': 'list', 'items': ['"-all', '+TLS_AES_128_GCM_SHA256', '-TLS_CHACHA20_POLY1305_SHA256"']}
Certificate Management -> CA Certificates
Given we show only Trusted CA certs, I think we should name this section so. And rename the other section to something like Other Certificates since untrusted CA certs end up there as well.

Finally, not related to CLI nor UI, but whatever is set up in sslVersionMin/Max is behaving weirdly for me, not respecting whatever was set in dse.ldif. Could you please quickly check along the way if it works for you as expected? Thanks.

EDIT: PS: Maybe it's really too little but on 1GB RAM machine I very often get OOM-killed dsconfs when first loading instance info, since like 20ish instances pop up simultaneously. We should think about it...

If I remove only one of the flags, it still removes the certificate from the list but showConfirmCAChange is not shown.

@mreynolds Sure, the sizing issue shall be resolved in a different ticket, just didn't know which. :/
Testing from ecbd1c0:

Security Configuration -> Server Certificate Name
The field's drop-down does not suggest certs with CTu,u,u. Although fairly discouraged, still a valid case. But low priority and postponable if that'd need bigger code changes.

What I'm trying to do in the UI is force the correct behavior. A server certificate would not be a CA - although that is technically possible. So the typeahead is only listing what the UI thinks are Server Certs. I'm trying to prevent users from doing the wrong thing and making accidents

Security Configuration -> Secure Port
Something for lib389 I guess but after setting port 6363 SELinux strikes again after restart rendering ns-slapd failing to start:

Jul 12 01:56:52 b audit[16797]: AVC avc: denied { name_bind } for pid=16797 comm="ns-slapd" src=6363 scontext=system_u:system_r:dirsrv_t:s0 tcontext=system_u:object_r:repository_port_t:s0 tclass=tcp_socket permissive=0
Jul 12 01:56:52 b ns-slapd[16797]: [12/Jul/2019:01:56:52.890720266 +0000] - ERR - createprlistensockets - PR_Bind() on All Interfaces port 6363 failed: Netscape Portable Runtime error -5966 (Access Denied.)

Yeah there is a ticket already open for this I believe. I will try and merge it into this PR...

Security Configuration -> Verify Certificate Subject Hostname
Changing and saving does request an CLI command for update.
Cipher Preferences
The three configuration fields do not seem to load the actual configuration after Restart Instance. Probably due to the fact the setting of the ciphers is now including double-quotes:

sudo dsconf -j ldapi://%2fvar%2frun%2fslapd-b.socket security ciphers get

{'type': 'list', 'items': ['"-all', '+TLS_AES_128_GCM_SHA256', '-TLS_CHACHA20_POLY1305_SHA256"']}

Ahh okay I'll look into this...

Certificate Management -> CA Certificates
Given we show only Trusted CA certs, I think we should name this section so. And rename the other section to something like Other Certificates since untrusted CA certs end up there as well.

Finally, not related to CLI nor UI, but whatever is set up in sslVersionMin/Max is behaving weirdly for me, not respecting whatever was set in dse.ldif. Could you please quickly check along the way if it works for you as expected? Thanks.

Yup, but what are you seeing that is weird?

EDIT: PS: Maybe it's really too little but on 1GB RAM machine I very often get OOM-killed dsconfs when first loading instance info, since like 20ish instances pop up simultaneously. We should think about it...

Hmmm, note sure this is something we can "fix", but might need to release note the memory requirements for the UI.

If I remove only one of the flags, it still removes the certificate from the list but showConfirmCAChange is not shown.

As of right now, its expects CT to be a CA cert, if one is missing it thinks its a server certificate. Its a bit inconsistent, I'll look into this...

@mhonek

Finally, not related to CLI nor UI, but whatever is set up in sslVersionMin/Max is behaving weirdly for me, not respecting whatever was set in dse.ldif. Could you please quickly check along the way if it works for you as expected? Thanks.

Looks like the server is adjusted the SSL min and Max: See errors log clip:

[09/Jul/2019:13:10:38.420475077 -0400] - WARN - Security Initialization - SSL alert: nsTLS1 is on, but the version range is lower than "TLS1.2"; Configuring the version range as default min: TLS1.2, max: TLS1.3.
[09/Jul/2019:13:10:38.423102620 -0400] - INFO - Security Initialization - slapd_ssl_init2 - Configured SSL version range: min: TLS1.2, max: TLS1.3
[09/Jul/2019:13:10:38.425555873 -0400] - INFO - Security Initialization - slapd_ssl_init2 - NSS adjusted SSL version range: min: TLS1.2, max: TLS1.3

There might be a bug here since in my dse.,ldif I requested TLS1.2 as the Max, but its using the NSS max. I suspect this is a bug from:

https://pagure.io/389-ds-base/pull-request/50372

I'll look into this...

3 new commits added

  • Fix selinux port labeling, and add 'saving' spinners
  • Fix npm vulnerabilities
  • Fix issue with listing certs with spaces in the name
4 years ago

Changes applied, I think I fixed everything and did not break anything. Please review...

rebased onto bc686714bd3996f70e2ee442b83ad118d7bd780b

4 years ago

rebased onto 64281a0b22562588382631c3d07891965d9a8622

4 years ago

LGTM
but I'll give @mhonek to check and set the ack

I would probably use a regex to make it more transparent (but not a big deal):

>>> re.match(r'^(.+[^\s])[\s]+([^\s]+)$', 'Self Signed CA                                               CTu,u,u').groups()
('Self Signed CA', 'CTu,u,u')

Testing 293d926a0 the UI looks to work as expected. ACK! Thanks!

rebased onto e973578f0394badb6e210bfce91a9e59fdad44ae

4 years ago

rebased onto a77abdb

4 years ago

Pull-Request has been merged by mreynolds

4 years ago

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/3538

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago