Add security settings page

Adds ability to enable/disable:
- SSH protocol
- IPMI protocol

Signed-off-by: Dixsie Wolmers <dixsie@ibm.com>
Change-Id: I2430a46343dd8756ef75fcc3cb068df8d51dd415
diff --git a/src/components/AppNavigation/AppNavigationMixin.js b/src/components/AppNavigation/AppNavigationMixin.js
index f0691a5..b163d75 100644
--- a/src/components/AppNavigation/AppNavigationMixin.js
+++ b/src/components/AppNavigation/AppNavigationMixin.js
@@ -108,6 +108,11 @@
               route: '/configuration/network-settings',
             },
             {
+              id: 'security-settings',
+              label: this.$t('appNavigation.securitySettings'),
+              route: '/configuration/security-settings',
+            },
+            {
               id: 'snmp-settings',
               label: this.$t('appNavigation.snmpSettings'),
               route: '/snmp-settings',
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index fb0d45a..0e28de5 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -110,6 +110,7 @@
     "overview": "@:appPageTitle.overview",
     "primaryNavigation": "Primary navigation",
     "rebootBmc": "@:appPageTitle.rebootBmc",
+    "securitySettings": "@:appPageTitle.securitySettings",
     "sensors": "@:appPageTitle.sensors",
     "serialOverLan": "SOL console",
     "serverLed": "@:appPageTitle.serverLed",
@@ -134,6 +135,7 @@
     "pageNotFound": "Page not found",
     "profileSettings": "Profile settings",
     "rebootBmc": "Reboot BMC",
+    "securitySettings": "Security settings",
     "sensors": "Sensors",
     "serialOverLan": "Serial over LAN (SOL) console",
     "serverLed": "Server LED",
@@ -546,6 +548,33 @@
       "successRebootStart": "Rebooting BMC."
     }
   },
+  "pageSecuritySettings": {
+    "ipmi": "Network IPMI (out-of-band IPMI)",
+    "ipmiDescription": "Allow remote management of the platform via IPMI. Tools such as ipmitool require this setting to be enabled.",
+    "networkServices": "Network services",
+    "ssh": "SSH port 22 (BMC shell)",
+    "sshDescription": "SSH access to the BMC's command shell. Disabling this will disable users' ability to connect BMC shell via SSH.",
+    "modal": {
+      "disableMessage": {
+        "ipmi": "Are you sure you want to disable @:pageSecuritySettings.ipmi?",
+        "ssh": "Are you sure you want to disable @:pageSecuritySettings.ssh?"
+      },
+      "enableMessage": {
+        "ipmi": "Are you sure you want to enable @:pageSecuritySettings.ipmi?",
+        "ssh": "Are you sure you want to enable @:pageSecuritySettings.ssh?"
+      }
+    },
+    "toast": {
+      "errorIpmiDisabled": "Error disabling IPMI security setting.",
+      "errorIpmiEnabled":"Error enabling IPMI security setting.",
+      "errorSshDisabled":"Error disabling SSH security setting.",
+      "errorSshEnabled": "Error enabling SSH security setting.",
+      "successIpmiDisabled": "Successfully disabled IPMI security setting.",
+      "successIpmiEnabled": "Successfully enabled IPMI security setting.",
+      "successSshDisabled": "Successfully disabled SSH security setting.",
+      "successSshEnabled": "Successfully enabled SSH security setting."
+    }
+  },
   "pageSensors": {
     "exportFilePrefix": "sensors_",
     "searchForSensors": "Search for sensors",
diff --git a/src/router/routes.js b/src/router/routes.js
index 3be1a1e..a82833a 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -17,6 +17,7 @@
 import PageNotFound from '@/views/PageNotFound';
 import ProfileSettings from '@/views/ProfileSettings';
 import RebootBmc from '@/views/Control/RebootBmc';
+import SecuritySettings from '@/views/Configuration/SecuritySettings';
 import Sensors from '@/views/Health/Sensors';
 import SerialOverLan from '@/views/Control/SerialOverLan';
 import SerialOverLanConsole from '@/views/Control/SerialOverLan/SerialOverLanConsole';
@@ -163,6 +164,14 @@
         },
       },
       {
+        path: '/configuration/security-settings',
+        name: 'security-settings',
+        component: SecuritySettings,
+        meta: {
+          title: i18n.t('appPageTitle.securitySettings'),
+        },
+      },
+      {
         path: '/control/kvm',
         name: 'kvm',
         component: Kvm,
diff --git a/src/store/index.js b/src/store/index.js
index e6153b1..b4a77d8 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -21,6 +21,7 @@
 import ChassisStore from './modules/Health/ChassisStore';
 import BmcStore from './modules/Health/BmcStore';
 import ProcessorStore from './modules/Health/ProcessorStore';
+import SecuritySettingsStore from './modules/Configuration/SecuritySettingsStore';
 
 import WebSocketPlugin from './plugins/WebSocketPlugin';
 import DateTimeStore from './modules/Configuration/DateTimeSettingsStore';
@@ -55,6 +56,7 @@
     bmc: BmcStore,
     processors: ProcessorStore,
     virtualMedia: VirtualMediaStore,
+    securitySettings: SecuritySettingsStore,
   },
   plugins: [WebSocketPlugin],
 });
