You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ha...@apache.org on 2022/11/24 03:43:41 UTC

[cloudstack] branch 2FA updated (025744d7b04 -> 7bbad688ef7)

This is an automated email from the ASF dual-hosted git repository.

harikrishna pushed a change to branch 2FA
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


    from 025744d7b04 Added success messages for UI operations
     new 1fbf3531271 Some improvements of messages in UI
     new 8ac23fed7f6 Changes in verification page and 2FA enable setting
     new 7bbad688ef7 Adding setup 2FA at login page

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../cloudstack/api/response/LoginCmdResponse.java  |  12 ++
 server/src/main/java/com/cloud/api/ApiServer.java  |   4 +
 .../api/auth/APIAuthenticationManagerImpl.java     |  11 +-
 .../main/java/com/cloud/user/AccountManager.java   |  16 +-
 .../java/com/cloud/user/AccountManagerImpl.java    |  16 +-
 ui/public/locales/en.json                          |  22 +--
 ui/src/config/router.js                            |   6 +-
 ui/src/config/section/user.js                      |   2 +-
 ui/src/permission.js                               |   4 +-
 ui/src/store/getters.js                            |   1 +
 ui/src/store/modules/user.js                       |   8 +-
 ui/src/views/auth/Login.vue                        |   4 +-
 ui/src/views/dashboard/Dashboard.vue               |   4 +-
 ui/src/views/dashboard/TwoFa.vue                   | 173 ---------------------
 ui/src/views/iam/RegisterTwoFactorAuth.vue         | 171 ++++++++++++--------
 16 files changed, 176 insertions(+), 279 deletions(-)
 delete mode 100644 ui/src/views/dashboard/TwoFa.vue


[cloudstack] 01/03: Some improvements of messages in UI

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

harikrishna pushed a commit to branch 2FA
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 1fbf35312717b8e72e5f6626bfe8e30095581e81
Author: Harikrishna Patnala <ha...@gmail.com>
AuthorDate: Thu Nov 24 00:14:56 2022 +0530

    Some improvements of messages in UI
---
 ui/public/locales/en.json                  |  21 ++--
 ui/src/config/section/user.js              |   2 +-
 ui/src/views/iam/RegisterTwoFactorAuth.vue | 171 +++++++++++++++++------------
 3 files changed, 115 insertions(+), 79 deletions(-)

diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 3031569654e..7f085b22490 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -137,8 +137,7 @@
 "label.action.reboot.systemvm": "Reboot system VM",
 "label.action.recover.volume": "Recover volume",
 "label.action.recurring.snapshot": "Recurring snapshots",
-"label.action.register.2FA.user.auth": "Register user Two Factor Authentication",
-"label.action.disable.2FA.user.auth": "Disable user Two Factor Authentication",
+"label.action.disable.2FA.user.auth": "Disable User Two Factor Authentication",
 "label.action.register.iso": "Register ISO",
 "label.action.register.template": "Register template from URL",
 "label.action.release.ip": "Release IP",
@@ -153,6 +152,7 @@
 "label.action.router.health.checks": "Get health checks result",
 "label.action.run.diagnostics": "Run diagnostics",
 "label.action.secure.host": "Provision host security keys",
+"label.action.setup.2FA.user.auth": "Setup User Two Factor Authentication",
 "label.action.start.instance": "Start instance",
 "label.action.start.router": "Start router",
 "label.action.start.systemvm": "Start system VM",
@@ -678,7 +678,8 @@
 "label.endipv6": "IPv6 end IP",
 "label.endpoint": "Endpoint",
 "label.endport": "End port",
-"label.enter.code": "Enter authentication code to verify",
+"label.enter.code": "Enter 2FA code to verify",
+"label.enter.static.pin": "Enter static pin to verify",
 "label.enter.token": "Enter token",
 "label.error": "Error",
 "label.error.caught": "Error caught",
@@ -1738,7 +1739,8 @@
 "label.transportzoneuuid": "Transport zone UUID",
 "label.try.again": "Try again",
 "label.tuesday": "Tuesday",
-"label.two.factor.secret": "Your Two-factor secret",
+"label.two.factor.authentication.secret.key": "Your Two factor authentication secret key",
+"label.two.factor.authentication.static.pin": "Your Two factor authentication static pin",
 "label.two.factor.authentication": "Two Factor Authentication",
 "label.type": "Type",
 "label.type.id": "Type ID",
@@ -1947,7 +1949,7 @@
 "message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the instance. There may be backups associated with the instance which will not be deleted.",
 "message.action.destroy.systemvm": "Please confirm that you want to destroy the System VM.",
 "message.action.destroy.volume": "Please confirm that you want to destroy the volume.",
-"message.action.disable.2FA.user.auth": "Please confirm that you want to disable user Two factor authentication.",
+"message.action.disable.2FA.user.auth": "Please confirm that you want to disable user two factor authentication.",
 "message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
 "message.action.disable.physical.network": "Please confirm that you want to disable this physical network.",
 "message.action.disable.pod": "Please confirm that you want to disable this pod.",
