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 507db9ef1a5207bdd7bf5da1231b0a427eb7bea8
Author: Harikrishna Patnala <[email protected]>
AuthorDate: Thu Oct 6 05:56:44 2022 +0530

    Recent partial changes
---
 .../org/apache/cloudstack/api/ApiConstants.java    |  5 ++
 .../cloudstack/api/response/LoginCmdResponse.java  | 12 +++
 client/pom.xml                                     | 10 +++
 .../auth/GoogleUserTwoFactorAuthenticator.java     | 28 +++----
 server/src/main/java/com/cloud/api/ApiServer.java  |  5 ++
 server/src/main/java/com/cloud/api/ApiServlet.java | 25 +++++-
 .../cloud/api/auth/TwoFactorAuthenticationCmd.java | 88 ++++++++++++++++++++++
 7 files changed, 158 insertions(+), 15 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 55002f70b1b..f4ae1a93a1d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -227,6 +227,8 @@ public class ApiConstants {
     public static final String IP_ADDRESSES = "ipaddresses";
     public static final String IP6_ADDRESS = "ip6address";
     public static final String IP_ADDRESS_ID = "ipaddressid";
+    public static final String IS_2FA_ENABLED = "is2faenabled";
+
     public static final String IS_ASYNC = "isasync";
     public static final String IP_AVAILABLE = "ipavailable";
     public static final String IP_LIMIT = "iplimit";
@@ -905,6 +907,9 @@ public class ApiConstants {
 
     public static final String ADMINS_ONLY = "adminsonly";
     public static final String ANNOTATION_FILTER = "annotationfilter";
+    public static final String TWOFACTORAUTHENTICATION = 
"twofactorauthentication";
+    public static final String SETUPTWOFACTORAUTHENTICATION = "setup2fa";
+    public static final String TWOFACTORAUTHENTICATIONCODE = "2facode";
     public static final String LOGIN = "login";
     public static final String LOGOUT = "logout";
     public static final String LIST_IDPS = "listIdps";
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 d2d122efb66..baba7ba805f 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
@@ -70,6 +70,10 @@ public class LoginCmdResponse extends 
AuthenticationCmdResponse {
     @Param(description = "Session key that can be passed in subsequent Query 
command calls", isSensitive = true)
     private String sessionKey;
 
+    @SerializedName(value = ApiConstants.IS_2FA_ENABLED)
+    @Param(description = "Is two factor authentication enabled")
+    private String is2FAenabled;
+
     public String getUsername() {
         return username;
     }
@@ -163,4 +167,12 @@ public class LoginCmdResponse extends 
AuthenticationCmdResponse {
     public void setSessionKey(String sessionKey) {
         this.sessionKey = sessionKey;
     }
+
+    public String Is2FAenabled() {
+        return is2FAenabled;
+    }
+
+    public void set2FAenabled(String is2FAenabled) {
+        this.is2FAenabled = is2FAenabled;
+    }
 }
diff --git a/client/pom.xml b/client/pom.xml
index 9394180848b..c0e7fc6116d 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -172,6 +172,16 @@
             
<artifactId>cloud-plugin-user-authenticator-sha256salted</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            
<artifactId>cloud-plugin-user-two-factor-authenticator-google</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            
<artifactId>cloud-plugin-user-two-factor-authenticator-static-pin</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-plugin-metrics</artifactId>
diff --git 
a/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java
 
b/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java
index 21aa63fa933..8dec2a862ea 100644
--- 
a/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java
+++ 
b/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java
@@ -18,17 +18,21 @@ package org.apache.cloudstack.auth;
 
 import javax.inject.Inject;
 
+import com.cloud.utils.exception.CloudRuntimeException;
 import de.taimos.totp.TOTP;
 
 import com.cloud.exception.CloudAuthenticationException;
 import com.cloud.user.UserAccount;
 import org.apache.commons.codec.binary.Base32;
 import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.user.dao.UserAccountDao;
 import com.cloud.utils.component.AdapterBase;
 
+import java.security.SecureRandom;
+
 public class GoogleUserTwoFactorAuthenticator extends AdapterBase implements 
UserTwoFactorAuthenticator {
     public static final Logger s_logger = 
Logger.getLogger(GoogleUserTwoFactorAuthenticator.class);
 
@@ -37,7 +41,6 @@ public class GoogleUserTwoFactorAuthenticator extends 
AdapterBase implements Use
 
     @Override
     public void check2FA(String code, UserAccount userAccount) throws 
CloudAuthenticationException {
-        // TODO: in future get userAccount specific 2FA key
         String expectedCode = get2FACode(get2FAKey(userAccount));
         if (expectedCode.equals(code)) {
             s_logger.info("2FA matches user's input");
@@ -48,18 +51,6 @@ public class GoogleUserTwoFactorAuthenticator extends 
AdapterBase implements Use
 
     public static String get2FAKey(UserAccount userAccount) {
         return userAccount.getKeyFor2fa();
-        //return "7t4gabg72liipmq7n43lt3cw66fel4iz";
-        /*
-        This logic can be replaced on per-user-account basis
-        where the key is generated to show the user one-time QR code,
-        and then stored in DB.
-        For #CCC21 hackathon, we'll take shortcuts ;)
-        SecureRandom random = new SecureRandom();
-        byte[] bytes = new byte[20];
-        random.nextBytes(bytes);
-        Base32 base32 = new Base32();
-        return base32.encodeToString(bytes);
-         */
     }
 
     public static String get2FACode(String secretKey) {
@@ -69,4 +60,15 @@ public class GoogleUserTwoFactorAuthenticator extends 
AdapterBase implements Use
         return TOTP.getOTP(hexKey);
     }
 
+    public static void setup2FAKey(UserAccount userAccount) {
+        if (StringUtils.isNotEmpty(userAccount.getKeyFor2fa())) {
+            throw new CloudRuntimeException(String.format("2FA key is already 
setup for the user account %s", userAccount.getAccountName()));
+        }
+        SecureRandom random = new SecureRandom();
+        byte[] bytes = new byte[20];
+        random.nextBytes(bytes);
+        Base32 base32 = new Base32();
+        String key = base32.encodeToString(bytes);
+        userAccount.setKeyFor2fa(key);
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java 
b/server/src/main/java/com/cloud/api/ApiServer.java
index 76b592a9d90..30c5ba0218a 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -1069,6 +1069,9 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
                 if (ApiConstants.SESSIONKEY.equalsIgnoreCase(attrName)) {
                     response.setSessionKey(attrObj.toString());
                 }
+                if (ApiConstants.IS_2FA_ENABLED.equalsIgnoreCase(attrName)) {
+                    response.set2FAenabled(attrObj.toString());
+                }
             }
         }
         response.setResponseName("loginresponse");
@@ -1132,6 +1135,8 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
                 session.setAttribute("timezoneoffset", 
Float.valueOf(offsetInHrs).toString());
             }
 
+            session.setAttribute("2FAenabled", 
Boolean.toString(userAcct.is2faEnabled()));
+
             // (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
             final SecureRandom sesssionKeyRandom = new SecureRandom();
diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java 
b/server/src/main/java/com/cloud/api/ApiServlet.java
index 1ab12d326af..2062e220c7a 100644
--- a/server/src/main/java/com/cloud/api/ApiServlet.java
+++ b/server/src/main/java/com/cloud/api/ApiServlet.java
@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import com.cloud.user.UserAccount;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiServerService;
 import org.apache.cloudstack.api.ServerApiException;
@@ -296,13 +297,33 @@ public class ApiServlet extends HttpServlet {
 
             if (isNew && s_logger.isTraceEnabled()) {
                 s_logger.trace(String.format("new session: %s", session));
+                // 1. 2fa enabled
+                // 2. except login command with 2fa code
+                // 3. login command and 2fa is succeeded
+                s_logger.trace("Checking if two factor authentication is 
enabled, if enabled it will be verified");
+                UserAccount userAccount = 
accountMgr.getUserAccountById(userId);
+                boolean is2FAenabled = userAccount.is2faEnabled();
+                if (is2FAenabled) {
+                    if (command != null && 
!command.equals(ApiConstants.TWOFACTORAUTHENTICATION) ) {
+                        if (session != null) {
+                            invalidateHttpSession(session, 
String.format("request verification failed for %s from %s", userId, 
remoteAddress.getHostAddress()));
+                        }
+
+                        auditTrailSb.append(" " + 
HttpServletResponse.SC_UNAUTHORIZED + " " + "two factor authentication is not 
done");
+                        final String serializedResponse =
+                                
apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "two 
factor authentication is not done", params,
+                                        responseType);
+                        HttpUtils.writeHttpResponse(resp, serializedResponse, 
HttpServletResponse.SC_UNAUTHORIZED, responseType, 
ApiServer.JSONcontentType.value());
+
+                    }
+                }
             }
             if (!isNew) {
                 userId = (Long)session.getAttribute("userid");
                 final String account = (String) 
session.getAttribute("account");
                 final Object accountObj = session.getAttribute("accountobj");
                 if (account != null) {
-                    if (invalidateHttpSesseionIfNeeded(req, resp, 
auditTrailSb, responseType, params, session, account)) return;
+                    if (invalidateHttpSessionIfNeeded(req, resp, auditTrailSb, 
responseType, params, session, account)) return;
                 } else {
                     if (s_logger.isDebugEnabled()) {
                         s_logger.debug("no account, this request will be 
validated through apikey(%s)/signature");
@@ -399,7 +420,7 @@ public class ApiServlet extends HttpServlet {
         return true;
     }
 
-    private boolean invalidateHttpSesseionIfNeeded(HttpServletRequest req, 
HttpServletResponse resp, StringBuilder auditTrailSb, String responseType, 
Map<String, Object[]> params, HttpSession session, String account) {
+    private boolean invalidateHttpSessionIfNeeded(HttpServletRequest req, 
HttpServletResponse resp, StringBuilder auditTrailSb, String responseType, 
Map<String, Object[]> params, HttpSession session, String account) {
         if (!HttpUtils.validateSessionKey(session, params, req.getCookies(), 
ApiConstants.SESSIONKEY)) {
             String msg = String.format("invalidating session %s for account 
%s", session.getId(), account);
             invalidateHttpSession(session, msg);
diff --git 
a/server/src/main/java/com/cloud/api/auth/TwoFactorAuthenticationCmd.java 
b/server/src/main/java/com/cloud/api/auth/TwoFactorAuthenticationCmd.java
new file mode 100644
index 00000000000..66282f0e823
--- /dev/null
+++ b/server/src/main/java/com/cloud/api/auth/TwoFactorAuthenticationCmd.java
@@ -0,0 +1,88 @@
+// 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.
+package com.cloud.api.auth;
+
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.log4j.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+
+@APICommand(name = ApiConstants.TWOFACTORAUTHENTICATION, description = "Checks 
the 2fa code for the user.", requestHasSensitiveInfo = false, responseObject = 
SuccessResponse.class, entityType = {})
+public class TwoFactorAuthenticationCmd extends BaseCmd implements 
APIAuthenticator {
+
+    public static final Logger s_logger = 
Logger.getLogger(TwoFactorAuthenticationCmd.class.getName());
+    private static final String s_name = "twofactorauthenticationresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.TWOFACTORAUTHENTICATIONCODE, type = 
CommandType.STRING, description = "two factor authentication code", required = 
true)
+    private String twoFactorAuthenticationCode;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getTwoFactorAuthenticationCode() {
+        return twoFactorAuthenticationCode;
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is 
an authentication api, cannot be used directly");
+    }
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.Type.NORMAL.ordinal();
+    }
+
+    @Override
+    public String authenticate(String command, Map<String, Object[]> params, 
HttpSession session, InetAddress remoteAddress, String responseType, 
StringBuilder auditTrailSb, HttpServletRequest req, HttpServletResponse resp) 
throws ServerApiException {
+        return null;
+    }
+
+    @Override
+    public APIAuthenticationType getAPIType() {
+        return APIAuthenticationType.LOGIN_API;
+    }
+
+    @Override
+    public void setAuthenticators(List<PluggableAPIAuthenticator> 
authenticators) {
+    }
+}

Reply via email to