diff --git a/src/store/modules/Configuration/SecuritySettingsStore.js b/src/store/modules/Configuration/SecuritySettingsStore.js
new file mode 100644
index 0000000..5a88542
--- /dev/null
+++ b/src/store/modules/Configuration/SecuritySettingsStore.js
@@ -0,0 +1,95 @@
+import api from '@/store/api';
+import i18n from '@/i18n';
+
+const SecuritySettingsStore = {
+  namespaced: true,
+  state: {
+    sshProtocolEnabled: false,
+    ipmiProtocolEnabled: false,
+  },
+  getters: {
+    sshProtocolEnabled: (state) => state.sshProtocolEnabled,
+    ipmiProtocolEnabled: (state) => state.ipmiProtocolEnabled,
+  },
+  mutations: {
+    setSshProtocolEnabled: (state, sshProtocolEnabled) =>
+      (state.sshProtocolEnabled = sshProtocolEnabled),
+    setIpmiProtocolEnabled: (state, ipmiProtocolEnabled) =>
+      (state.ipmiProtocolEnabled = ipmiProtocolEnabled),
+  },
+  actions: {
+    async getNetworkProtocolStatus({ commit }) {
+      return await api
+        .get('/redfish/v1/Managers/bmc/NetworkProtocol')
+        .then((response) => {
+          const sshProtocol = response.data.SSH.ProtocolEnabled;
+          const ipmiProtocol = response.data.IPMI.ProtocolEnabled;
+          commit('setSshProtocolEnabled', sshProtocol);
+          commit('setIpmiProtocolEnabled', ipmiProtocol);
+        })
+        .catch((error) => console.log(error));
+    },
+    async saveIpmiProtocolState({ commit }, protocolEnabled) {
+      commit('setIpmiProtocolEnabled', protocolEnabled);
+      const ipmi = {
+        IPMI: {
+          ProtocolEnabled: protocolEnabled,
+        },
+      };
+      return await api
+        .patch('/redfish/v1/Managers/bmc/NetworkProtocol', ipmi)
+        .then(() => {
+          if (protocolEnabled) {
+            return i18n.t('pageSecuritySettings.toast.successIpmiEnabled');
+          } else {
+            return i18n.t('pageSecuritySettings.toast.successIpmiDisabled');
+          }
+        })
+        .catch((error) => {
+          console.log(error);
+          commit('setIpmiProtocolEnabled', !protocolEnabled);
+          if (protocolEnabled) {
+            throw new Error(
+              i18n.t('pageSecuritySettings.toast.errorIpmiEnabled')
+            );
+          } else {
+            throw new Error(
+              i18n.t('pageSecuritySettings.toast.errorIpmiDisabled')
+            );
+          }
+        });
+    },
+    async saveSshProtocolState({ commit }, protocolEnabled) {
+      commit('setSshProtocolEnabled', protocolEnabled);
+      const ssh = {
+        SSH: {
+          ProtocolEnabled: protocolEnabled,
+        },
+      };
+      return await api
+        .patch('/redfish/v1/Managers/bmc/NetworkProtocol', ssh)
+        .then(() => {
+          if (protocolEnabled) {
+            return i18n.t('pageSecuritySettings.toast.successSshEnabled');
+          } else {
+            return i18n.t('pageSecuritySettings.toast.successSshDisabled');
+          }
+        })
+        .catch((error) => {
+          console.log(error);
+          commit('setSshProtocolEnabled', !protocolEnabled);
+          if (protocolEnabled) {
+            throw new Error(
+              i18n.t('pageSecuritySettings.toast.errorSshEnabled')
+            );
+          } else {
+            throw new Error(
+              i18n.t('pageSecuritySettings.toast.errorSshDisabled')
+            );
+          }
+        });
+    },
+  },
+};
+
+export default SecuritySettingsStore;
diff --git a/src/views/Configuration/SecuritySettings/SecuritySettings.vue b/src/views/Configuration/SecuritySettings/SecuritySettings.vue
new file mode 100644
index 0000000..d665a7f
--- /dev/null
+++ b/src/views/Configuration/SecuritySettings/SecuritySettings.vue
@@ -0,0 +1,125 @@
+<template>
+  <b-container fluid="xl">
+    <page-title />
+    <b-row>
+      <b-col lg="8" md="8">
+        <page-section
+          :section-title="$t('pageSecuritySettings.networkServices')"
+        >
+          <b-row class="setting-section">
+            <b-col class="d-flex align-items-center justify-content-between">
+              <dl class="mr-3 w-75">
+                <dt>{{ $t('pageSecuritySettings.ssh') }}</dt>
+                <dd>
+                  {{ $t('pageSecuritySettings.sshDescription') }}
+                </dd>
+              </dl>
+              <b-form-checkbox
+                id="sshSwitch"
+                v-model="sshProtocolState"
+                data-test-id="securitySettings-checkbox-switchSshProtocol"
+                switch
+                @change="changeSshProtocolState"
+              >
+                <span class="sr-only">
+                  {{ $t('pageSecuritySettings.ssh') }}
+                </span>
+                <span v-if="sshProtocolState">
+                  {{ $t('global.status.enabled') }}
+                </span>
+                <span v-else>{{ $t('global.status.disabled') }}</span>
+              </b-form-checkbox>
+            </b-col>
+          </b-row>
+          <b-row class="setting-section">
+            <b-col class="d-flex align-items-center justify-content-between">
+              <dl class="mt-3 mr-3 w-75">
+                <dt>{{ $t('pageSecuritySettings.ipmi') }}</dt>
+                <dd>
+                  {{ $t('pageSecuritySettings.ipmiDescription') }}
+                </dd>
+              </dl>
+              <b-form-checkbox
+                id="ipmiSwitch"
+                v-model="ipmiProtocolState"
+                data-test-id="securitySettings-checkbox-switchIpmiProtocol"
+                switch
+                @change="changeIpmiProtocolState"
+              >
+                <span class="sr-only">
+                  {{ $t('pageSecuritySettings.ipmi') }}
+                </span>
+                <span v-if="ipmiProtocolState">
+                  {{ $t('global.status.enabled') }}
+                </span>
+                <span v-else>{{ $t('global.status.disabled') }}</span>
+              </b-form-checkbox>
+            </b-col>
+          </b-row>
+        </page-section>
+      </b-col>
+    </b-row>
+  </b-container>
+</template>
+
+<script>
+import PageSection from '@/components/Global/PageSection';
+import PageTitle from '@/components/Global/PageTitle';
+
+import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+
+export default {
+  name: 'SecuritySettings',
+  components: { PageTitle, PageSection },
+  mixins: [LoadingBarMixin, BVToastMixin],
+  beforeRouteLeave(to, from, next) {
+    this.hideLoader();
+    next();
+  },
+  computed: {
+    sshProtocolState: {
+      get() {
+        return this.$store.getters['securitySettings/sshProtocolEnabled'];
+      },
+      set(newValue) {
+        return newValue;
+      },
+    },
+    ipmiProtocolState: {
+      get() {
+        return this.$store.getters['securitySettings/ipmiProtocolEnabled'];
+      },
+      set(newValue) {
+        return newValue;
+      },
+    },
+  },
+  created() {
+    this.startLoader();
+    this.$store
+      .dispatch('securitySettings/getNetworkProtocolStatus')
+      .finally(() => this.endLoader());
+  },
+  methods: {
+    changeIpmiProtocolState(state) {
+      this.$store
+        .dispatch('securitySettings/saveIpmiProtocolState', state)
+        .then((message) => this.successToast(message))
+        .catch(({ message }) => this.errorToast(message));
+    },
+    changeSshProtocolState(state) {
+      this.$store
+        .dispatch('securitySettings/saveSshProtocolState', state)
+        .then((message) => this.successToast(message))
+        .catch(({ message }) => this.errorToast(message));
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.setting-section {
+  border-bottom: 1px solid gray('300');
+}
+</style>
diff --git a/src/views/Configuration/SecuritySettings/index.js b/src/views/Configuration/SecuritySettings/index.js
new file mode 100644
index 0000000..5ec2b61
--- /dev/null
+++ b/src/views/Configuration/SecuritySettings/index.js
@@ -0,0 +1,2 @@
+import SecuritySettings from './SecuritySettings.vue';
+export default SecuritySettings;