@@ -2586,10 +2588,11 @@
 "message.template.type.change.warning": "WARNING: Changing the template type to SYSTEM will disable further changes to the template.",
 "message.tooltip.reserved.system.netmask": "The network prefix that defines the pod subnet. Uses CIDR notation.",
 "message.traffic.type.to.basic.zone": "traffic type to basic zone",
-"message.two.fa.auth": "Open the two-factor authentication app on your mobile device to view your authentication code",
-"message.two.fa.auth.register.account": "Open the two-factor authentication application and scan the QR code add the user account",
-"message.two.fa.static.pin.part1": "If you can't scan the QR code, ",
-"message.two.fa.static.pin.part2": "Click here to view the secret code",
+"message.two.fa.auth": "Open the two factor authentication application on your device to view your authentication code",
+"message.two.fa.register.account": "1. Open the authenticator application on your device <br>2. Scan the below QR code to add the user <br>3. If you cannot scan the QR code, enter the setup key manually <br>4. Verification of the 2FA code is mandatory to complete the 2FA setup",
+"message.two.fa.staticpin": "1. Use the generated static pin as 2FA code for two factor authentication<br>2. Verification of the 2FA code is mandatory to complete the 2FA setup",
+"message.two.fa.view.setup.key": "Click here to view the setup key",
+"message.two.fa.view.static.pin": "Click here to view the static pin",
 "message.update.ipaddress.processing": "Updating IP Address...",
 "message.update.resource.count": "Please confirm that you want to update resource counts for this account.",
 "message.update.resource.count.domain": "Please confirm that you want to update resource counts for this domain.",
