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:44 UTC
[cloudstack] 03/03: Adding setup 2FA at login page
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>