LDAP configuration and user groups

Adds LDAP page and ability to add and change configuration settings.
Adds ability to add, remove and edit user groups for LDAP.

Resolves openbmc/phosphor-webui#38
Resolves openbmc/phosphor-webui#39

Tested: Loaded on to a witherspoon and able to add initial LDAP config
        as well us update the configuration and role groups. Appropriate messages displayed
        to user when required fields are missing or in the incorrect format.

Change-Id: If8a21f3f9d9334415ead73472e90b2a0823bf9ea
Signed-off-by: beccabroek <beccabroek@gmail.com>
Signed-off-by: Dixsie Wolmers <dixsiew@gmail.com>
Signed-off-by: Derick Montague <derick.montague@ibm.com>
diff --git a/app/configuration/controllers/ldap-controller.html b/app/configuration/controllers/ldap-controller.html
new file mode 100644
index 0000000..daace8e
--- /dev/null
+++ b/app/configuration/controllers/ldap-controller.html
@@ -0,0 +1,148 @@
+<loader loading="loading"></loader>
+<div class="ldap" id="configuration-ldap">
+  <div class="row column">
+    <h1>LDAP role group settings</h1>
+  </div>
+  <div class="row column">
+    <p>Configure LDAP settings and manage role groups.</p>
+  </div>
+  <div class="row column">
+    <h2 class="subhead">
+      Settings
+    </h2>
+  </div>
+  <div class="row column">
+    <label class="control-check ldap__control-check">
+      <input type="checkbox" id="enable-ldap-checkbox"
+        ng-change="updateServiceEnabled(); ldap__configuration.$setUntouched()"
+        ng-model="ldapProperties.ServiceEnabled" />
+      <span class="control__indicator"></span>
+      <span class="control__label">
+        <strong>Enable LDAP authentication</strong> <br>
+        LDAP authentication must be enabled to modify role groups.
+      </span>
+    </label>
+  </div>
+  <div class="row column">
+    <form id="ldap__configuration" name="ldap__configuration" ng-class="{'submitted': submitted}"
+      class="ldap__configuration" novalidate>
+      <fieldset ng-disabled="!ldapProperties.ServiceEnabled">
+        <div class="ldap__configure-settings row column">
+          <div class="large-3 column ldap__ssl-column">
+            <label class="control-check" ng-class="{'disabled' : certificates.length < 1}">
+              <input id="secure-ldap-ssl" type="checkbox" ng-model="ldapProperties.useSSL"
+                ng-checked="ldapProperties.useSSL" ng-disabled="certificates.length < 1" />
+              <span class="control__indicator"></span>
+              <span class="control__label">Secure LDAP using SSL</span>
+            </label>
+            <div>
+              <div class="ldap__certificate-info" ng-if="ldapProperties.ServiceEnabled">
+                <p>Client certificate valid until:</p>
+                <small>
+                  {{clientCertificateExpires ? (clientCertificateExpires | localeDate) : 'none available'}}</small>
+              </div>
+            </div>
+            <div class="ldap__certificate-info" ng-if="data.ValidNotAfter='' || !ldapProperties.ServiceEnabled">
+              <span>SSL certificates must be uploaded to secure LDAP using SSL.</span>
+            </div>
+            <div class="ldap__certificate-info">
+              <a href="#/configuration/certificate">Go to SSL certificates</a>
+            </div>
+          </div>
+          <div class="large-9 columns ldap__server-info">
+            <div class="column service-type-column">
+              <fieldset class="ldap__server-info-service-type">
+                <legend class="content-label">Service Type</legend>
+                <label class="control-radio control__radio__label" for="open-ldap">Open LDAP
+                  <input type="radio" name="service_enabled_type" id="open-ldap" value="ldap"
+                    ng-checked="ldapProperties.LDAPServiceEnabled"
+                    ng-change="ldapProperties.EnabledServiceUpdated = true" ng-model="ldapProperties.EnabledServiceType"
+                    required />
+                  <span class="control__indicator control__indicator-on control__indicator-service-type"></span>
+                </label>
+                <label class="control-radio control__radio__label" for="active-directory">Active directory
+                  <input type="radio" name="service_enabled_type" id="active-directory"
+                    ng-change="ldapProperties.EnabledServiceUpdated = true" value="ad"
+                    ng-checked="ldapProperties.ADServiceEnabled" ng-model="ldapProperties.EnabledServiceType"
+                    required />
+                  <span class="control__indicator control__indicator-on control__indicator-service-type"></span>
+                </label>
+              </fieldset>
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__uri">Server uri</label>
+              <input id="ldap__uri" name="ldap__uri" type="text"
+                ng-change="ldapProperties.ServiceAddressesUpdated = true" ng-model="ldapProperties.ServiceAddresses[0]"
+                required />
+              <div ng-messages="ldap__configuration.ldap__uri.$error" class="form-error"
+                ng-class="{'visible' : ldap__configuration.ldap__uri.$touched || submitted}">
+                <p ng-message="required">Field is required</p>
+              </div>
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__bind__dn">Bind DN</label>
+              <input id="ldap__bind__dn" name="ldap__bind__dn" type="text"
+                ng-change="ldapProperties.UsernameUpdated = true" ng-model="ldapProperties.Username" required />
+              <div ng-messages="ldap__configuration.ldap__bind__dn.$error" class="form-error"
+                ng-class="{'visible' : ldap__configuration.ldap__bind__dn.$touched || submitted}">
+                <p ng-message="required">Field is required</p>
+              </div>
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__bind_pw">Bind password</label>
+              <input id="ldap__bind_pw" type="{{showpassword ? 'text' : 'password'}}" name="ldap__bind_pw"
+                ng-change="ldapProperties.PasswordUpdated = true" autocomplete="off" ng-model="ldapProperties.Password"
+                required />
+              <button ng-model="showpassword" ng-class="{'disabled' : !ldap__configuration.$valid}"
+                ng-click="togglePassword = !togglePassword; showpassword = !showpassword;" class="password-toggle">
+                <span ng-hide="togglePassword">Show</span>
+                <span ng-show="togglePassword">Hide</span>
+              </button>
+              <div ng-messages="ldap__configuration.ldap__bind_pw.$error" class="form-error"
+                ng-class="{'visible' : ldap__configuration.ldap__bind_pw.$touched || submitted}">
+                <p ng-message="required">Field is required</p>
+              </div>
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__base__dn">Base DN</label>
+              <input id="ldap__base__dn" name="ldap__base__dn" type="text"
+                ng-change="ldapProperties.BaseDistinguishedNamesUpdated = true"
+                ng-model="ldapProperties.BaseDistinguishedNames[0]" required />
+              <div ng-messages="ldap__configuration.ldap__base__dn.$error" class="form-error"
+                ng-class="{'visible' : ldap__configuration.ldap__base__dn.$touched || submitted}">
+                <p ng-message="required">Field is required</p>
+              </div>
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__user_attribute">User id attribute (optional)</label>
+              <input id="ldap__user_attribute" name="ldap__user_attribute" type="text"
+                ng-change="ldapProperties.UsernameAttributeUpdated = true" ng-model="ldapProperties.UsernameAttribute"
+                class="ldap__optional-field" />
+            </div>
+            <div class="medium-6 large-4 columns">
+              <label for="ldap__group_attribute">Group id attribute (optional)</label>
+              <input id="ldap__group_attribute" name="ldap__group_attribute" type="text"
+                ng-change="ldapProperties.GroupsAttributeUpdated = true" ng-model="ldapProperties.GroupsAttribute"
+                class="ldap__optional-field" />
+            </div>
+            <div class="column ldap__configuration-buttons">
+              <button type="button" class="btn btn-primary" ng-disabled="!ldap__configuration.$valid"
+                ng-click="$parent.submitted=true; ldap__configuration.$valid && saveLdapSettings(); ldap__configuration.$setUntouched()">Save</button>
+              <button type="button" class="btn btn-secondary"
+                ng-click="loadLdap(); ldap__configuration.$setUntouched()">Reset</button>
+            </div>
+      </fieldset>
+    </form>
+  </div>
+</div>
+<div class="ldap-groups row column">
+  <h2 class="small-12 subhead">
+    Role groups
+  </h2>
+  <div class="row column">
+    <div class="small-12">
+      <ldap-user-roles role-groups="roleGroups" role-group-type="roleGroupType" enabled="ldapProperties.ServiceEnabled">
+      </ldap-user-roles>
+    </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/app/configuration/controllers/ldap-controller.js b/app/configuration/controllers/ldap-controller.js
new file mode 100644
index 0000000..129e3db
--- /dev/null
+++ b/app/configuration/controllers/ldap-controller.js
@@ -0,0 +1,224 @@
+/**
+ * Controller for LDAP
+ *
+ * @module app/configuration
+ * @exports ldapController
+ * @name ldapController
+ */
+
+window.angular && (function(angular) {
+  'use strict';
+
+  angular.module('app.configuration').controller('ldapController', [
+    '$scope', 'APIUtils', '$q', 'toastService',
+    function($scope, APIUtils, $q, toastService) {
+      $scope.loading = false;
+      $scope.isSecure = false;
+      $scope.ldapProperties = {};
+      $scope.originalProperties = {};
+      $scope.submitted = false;
+      $scope.roleGroups = [];
+      $scope.roleGroupType = '';
+      $scope.clientCertificateExpires = '';
+
+      $scope.$on('$viewContentLoaded', function() {
+        $scope.loadLdap();
+      });
+
+      $scope.loadLdap = function() {
+        $scope.loading = true;
+        $scope.submitted = false;
+        var getLdapProperties =
+            APIUtils.getAllUserAccountProperties()
+                .then(function(data) {
+                  $scope.ldapProperties = {
+                    'ServiceEnabled': data.LDAP.ServiceEnabled ?
+                        data.LDAP.ServiceEnabled :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.ServiceEnabled :
+                        false,
+                    'LDAPServiceEnabled': data.LDAP.ServiceEnabled,
+                    'ADServiceEnabled': data.ActiveDirectory.ServiceEnabled,
+                    'EnabledServiceType': data.LDAP.ServiceEnabled ?
+                        'ldap' :
+                        data.ActiveDirectory.ServiceEnabled ? 'ad' : '',
+                    'ServiceAddresses': data.LDAP.ServiceEnabled ?
+                        data.LDAP.ServiceAddresses :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.ServiceAddresses :
+                        [],
+                    'useSSL': $scope.isSSL(
+                        data.LDAP.ServiceEnabled ?
+                            data.LDAP.ServiceAddresses[0] :
+                            data.ActiveDirectory.ServiceAddresses[0]),
+                    'Username': data.LDAP.ServiceEnabled ?
+                        data.LDAP.Authentication.Username :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.Authentication.Username :
+                        '',
+                    'BaseDistinguishedNames': data.LDAP.ServiceEnabled ?
+                        data.LDAP.LDAPService.SearchSettings
+                            .BaseDistinguishedNames :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.LDAPService.SearchSettings
+                                .BaseDistinguishedNames :
+                        [],
+                    'GroupsAttribute': data.LDAP.ServiceEnabled ?
+                        data.LDAP.LDAPService.SearchSettings.GroupsAttribute :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.LDAPService.SearchSettings
+                                .GroupsAttribute :
+                        '',
+                    'UsernameAttribute': data.LDAP.ServiceEnabled ?
+                        data.LDAP.LDAPService.SearchSettings.UsernameAttribute :
+                        data.ActiveDirectory.ServiceEnabled ?
+                        data.ActiveDirectory.LDAPService.SearchSettings
+                                .UsernameAttribute :
+                        '',
+                    'AuthenticationType': data.LDAP.ServiceEnabled ?
+                        data.LDAP.Authentication.AuthenticationType :
+                        data.ActiveDirectory.Authentication.AuthenticationType,
+                  };
+
+                  $scope.roleGroupType =
+                      $scope.ldapProperties.EnabledServiceType;
+
+                  if ($scope.ldapProperties.ServiceEnabled) {
+                    if ($scope.ldapProperties.LDAPServiceEnabled) {
+                      $scope.roleGroups = data.LDAP.RemoteRoleMapping;
+                    } else if ($scope.ldapProperties.ADServiceEnabled) {
+                      $scope.roleGroups =
+                          data.ActiveDirectory.RemoteRoleMapping;
+                    }
+                  }
+                })
+                .catch(function(error) {
+                  console.log(JSON.stringify(error));
+                });
+        var getClientCertificate =
+            APIUtils
+                .getCertificate('/redfish/v1/AccountService/LDAP/Certificates')
+                .then(function(data) {
+                  if (data.Members) {
+                    var certificate = data.Members[0];
+                    APIUtils.getCertificate(certificate['@odata.id'])
+                        .then(
+                            function(data) {
+                              $scope.clientCertificateExpires =
+                                  data.ValidNotAfter;
+                            },
+                            function(error) {
+                              console.log(JSON.stringify(error));
+                            })
+                  }
+                })
+                .catch(function(error) {
+                  console.log(JSON.stringify(error));
+                });
+
+        var promises = [getLdapProperties, getClientCertificate];
+        $q.all(promises).finally(function() {
+          $scope.loading = false;
+        });
+      };
+
+      $scope.saveLdapSettings = function() {
+        for (var i in $scope.ldapProperties.ServiceAddresses) {
+          if ($scope.ldapProperties.useSSL !==
+              $scope.isSSL($scope.ldapProperties.ServiceAddresses[i])) {
+            toastService.error(
+                'Server URI ' + $scope.ldapProperties.ServiceAddresses[i] +
+                ' must begin with ' +
+                ($scope.ldapProperties.useSSL ? 'ldaps:// ' : 'ldap:// ') +
+                'when SSL is ' +
+                ($scope.ldapProperties.useSSL ? 'configured. ' :
+                                                'not configured.'));
+          }
+        }
+
+        // Default LDAP and AD Attributes
+        let LDAP = {};
+
+        let ActiveDirectory = {};
+
+        // Data to pass to request
+        let data = {};
+        data.LDAP = LDAP;
+        data.ActiveDirectory = ActiveDirectory;
+
+        // Values to update the service type object
+        let Authentication = {};
+        Authentication.Username = $scope.ldapProperties.Username;
+        Authentication.Password = $scope.ldapProperties.Password;
+        Authentication.AuthenticationType =
+            $scope.ldapProperties.AuthenticationType;
+
+        let LDAPService = {};
+        LDAPService.SearchSettings = {};
+        LDAPService.SearchSettings.BaseDistinguishedNames =
+            $scope.ldapProperties.BaseDistinguishedNames;
+        LDAPService.SearchSettings.GroupsAttribute =
+            $scope.ldapProperties.GroupsAttribute;
+        LDAPService.SearchSettings.UsernameAttribute =
+            $scope.ldapProperties.UsernameAttribute;
+
+        let ServiceAddresses = $scope.ldapProperties.ServiceAddresses;
+        if ($scope.ldapProperties.EnabledServiceType == 'ldap') {
+          ActiveDirectory.ServiceEnabled = false;
+          LDAP.ServiceEnabled = true;
+          LDAP.Authentication = Authentication;
+          LDAP.LDAPService = LDAPService;
+          LDAP.ServiceAddresses = ServiceAddresses;
+        } else if ($scope.ldapProperties.EnabledServiceType == 'ad') {
+          ActiveDirectory.ServiceEnabled = true;
+          LDAP.ServiceEnabled = false;
+          ActiveDirectory.Authentication = Authentication;
+          ActiveDirectory.LDAPService = LDAPService;
+          ActiveDirectory.ServiceAddresses = ServiceAddresses;
+        }
+
+        APIUtils.saveLdapProperties(data).then(
+            function(response) {
+              if (!response.data.hasOwnProperty('error')) {
+                toastService.success('Successfully updated LDAP settings.');
+                $scope.loadLdap();
+              } else {
+                toastService.error('Unable to update LDAP settings.');
+                console.log(JSON.stringify(response.data.error.message));
+              }
+            },
+            function(error) {
+              toastService.error('Unable to update LDAP settings.');
+              console.log(JSON.stringify(error));
+            });
+      };
+
+      $scope.isSSL = function(uri) {
+        return uri.startsWith('ldaps://');
+      };
+      $scope.updateServiceEnabled = function() {
+        if (!$scope.ldapProperties.ServiceEnabled) {
+          $scope.ldapProperties.EnabledServiceType = '';
+          let data = {};
+          let LDAP = {};
+          data.LDAP = LDAP;
+          LDAP.ServiceEnabled = false;
+          let ActiveDirectory = {};
+          data.ActiveDirectory = ActiveDirectory;
+          ActiveDirectory.ServiceEnabled = false;
+
+          APIUtils.saveLdapProperties(data).then(
+              function(response) {
+                toastService.success('Successfully disabled LDAP.');
+                $scope.roleGroups = [];
+                $scope.loadLdap();
+              },
+              function(error) {
+                toastService.error('Unable to disable LDAP.');
+                console.log(JSON.stringify(error));
+              });
+        }
+      }
+    }
+  ]);
+})(angular);