diff --git a/ui/src/config/section/user.js b/ui/src/config/section/user.js
index c7fe2a7606d..5936f387dd6 100644
--- a/ui/src/config/section/user.js
+++ b/ui/src/config/section/user.js
@@ -109,7 +109,7 @@ export default {
     {
       api: 'setupUserTwoFactorAuthentication',
       icon: 'scan-outlined',
-      label: 'label.action.register.2FA.user.auth',
+      label: 'label.action.setup.2FA.user.auth',
       dataView: true,
       popup: true,
       show: (record, store) => {
diff --git a/ui/src/views/iam/RegisterTwoFactorAuth.vue b/ui/src/views/iam/RegisterTwoFactorAuth.vue
index 388e7fa3173..5887789256e 100644
--- a/ui/src/views/iam/RegisterTwoFactorAuth.vue
+++ b/ui/src/views/iam/RegisterTwoFactorAuth.vue
@@ -17,67 +17,72 @@
 
 <template>
   <div style="width:500px;height=500px">
-  <h3> {{ $t('label.select.2fa.provider') }} </h3>
-  <a-form
-    :rules="rules"
-    layout="vertical">
-    <div class="form-layout" v-ctrl-enter="submitPin">
-      <a-select
-        v-model:value="selectedProvider"
-        optionFilterProp="label"
-        :filterOption="(input, option) => {
-          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-        }"
-        style="width: 100%"
-        @change="val => { handleSelectChange(val) }">
-        <a-select-option
-          v-for="(opt) in providers"
-          :key="opt.name"
-          :disabled="opt.enabled === false">
-            {{ opt.name }}
-        </a-select-option>
-      </a-select>
-    </div>
-    <div v-if="show2FAdetails">
-      <div v-if="selectedProvider === 'google'">
-        <br />
-        <div> {{ $t('message.two.fa.auth.register.account') }} </div>
-        <vue-qrious
-          class="center-align"
-          :value="googleUrl"
-          @change="onDataUrlChange"
-        />
-      </div>
-      <div v-else-if="selectedProvider === 'staticpin'">
-        <div> <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</a></div>
-      </div>
-      <div v-else-if="selectedProvider !== null && selectedProvider !== 'staticpin'">
-        <div> {{ $t('message.two.fa.static.pin.part1') }} <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</a></div>
-      </div>
-      <div v-if="selectedProvider">
-        <br />
-        <h3> {{ $t('label.enter.code') }} </h3>
-        <a-form @finish="submitPin" v-ctrl-enter="submitPin" class="container">
-          <a-input v-model:value="code" />
-          <div :span="24">
-            <a-button ref="submit" type="primary" @click="submitPin">{{ $t('label.ok') }}</a-button>
-          </div>
-        </a-form>
+    <h3> {{ $t('label.select.2fa.provider') }} </h3>
+    <a-form
+      :rules="rules"
+      @close="onCloseModalDisable2FA()"
+      layout="vertical">
+      <div class="form-layout form-align" v-ctrl-enter="submitPin">
+         <a-select
+          v-model:value="selectedProvider"
+          optionFilterProp="label"
+          :filterOption="(input, option) => {
+            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          }"
+          style="width: 100%"
+          @change="val => { handleSelectChange(val) }">
+          <a-select-option
+            v-for="(opt) in providers"
+            :key="opt.name"
+            :disabled="opt.enabled === false">
+              {{ opt.name }}
+          </a-select-option>
+        </a-select>
+        <div :span="24" v-if="selectedProvider">
+          <a-button ref="submit" type="primary" @click="setup2FAProvider">{{ $t('label.setup') }}</a-button>
+        </div>
       </div>
+      <div v-if="show2FAdetails">
+        <div v-if="selectedProvider !== 'staticpin'">
+          <br />
+          <p v-html="$t('message.two.fa.register.account')"></p>
+          <vue-qrious
+            class="center-align"
+            :value="googleUrl"
+            @change="onDataUrlChange"
+          />
+          <div style="text-align: center"> <a @click="showConfiguredPin"> {{ $t('message.two.fa.view.setup.key') }}</a></div>
+        </div>
+        <div v-if="selectedProvider === 'staticpin'">
+          <br>
+          <p v-html="$t('message.two.fa.staticpin')"></p>
+          <br>
+          <div> <a @click="showConfiguredPin"> {{ $t('message.two.fa.view.static.pin') }}</a></div>
+        </div>
+        <div v-if="selectedProvider">
+          <br />
+          <h3> {{ $t('label.enter.code') }} </h3>
+          <a-form @finish="submitPin" v-ctrl-enter="submitPin" class="container">
+            <a-input v-model:value="code" />
+            <div :span="24">
+              <a-button ref="submit" type="primary" @click="submitPin">{{ $t('label.ok') }}</a-button>
+            </div>
+          </a-form>
+        </div>
 
-      <a-modal
-        v-if="showPin"
-        :visible="showPin"
-        :title="$t('label.two.factor.secret')"
-        :closable="true"
-        :footer="null"
-        @cancel="onCloseModal"
-        centered
-        width="450px">
-        <div> {{ pin }} </div>
-      </a-modal>
-    </div>
-  </a-form>
+        <a-modal
+          v-if="showPin"
+          :visible="showPin"
+          :title="$t(selectedProvider === 'staticpin'? 'label.two.factor.authentication.static.pin' : 'label.two.factor.authentication.secret.key')"
+          :closable="true"
+          :footer="null"
+          @cancel="onCloseModal"
+          centered
+          width="450px">
+          <div> {{ pin }} </div>
+        </a-modal>
+      </div>
+    </a-form>
   </div>
 </template>
 <script>
@@ -103,6 +108,7 @@ export default {
       code: '',
       showPin: false,
       show2FAdetails: false,
+      twoFAenabled: false,
       providers: [],
       selectedProvider: null
     }
@@ -116,18 +122,35 @@ export default {
     },
     handleSelectChange (val) {
       this.selectedProvider = val
-      this.setup2FAProvider()
     },
     setup2FAProvider () {
-      api('setupUserTwoFactorAuthentication', { provider: this.selectedProvider }).then(response => {
-        console.log(response)
-        this.pin = response.setupusertwofactorauthenticationresponse.setup2fa.secretcode
-        if (this.selectedProvider === 'google') {
-          this.username = response.setupusertwofactorauthenticationresponse.setup2fa.username
-          this.googleUrl = 'otpauth://totp/CloudStack:' + this.username + '?secret=' + this.pin + '&issuer=CloudStack'
-        }
-        this.showPin = true
-        this.show2FAdetails = true
+      if (!this.twoFAenabled) {
+        api('setupUserTwoFactorAuthentication', { provider: this.selectedProvider }).then(response => {
+          console.log(response)
+          this.pin = response.setupusertwofactorauthenticationresponse.setup2fa.secretcode
+          if (this.selectedProvider === 'google') {
+            this.username = response.setupusertwofactorauthenticationresponse.setup2fa.username
+            this.googleUrl = 'otpauth://totp/CloudStack:' + this.username + '?secret=' + this.pin + '&issuer=CloudStack'
+            this.showPin = false
+          }
+          if (this.selectedProvider === 'staticpin') {
+            this.showPin = true
+          }
+          this.show2FAdetails = true
+          this.twoFAenabled = true
+        }).catch(error => {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
+          })
+        })
+      }
+    },
+    disable2FAProvider () {
+      api('setupUserTwoFactorAuthentication', { enable: false }).then(response => {
+        this.showPin = false
+        this.show2FAdetails = false
+        this.twoFAenabled = false
       }).catch(error => {
         this.$notification.error({
           message: this.$t('message.request.failed'),
@@ -158,8 +181,14 @@ export default {
     closeAction () {
       this.$emit('close-action')
     },
+    showConfiguredPin () {
+      this.showPin = true
+    },
     onCloseModal () {
       this.showPin = false
+    },
+    onCloseModalDisable2FA () {
+      this.disable2FAProvider()
     }
   }
 }
@@ -171,6 +200,10 @@ export default {
     margin-left: auto;
     margin-right: auto;
   }
+  .form-align {
+    display: flex;
+    flex-direction: row;
+  }
   .container {
     display: flex;
   }


[cloudstack] 02/03: Changes in verification page and 2FA enable setting

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

harikrishna pushed a commit to branch 2FA
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 8ac23fed7f6c31691dc514a137c4cd67c5fca701
Author: Harikrishna Patnala <ha...@gmail.com>
AuthorDate: Thu Nov 24 01:28:00 2022 +0530

    Changes in verification page and 2FA enable setting
---
 .../com/cloud/api/auth/APIAuthenticationManagerImpl.java    |  9 ++++-----
 server/src/main/java/com/cloud/user/AccountManager.java     |  2 +-
 server/src/main/java/com/cloud/user/AccountManagerImpl.java |  9 ++++++---
 ui/public/locales/en.json                                   |  3 ++-
 ui/src/views/dashboard/TwoFa.vue                            | 13 ++++++-------
 5 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
index 4927b62d5a3..d847e5c6135 100644
--- a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
@@ -80,11 +80,10 @@ public class APIAuthenticationManagerImpl extends ManagerBase implements APIAuth
         cmdList.add(DefaultLoginAPIAuthenticatorCmd.class);
         cmdList.add(DefaultLogoutAPIAuthenticatorCmd.class);
 
-        if(enable2FA.value()) {
-            cmdList.add(ListUserTwoFactorAuthenticatorProvidersCmd.class);
-            cmdList.add(ValidateUserTwoFactorAuthenticationCodeCmd.class);
-            cmdList.add(SetupUserTwoFactorAuthenticationCmd.class);
-        }
+        cmdList.add(ListUserTwoFactorAuthenticatorProvidersCmd.class);
+        cmdList.add(ValidateUserTwoFactorAuthenticationCodeCmd.class);
+        cmdList.add(SetupUserTwoFactorAuthenticationCmd.class);
+
 
         for (PluggableAPIAuthenticator apiAuthenticator: _apiAuthenticators) {
             List<Class<?>> commands = apiAuthenticator.getAuthCommands();
diff --git a/server/src/main/java/com/cloud/user/AccountManager.java b/server/src/main/java/com/cloud/user/AccountManager.java
index 95d87079a8d..46936fb47f3 100644
--- a/server/src/main/java/com/cloud/user/AccountManager.java
+++ b/server/src/main/java/com/cloud/user/AccountManager.java
@@ -195,7 +195,7 @@ public interface AccountManager extends AccountService, Configurable {
             "enable.two.factor.authentication",
             "false",
             "Determines whether two factor authentication is enabled or not. This can be done at domain level as well",
-            true,
+            false,
             ConfigKey.Scope.Domain);
 
     ConfigKey<String> userTwoFactorAuthenticationProviderPlugin = new ConfigKey<>("Advanced", String.class,
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index 36d7272d87d..c7fb0737427 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -3201,15 +3201,18 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
         if (cmd.getEnable()) {
             checkAccess(caller, null, true, owner);
             Long userId = CallContext.current().getCallingUserId();
+            UserAccountVO userAccount = _userAccountDao.findById(userId);
+            UserVO userVO = _userDao.findById(userId);
+
+            if (!enable2FA.valueIn(userAccount.getDomainId())) {
+                throw new CloudRuntimeException("2FA is not enabled for this domain or at global level");
+            }
 
             if (StringUtils.isEmpty(providerName)) {
                 throw new InvalidParameterValueException("Provider name is mandatory to setup 2FA");
             }
             UserTwoFactorAuthenticator provider = getUserTwoFactorAuthenticationProvider(providerName);
-            UserAccountVO userAccount = _userAccountDao.findById(userId);
-            UserVO userVO = _userDao.findById(userId);
             String code = provider.setup2FAKey(userAccount);
-
             UserVO user = _userDao.createForUpdate();
             user.setKeyFor2fa(code);
             user.setUser2faProvider(provider.getName());
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 7f085b22490..31f9783dded 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -2219,6 +2219,7 @@
 "message.error.add.secondary.ipaddress": "There was an error adding the secondary IP Address.",
 "message.error.agent.password": "Please enter agent password.",
 "message.error.agent.username": "Please enter agent username.",
+"message.error.authentication.code": "Please enter authentication code.",
 "message.error.binaries.iso.url": "Please enter binaries ISO URL.",
 "message.error.bucket": "Please enter bucket",
 "message.error.cloudian.console": "Single-Sign-On failed for Cloudian management console. Please ask your administrator to fix integration issues.",
@@ -2588,7 +2589,7 @@
 "message.template.type.change.warning": "WARNING: Changing the template type to SYSTEM will disable further changes to the template.",
 "message.tooltip.reserved.system.netmask": "The network prefix that defines the pod subnet. Uses CIDR notation.",
 "message.traffic.type.to.basic.zone": "traffic type to basic zone",
-"message.two.fa.auth": "Open the two factor authentication application on your device to view your authentication code",
+"message.two.fa.auth": "<br>1. Open the authenticator application on your device <br>2. Enter the authentication code of the user to verify",
 "message.two.fa.register.account": "1. Open the authenticator application on your device <br>2. Scan the below QR code to add the user <br>3. If you cannot scan the QR code, enter the setup key manually <br>4. Verification of the 2FA code is mandatory to complete the 2FA setup",
 "message.two.fa.staticpin": "1. Use the generated static pin as 2FA code for two factor authentication<br>2. Verification of the 2FA code is mandatory to complete the 2FA setup",
 "message.two.fa.view.setup.key": "Click here to view the setup key",
diff --git a/ui/src/views/dashboard/TwoFa.vue b/ui/src/views/dashboard/TwoFa.vue
index 416c915e2e3..acbd87170a5 100644
--- a/ui/src/views/dashboard/TwoFa.vue
+++ b/ui/src/views/dashboard/TwoFa.vue
@@ -35,12 +35,12 @@
       :rules="rules"
       @finish="handleSubmit"
       layout="vertical">
-      <a-form-item name="secretkey" ref="secretkey">
+      <a-form-item name="code" ref="code">
         <a-input
           class="center-align"
           style="width: 400px"
-          v-model:value="form.secretkey"
-          placeholder="secret key" />
+          v-model:value="form.code"
+          placeholder="xxxxxxx" />
       </a-form-item>
       <div :span="24" class="center-align top-padding">
           <a-button
@@ -51,7 +51,7 @@
             @click="handleSubmit">{{ $t('label.verify') }}
           </a-button>
         </div>
-      <div class="note"> {{ $t('message.two.fa.auth') }} </div>
+      <p style="text-align: center" v-html="$t('message.two.fa.auth')"></p>
     </a-form>
   </a-form>
 </template>
@@ -75,14 +75,13 @@ export default {
       this.formRef = ref()
       this.form = reactive({})
       this.rules = reactive({
-        secretkey: [{ required: true, message: this.$t('message.error.secret.key') }]
+        code: [{ required: true, message: this.$t('message.error.authentication.code') }]
       })
     },
     handleSubmit () {
       this.formRef.value.validate().then(() => {
         const values = toRaw(this.form)
-        console.log(values.secretkey)
-        api('validateUserTwoFactorAuthenticationCode', { '2facode': values.secretkey }).then(response => {
+        api('validateUserTwoFactorAuthenticationCode', { '2facode': values.code }).then(response => {
           this.twoFAresponse = true
           if (this.twoFAresponse) {
             this.$notification.destroy()


[cloudstack] 03/03: Adding setup 2FA at login page

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

harikrishna pushed a commit to branch 2FA
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 7bbad688ef79a8366ce83bd7fdb59a3cb42f5710
Author: Harikrishna Patnala <ha...@gmail.com>
AuthorDate: Thu Nov 24 09:11:33 2022 +0530

    Adding setup 2FA at login page
---
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../cloudstack/api/response/LoginCmdResponse.java  |  12 ++
 server/src/main/java/com/cloud/api/ApiServer.java  |   4 +
 .../api/auth/APIAuthenticationManagerImpl.java     |   2 -
 .../main/java/com/cloud/user/AccountManager.java   |  14 +-
 .../java/com/cloud/user/AccountManagerImpl.java    |   9 +-
 ui/src/config/router.js                            |   6 +-
 ui/src/permission.js                               |   4 +-
 ui/src/store/getters.js                            |   1 +
 ui/src/store/modules/user.js                       |   8 +-
 ui/src/views/auth/Login.vue                        |   4 +-
 ui/src/views/dashboard/Dashboard.vue               |   4 +-
 ui/src/views/dashboard/TwoFa.vue                   | 172 ---------------------
 13 files changed, 50 insertions(+), 191 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 786b0ddbbad..71bb1d6252c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -909,6 +909,7 @@ public class ApiConstants {
     public static final String ADMINS_ONLY = "adminsonly";
     public static final String ANNOTATION_FILTER = "annotationfilter";
     public static final String TWOFACTORAUTHENTICATIONCODE = "2facode";
+    public static final String TWOFACTORAUTHENTICATIONPROVIDER = "twofaprovider";
     public static final String SECRET_CODE = "secretcode";
     public static final String LOGIN = "login";
     public static final String LOGOUT = "logout";
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java
index 94cf380cb05..3c7e35eaf99 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java
@@ -78,6 +78,10 @@ public class LoginCmdResponse extends AuthenticationCmdResponse {
     @Param(description = "Is two factor authentication verified")
     private String is2FAverified;
 
+    @SerializedName(value = ApiConstants.TWOFACTORAUTHENTICATIONPROVIDER)
+    @Param(description = "Two factor authentication provider")
+    private String twoFAprovider;
+
     public String getUsername() {
         return username;
     }
@@ -187,4 +191,12 @@ public class LoginCmdResponse extends AuthenticationCmdResponse {
     public void set2FAverfied(String is2FAverified) {
         this.is2FAverified = is2FAverified;
     }
+
+    public String get2FAprovider() {
+        return twoFAprovider;
+    }
+
+    public void set2FAprovider(String twoFAprovider) {
+        this.twoFAprovider = twoFAprovider;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java
index 2979639aabe..9b4a617d49e 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -1075,6 +1075,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
                 if (ApiConstants.IS_2FA_VERIFIED.equalsIgnoreCase(attrName)) {
                     response.set2FAverfied(attrObj.toString());
                 }
+                if (ApiConstants.TWOFACTORAUTHENTICATIONPROVIDER.equalsIgnoreCase(attrName)) {
+                    response.set2FAprovider(attrObj.toString());
+                }
             }
         }
         response.setResponseName("loginresponse");
@@ -1140,6 +1143,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
 
             session.setAttribute(ApiConstants.IS_2FA_ENABLED, Boolean.toString(userAcct.isTwoFactorAuthenticationEnabled()));
             session.setAttribute(ApiConstants.IS_2FA_VERIFIED, false);
+            session.setAttribute(ApiConstants.TWOFACTORAUTHENTICATIONPROVIDER, userAcct.getUser2faProvider());
 
             // (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that
             // to the login response so that session-based authenticators know to send the key back
diff --git a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
index d847e5c6135..6ec9ff9c1ce 100644
--- a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
@@ -32,8 +32,6 @@ import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
 import com.cloud.utils.component.ComponentContext;
 import com.cloud.utils.component.ManagerBase;
 
-import static com.cloud.user.AccountManager.enable2FA;
-
 @SuppressWarnings("unchecked")
 public class APIAuthenticationManagerImpl extends ManagerBase implements APIAuthenticationManager {
     public static final Logger s_logger = Logger.getLogger(APIAuthenticationManagerImpl.class.getName());
diff --git a/server/src/main/java/com/cloud/user/AccountManager.java b/server/src/main/java/com/cloud/user/AccountManager.java
index 46936fb47f3..7990a826a97 100644
--- a/server/src/main/java/com/cloud/user/AccountManager.java
+++ b/server/src/main/java/com/cloud/user/AccountManager.java
@@ -190,11 +190,19 @@ public interface AccountManager extends AccountService, Configurable {
     ConfigKey<Boolean> UseSecretKeyInResponse = new ConfigKey<Boolean>("Advanced", Boolean.class, "use.secret.key.in.response", "false",
             "This parameter allows the users to enable or disable of showing secret key as a part of response for various APIs. By default it is set to false.", true);
 
-    ConfigKey<Boolean> enable2FA = new ConfigKey<Boolean>("Advanced",
+    ConfigKey<Boolean> enableUserTwoFactorAuthentication = new ConfigKey<Boolean>("Advanced",
             Boolean.class,
-            "enable.two.factor.authentication",
+            "enable.user.two.factor.authentication",
             "false",
-            "Determines whether two factor authentication is enabled or not. This can be done at domain level as well",
+            "Determines whether two factor authentication is enabled or not. This can be configured at domain level also",
+            false,
+            ConfigKey.Scope.Domain);
+
+    ConfigKey<Boolean> mandateUserTwoFactorAuthentication = new ConfigKey<Boolean>("Advanced",
+            Boolean.class,
+            "mandate.user.two.factor.authentication",
+            "false",
+            "Determines whether to make the two factor authentication mandatory or not. This can be configured at domain level also",
             false,
             ConfigKey.Scope.Domain);
 
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index c7fb0737427..b6483c20fa2 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -2386,7 +2386,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
         if (userUUID == null) {
             userUUID = UUID.randomUUID().toString();
         }
-        UserVO user = _userDao.persist(new UserVO(accountId, userName, encodedPassword, firstName, lastName, email, timezone, userUUID, source));
+
+        UserVO userVO = new UserVO(accountId, userName, encodedPassword, firstName, lastName, email, timezone, userUUID, source);
+        userVO.setTwoFactorAuthenticationEnabled(mandateUserTwoFactorAuthentication.valueIn(getAccount(accountId).getDomainId()));
+        UserVO user = _userDao.persist(userVO);
         CallContext.current().putContextParameter(User.class, user.getUuid());
         return user;
     }
@@ -3143,7 +3146,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {UseSecretKeyInResponse, enable2FA, userTwoFactorAuthenticationProviderPlugin};
+        return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication, userTwoFactorAuthenticationProviderPlugin};
     }
 
     public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() {
@@ -3204,7 +3207,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
             UserAccountVO userAccount = _userAccountDao.findById(userId);
             UserVO userVO = _userDao.findById(userId);
 
-            if (!enable2FA.valueIn(userAccount.getDomainId())) {
+            if (!enableUserTwoFactorAuthentication.valueIn(userAccount.getDomainId())) {
                 throw new CloudRuntimeException("2FA is not enabled for this domain or at global level");
             }
 
diff --git a/ui/src/config/router.js b/ui/src/config/router.js
index 349e5da7d71..a1d5be7522d 100644
--- a/ui/src/config/router.js
+++ b/ui/src/config/router.js
@@ -308,13 +308,13 @@ export const constantRouterMap = [
     ]
   },
   {
-    path: '/2FA',
-    name: 'TwoFa',
+    path: '/verify2FA',
+    name: 'VerifyTwoFa',
     meta: {
       title: 'label.two.factor.authentication',
       hidden: true
     },
-    component: () => import('@/views/dashboard/TwoFa')
+    component: () => import('@/views/dashboard/VerifyTwoFa')
   },
   {
     path: '/403',
diff --git a/ui/src/permission.js b/ui/src/permission.js
index 834d0e098be..8dbed2e950e 100644
--- a/ui/src/permission.js
+++ b/ui/src/permission.js
@@ -57,10 +57,9 @@ router.beforeEach((to, from, next) => {
   const validLogin = vueProps.$localStorage.get(ACCESS_TOKEN) || Cookies.get('userid') || Cookies.get('userid', { path: '/client' })
   if (validLogin) {
     if (to.path === '/user/login') {
-      console.log('hari3')
       next({ path: '/dashboard' })
       NProgress.done()
-    } else if (to.path === '/2FA') {
+    } else if (to.path === '/verify2FA') {
       if (store.getters.twoFaEnabled && !store.getters.loginFlag) {
         console.log('Do Two-factor authentication')
         next()
@@ -69,7 +68,6 @@ router.beforeEach((to, from, next) => {
         NProgress.done()
       }
     } else {
-      console.log('hari4')
       if (Object.keys(store.getters.apis).length === 0) {
         const cachedApis = vueProps.$localStorage.get(APIS, {})
         if (Object.keys(cachedApis).length > 0) {
diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js
index 5e4393fa7aa..7c17195dbdc 100644
--- a/ui/src/store/getters.js
+++ b/ui/src/store/getters.js
@@ -45,6 +45,7 @@ const getters = {
   customColumns: state => state.user.customColumns,
   logoutFlag: state => state.user.logoutFlag,
   twoFaEnabled: state => state.user.twoFaEnabled,
+  twoFaProvider: state => state.user.twoFaProvider,
   loginFlag: state => state.user.loginFlag
 }
 
diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js
index 42c7db72ca3..222688d9185 100644
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@ -61,7 +61,8 @@ const user = {
     loginFlag: false,
     logoutFlag: false,
     customColumns: {},
-    twoFaEnabled: false
+    twoFaEnabled: false,
+    twoFaProvider: ''
   },
 
   mutations: {
@@ -137,6 +138,9 @@ const user = {
     SET_2FA_ENABLED: (state, flag) => {
       state.twoFaEnabled = flag
     },
+    SET_2FA_PROVIDER: (state, flag) => {
+      state.twoFaProvider = flag
+    },
     SET_LOGIN_FLAG: (state, flag) => {
       state.loginFlag = flag
     }
@@ -181,6 +185,7 @@ const user = {
           commit('SET_DOMAIN_STORE', {})
           commit('SET_LOGOUT_FLAG', false)
           commit('SET_2FA_ENABLED', (result.is2faenabled === 'true'))
+          commit('SET_2FA_PROVIDER', result.twofaprovider)
           commit('SET_LOGIN_FLAG', false)
           notification.destroy()
 
@@ -310,6 +315,7 @@ const user = {
         commit('SET_DOMAIN_STORE', {})
         commit('SET_LOGOUT_FLAG', true)
         commit('SET_2FA_ENABLED', false)
+        commit('SET_2FA_PROVIDER', '')
         commit('SET_LOGIN_FLAG', false)
         vueProps.$localStorage.remove(CURRENT_PROJECT)
         vueProps.$localStorage.remove(ACCESS_TOKEN)
diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue
index 4b1f773ddb0..0a64fbe3925 100644
--- a/ui/src/views/auth/Login.vue
+++ b/ui/src/views/auth/Login.vue
@@ -298,8 +298,8 @@ export default {
     loginSuccess (res) {
       this.$notification.destroy()
       this.$store.commit('SET_COUNT_NOTIFY', 0)
-      if (store.getters.twoFaEnabled === true) {
-        this.$router.push({ path: '/2FA' }).catch(() => {})
+      if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider !== '') {
+        this.$router.push({ path: '/verify2FA' }).catch(() => {})
       } else {
         this.$store.commit('SET_LOGIN_FLAG', true)
         this.$router.push({ path: '/dashboard' }).catch(() => {})
diff --git a/ui/src/views/dashboard/Dashboard.vue b/ui/src/views/dashboard/Dashboard.vue
index e07b7735bdd..e5bab748fb6 100644
--- a/ui/src/views/dashboard/Dashboard.vue
+++ b/ui/src/views/dashboard/Dashboard.vue
@@ -35,7 +35,7 @@ import store from '@/store'
 import CapacityDashboard from './CapacityDashboard'
 import UsageDashboard from './UsageDashboard'
 import OnboardingDashboard from './OnboardingDashboard'
-import TwoFa from './TwoFa'
+import VerifyTwoFa from './VerifyTwoFa'
 
 export default {
   name: 'Dashboard',
@@ -43,7 +43,7 @@ export default {
     CapacityDashboard,
     UsageDashboard,
     OnboardingDashboard,
-    TwoFa
+    VerifyTwoFa
   },
   provide: function () {
     return {
diff --git a/ui/src/views/dashboard/TwoFa.vue b/ui/src/views/dashboard/TwoFa.vue
deleted file mode 100644
index acbd87170a5..00000000000
--- a/ui/src/views/dashboard/TwoFa.vue
+++ /dev/null
@@ -1,172 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-<template>
-  <a-form>
-    <img
-      v-if="$config.banner"
-      :style="{
-        width: $config.theme['@banner-width'],
-        height: $config.theme['@banner-height']
-      }"
-      :src="$config.banner"
-      class="user-layout-logo"
-      alt="logo">
-    <h1 style="text-align: center; font-size: 24px; color: gray"> {{ $t('label.two.factor.authentication') }} </h1>
-    <br />
-    <br />
-    <a-form
-      :ref="formRef"
-      :model="form"
-      :rules="rules"
-      @finish="handleSubmit"
-      layout="vertical">
-      <a-form-item name="code" ref="code">
-        <a-input
-          class="center-align"
-          style="width: 400px"
-          v-model:value="form.code"
-          placeholder="xxxxxxx" />
-      </a-form-item>
-      <div :span="24" class="center-align top-padding">
-          <a-button
-            :loading="loading"
-            ref="submit"
-            type="primary"
-            class="center-align"
-            @click="handleSubmit">{{ $t('label.verify') }}
-          </a-button>
-        </div>
-      <p style="text-align: center" v-html="$t('message.two.fa.auth')"></p>
-    </a-form>
-  </a-form>
-</template>
-<script>
-
-import { api } from '@/api'
-import { ref, reactive, toRaw } from 'vue'
-
-export default {
-  name: 'TwoFa',
-  data () {
-    return {
-      twoFAresponse: false
-    }
-  },
-  created () {
-    this.initForm()
-  },
-  methods: {
-    initForm () {
-      this.formRef = ref()
-      this.form = reactive({})
-      this.rules = reactive({
-        code: [{ required: true, message: this.$t('message.error.authentication.code') }]
-      })
-    },
-    handleSubmit () {
-      this.formRef.value.validate().then(() => {
-        const values = toRaw(this.form)
-        api('validateUserTwoFactorAuthenticationCode', { '2facode': values.code }).then(response => {
-          this.twoFAresponse = true
-          if (this.twoFAresponse) {
-            this.$notification.destroy()
-            this.$store.commit('SET_COUNT_NOTIFY', 0)
-            this.$store.commit('SET_LOGIN_FLAG', true)
-            this.$router.push({ path: '/dashboard' }).catch(() => {})
-
-            this.$message.success({
-              content: `${this.$t('label.action.enable.two.factor.authentication')}`,
-              duration: 2
-            })
-            this.$emit('refresh-data')
-          }
-          console.log(response)
-        }).catch(error => {
-          this.$notification.error({
-            message: this.$t('message.request.failed'),
-            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
-          })
-        })
-      })
-    }
-  }
-}
-</script>
-<style lang="less" scoped>
-  .center-align {
-    display: block;
-    margin-left: auto;
-    margin-right: auto;
-  }
-  .top-padding {
-    padding-top: 35px;
-  }
-  .note {
-    text-align: center;
-    color: grey;
-    padding-top: 10px;
-  }
-
-  .user-layout {
-    height: 100%;
-
-    &-container {
-      padding: 3rem 0;
-      width: 100%;
-
-      @media (min-height:600px) {
-        padding: 0;
-        position: relative;
-        top: 50%;
-        transform: translateY(-50%);
-        margin-top: -50px;
-      }
-    }
-
-    &-logo {
-      border-style: none;
-      margin: 0 auto 2rem;
-      display: block;
-
-      .mobile & {
-        max-width: 300px;
-        margin-bottom: 1rem;
-      }
-    }
-
-    &-footer {
-      display: flex;
-      flex-direction: column;
-      position: absolute;
-      bottom: 20px;
-      text-align: center;
-      width: 100%;
-
-      @media (max-height: 600px) {
-        position: relative;
-        margin-top: 50px;
-      }
-
-      label {
-        width: 368px;
-        font-weight: 500;
-        margin: 0 auto;
-      }
-    }
-  }
-</style>