[ 
https://issues.apache.org/jira/browse/CLOUDSTACK-9976?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16666206#comment-16666206
 ] 

ASF GitHub Bot commented on CLOUDSTACK-9976:
--------------------------------------------

rhtyd closed pull request #2185: CLOUDSTACK-9976: Redirect saml2 failed login 
message to a configurable URL
URL: https://github.com/apache/cloudstack/pull/2185
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git 
a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
 
b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
index 41005ab8f9c..4b486466f8e 100644
--- 
a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
+++ 
b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
@@ -16,14 +16,18 @@
 // under the License.
 package org.apache.cloudstack.api.command;
 
-import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.exception.CloudAuthenticationException;
-import com.cloud.user.Account;
-import com.cloud.user.DomainManager;
-import com.cloud.user.UserAccount;
-import com.cloud.user.UserAccountVO;
-import com.cloud.user.dao.UserAccountDao;
-import com.cloud.utils.db.EntityManager;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
+
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiErrorCode;
@@ -35,11 +39,14 @@
 import org.apache.cloudstack.api.auth.APIAuthenticator;
 import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
 import org.apache.cloudstack.api.response.LoginCmdResponse;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLPluginConstants;
 import org.apache.cloudstack.saml.SAMLProviderMetadata;
 import org.apache.cloudstack.saml.SAMLTokenVO;
 import org.apache.cloudstack.saml.SAMLUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.saml2.core.Assertion;
@@ -62,19 +69,17 @@
 import org.opensaml.xml.validation.ValidationException;
 import org.xml.sax.SAXException;
 
-import javax.inject.Inject;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.FactoryConfigurationError;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.util.List;
-import java.util.Map;
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.user.Account;
+import com.cloud.user.DomainManager;
+import com.cloud.user.UserAccount;
+import com.cloud.user.UserAccountVO;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.utils.db.EntityManager;
 
 @APICommand(name = "samlSso", description = "SP initiated SAML Single Sign 
On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, 
entityType = {})
-public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements 
APIAuthenticator {
+public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements 
APIAuthenticator, Configurable {
     public static final Logger s_logger = 
Logger.getLogger(SAML2LoginAPIAuthenticatorCmd.class.getName());
     private static final String s_name = "loginresponse";
 
@@ -85,15 +90,18 @@
     private String idpId;
 
     @Inject
-    ApiServerService _apiServer;
+    ApiServerService apiServer;
     @Inject
-    EntityManager _entityMgr;
+    EntityManager entityMgr;
     @Inject
-    DomainManager _domainMgr;
+    DomainManager domainMgr;
     @Inject
-    private UserAccountDao _userAccountDao;
+    private UserAccountDao userAccountDao;
+
+    protected static ConfigKey<String> saml2FailedLoginRedirectUrl = new 
ConfigKey<String>("Advanced", String.class, "saml2.failed.login.redirect.url", 
"",
+            "The URL to redirect the SAML2 login failed message (the default 
vaulue is empty).", true);
 
-    SAML2AuthManager _samlAuthManager;
+    SAML2AuthManager samlAuthManager;
 
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
@@ -159,27 +167,27 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                     }
                 }
 
-                SAMLProviderMetadata spMetadata = 
_samlAuthManager.getSPMetadata();
-                SAMLProviderMetadata idpMetadata = 
_samlAuthManager.getIdPMetadata(idpId);
+                SAMLProviderMetadata spMetadata = 
samlAuthManager.getSPMetadata();
+                SAMLProviderMetadata idpMetadata = 
samlAuthManager.getIdPMetadata(idpId);
                 if (idpMetadata == null) {
-                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
+                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
                             "IdP ID (" + idpId + ") is not found in our list 
of supported IdPs, cannot proceed.",
                             params, responseType));
                 }
                 if (idpMetadata.getSsoUrl() == null || 
idpMetadata.getSsoUrl().isEmpty()) {
-                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
+                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
                             "IdP ID (" + idpId + ") has no Single Sign On URL 
defined please contact "
                                     + idpMetadata.getContactPersonName() + " 
<" + idpMetadata.getContactPersonEmail() + ">, cannot proceed.",
                             params, responseType));
                 }
                 String authnId = SAMLUtils.generateSecureRandomId();
-                _samlAuthManager.saveToken(authnId, domainPath, 
idpMetadata.getEntityId());
+                samlAuthManager.saveToken(authnId, domainPath, 
idpMetadata.getEntityId());
                 s_logger.debug("Sending SAMLRequest id=" + authnId);
                 String redirectUrl = SAMLUtils.buildAuthnRequestUrl(authnId, 
spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value());
                 resp.sendRedirect(redirectUrl);
                 return "";
             } if (params.containsKey("SAMLart")) {
-                throw new 
ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.UNSUPPORTED_ACTION_ERROR.getHttpCode(),
+                throw new 
ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.UNSUPPORTED_ACTION_ERROR.getHttpCode(),
                         "SAML2 HTTP Artifact Binding is not supported",
                         params, responseType));
             } else {
@@ -187,27 +195,27 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                 Response processedSAMLResponse = 
this.processSAMLResponse(samlResponse);
                 String statusCode = 
processedSAMLResponse.getStatus().getStatusCode().getValue();
                 if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
-                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                             "Identity Provider send a non-successful 
authentication status code",
                             params, responseType));
                 }
 
                 String username = null;
                 Issuer issuer = processedSAMLResponse.getIssuer();
-                SAMLProviderMetadata spMetadata = 
_samlAuthManager.getSPMetadata();
-                SAMLProviderMetadata idpMetadata = 
_samlAuthManager.getIdPMetadata(issuer.getValue());
+                SAMLProviderMetadata spMetadata = 
samlAuthManager.getSPMetadata();
+                SAMLProviderMetadata idpMetadata = 
samlAuthManager.getIdPMetadata(issuer.getValue());
 
                 String responseToId = processedSAMLResponse.getInResponseTo();
                 s_logger.debug("Received SAMLResponse in response to id=" + 
responseToId);
-                SAMLTokenVO token = _samlAuthManager.getToken(responseToId);
+                SAMLTokenVO token = samlAuthManager.getToken(responseToId);
                 if (token != null) {
                     if 
(!(token.getEntity().equalsIgnoreCase(issuer.getValue()))) {
-                        throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                        throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                                 "The SAML response contains Issuer Entity ID 
that is different from the original SAML request",
                                 params, responseType));
                     }
                 } else {
-                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                             "Received SAML response for a SSO request that we 
may not have made or has expired, please try logging in again",
                             params, responseType));
                 }
@@ -224,7 +232,7 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                         validator.validate(sig);
                     } catch (ValidationException e) {
                         s_logger.error("SAML Response's signature failed to be 
validated by IDP signing key:" + e.getMessage());
-                        throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                        throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                                 "SAML Response's signature failed to be 
validated by IDP signing key",
                                 params, responseType));
                     }
@@ -269,7 +277,7 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                                     validator.validate(encSig);
                                 } catch (ValidationException e) {
                                     s_logger.error("SAML Response's signature 
failed to be validated by IDP signing key:" + e.getMessage());
-                                    throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                                    throw new 
ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                                             "SAML Response's signature failed 
to be validated by IDP signing key",
                                             params, responseType));
                                 }
@@ -285,16 +293,16 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                 }
 
                 if (username == null) {
-                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                             "Failed to find admin configured username 
attribute in the SAML Response. Please ask your administrator to check SAML 
user attribute name.", params, responseType));
                 }
 
                 UserAccount userAccount = null;
-                List<UserAccountVO> possibleUserAccounts = 
_userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue());
+                List<UserAccountVO> possibleUserAccounts = 
userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue());
                 if (possibleUserAccounts != null && 
possibleUserAccounts.size() > 0) {
                     // Log into the first enabled user account
                     // Users can switch to other allowed accounts later
-                    for (UserAccountVO possibleUserAccount: 
possibleUserAccounts) {
+                    for (UserAccountVO possibleUserAccount : 
possibleUserAccounts) {
                         if 
(possibleUserAccount.getAccountState().equals(Account.State.enabled.toString()))
 {
                             userAccount = possibleUserAccount;
                             break;
@@ -302,15 +310,11 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
                     }
                 }
 
-                if (userAccount == null || userAccount.getExternalEntity() == 
null || !_samlAuthManager.isUserAuthorized(userAccount.getId(), 
issuer.getValue())) {
-                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
-                            "Your authenticated user is not authorized for 
SAML Single Sign-On, please contact your administrator",
-                            params, responseType));
-                }
+                whenFailToAuthenticateThrowExceptionOrRedirectToUrl(params, 
responseType, resp, issuer, userAccount);
 
                 try {
-                    if (_apiServer.verifyUser(userAccount.getId())) {
-                        LoginCmdResponse loginResponse = (LoginCmdResponse) 
_apiServer.loginUser(session, userAccount.getUsername(), 
userAccount.getUsername() + userAccount.getSource().toString(),
+                    if (apiServer.verifyUser(userAccount.getId())) {
+                        LoginCmdResponse loginResponse = (LoginCmdResponse) 
apiServer.loginUser(session, userAccount.getUsername(), 
userAccount.getUsername() + userAccount.getSource().toString(),
                                 userAccount.getDomainId(), null, 
remoteAddress, params);
                         SAMLUtils.setupSamlUserCookies(loginResponse, resp);
                         
resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
@@ -324,11 +328,29 @@ public String authenticate(final String command, final 
Map<String, Object[]> par
             auditTrailSb.append("SP initiated SAML authentication using HTTP 
redirection failed:");
             auditTrailSb.append(e.getMessage());
         }
-        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
                 "Unable to authenticate user while performing SAML based SSO. 
Please make sure your user/account has been added, enable and authorized by the 
admin before you can authenticate. Please contact your administrator.",
                 params, responseType));
     }
 
+    /**
+     * If it fails to authenticate the user, the method gets the value from 
configuration
+     * Saml2FailedLoginRedirectUrl; if the user configured an error URL then 
it redirects to that
+     * URL, otherwise it throws the ServerApiException
+     */
+    protected void whenFailToAuthenticateThrowExceptionOrRedirectToUrl(final 
Map<String, Object[]> params, final String responseType, final 
HttpServletResponse resp, Issuer issuer,
+            UserAccount userAccount) throws IOException {
+        if (userAccount == null || userAccount.getExternalEntity() == null || 
!samlAuthManager.isUserAuthorized(userAccount.getId(), issuer.getValue())) {
+            String saml2RedirectUrl = saml2FailedLoginRedirectUrl.value();
+            if (StringUtils.isBlank(saml2RedirectUrl)) {
+                throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, 
apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                        "Your authenticated user is not authorized for SAML 
Single Sign-On, please contact your administrator", params, responseType));
+            } else {
+                resp.sendRedirect(saml2RedirectUrl);
+            }
+        }
+    }
+
     @Override
     public APIAuthenticationType getAPIType() {
         return APIAuthenticationType.LOGIN_API;
@@ -338,11 +360,22 @@ public APIAuthenticationType getAPIType() {
     public void setAuthenticators(List<PluggableAPIAuthenticator> 
authenticators) {
         for (PluggableAPIAuthenticator authManager: authenticators) {
             if (authManager != null && authManager instanceof 
SAML2AuthManager) {
-                _samlAuthManager = (SAML2AuthManager) authManager;
+                samlAuthManager = (SAML2AuthManager) authManager;
             }
         }
-        if (_samlAuthManager == null) {
+        if (samlAuthManager == null) {
             s_logger.error("No suitable Pluggable Authentication Manager found 
for SAML2 Login Cmd");
         }
     }
+
+    @Override
+    public String getConfigComponentName() {
+        return SAML2LoginAPIAuthenticatorCmd.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {saml2FailedLoginRedirectUrl};
+    }
+
 }
diff --git 
a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
 
b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
index 2ce88414b40..cc45cbb987d 100644
--- 
a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
+++ 
b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertFalse;
 
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.net.InetAddress;
 import java.security.KeyPair;
@@ -36,6 +37,7 @@
 import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLPluginConstants;
 import org.apache.cloudstack.saml.SAMLProviderMetadata;
@@ -45,8 +47,10 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.opensaml.common.SAMLVersion;
 import org.opensaml.saml2.core.Assertion;
@@ -106,6 +110,10 @@
     @Mock
     HttpServletRequest req;
 
+    @Spy
+    @InjectMocks
+    private SAML2LoginAPIAuthenticatorCmd cmdSpy;
+
     private Response buildMockResponse() throws Exception {
         Response samlMessage = new ResponseBuilder().buildObject();
         samlMessage.setID("foo");
@@ -139,11 +147,11 @@ private Response buildMockResponse() throws Exception {
     public void testAuthenticate() throws Exception {
         SAML2LoginAPIAuthenticatorCmd cmd = Mockito.spy(new 
SAML2LoginAPIAuthenticatorCmd());
 
-        Field apiServerField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_apiServer");
+        Field apiServerField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("apiServer");
         apiServerField.setAccessible(true);
         apiServerField.set(cmd, apiServer);
 
-        Field managerField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_samlAuthManager");
+        Field managerField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("samlAuthManager");
         managerField.setAccessible(true);
         managerField.set(cmd, samlAuthManager);
 
@@ -151,11 +159,11 @@ public void testAuthenticate() throws Exception {
         accountServiceField.setAccessible(true);
         accountServiceField.set(cmd, accountService);
 
-        Field domainMgrField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_domainMgr");
+        Field domainMgrField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("domainMgr");
         domainMgrField.setAccessible(true);
         domainMgrField.set(cmd, domainMgr);
 
-        Field userAccountDaoField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_userAccountDao");
+        Field userAccountDaoField = 
SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("userAccountDao");
         userAccountDaoField.setAccessible(true);
         userAccountDaoField.set(cmd, userAccountDao);
 
@@ -205,4 +213,87 @@ public void testAuthenticate() throws Exception {
     public void testGetAPIType() {
         Assert.assertTrue(new SAML2LoginAPIAuthenticatorCmd().getAPIType() == 
APIAuthenticationType.LOGIN_API);
     }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlBlank()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", " ", 
false);
+        boolean expectServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(true, 
expectServerApiException, 0, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlNull()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", 
null, false);
+        boolean expectServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(true, 
expectServerApiException, 0, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlEmpty()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", "", 
false);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(true, 
hasThrownServerApiException, 0, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectExternalEntityNullAndUrlNotConfigured()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(null, " ", 
false);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(true, 
hasThrownServerApiException, 0, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectExternalEntityNullAndUrlConfigured()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(null, 
"some.url", true);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, 
hasThrownServerApiException, 1, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlConfigured()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", 
"some.url", false);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, 
hasThrownServerApiException, 1, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlUserAccountNull()
 throws IOException {
+        
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", 
"some.url", true);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(null);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, 
hasThrownServerApiException, 1, 1);
+    }
+
+    @Test
+    public void 
whenFailToAuthenticateThrowExceptionOrRedirectToUrlTestSaml2FailedLoginRedirectUrlIsUserAuthorized()
 throws IOException {
+        UserAccountVO userAccount = 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl("entity", 
"some.url", true);
+        boolean hasThrownServerApiException = 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(userAccount);
+        verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, 
hasThrownServerApiException, 0, 0);
+    }
+
+    private UserAccountVO 
configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(String entity, 
String configurationValue, Boolean isUserAuthorized)
+            throws IOException {
+        Mockito.when(samlAuthManager.isUserAuthorized(Mockito.anyLong(), 
Mockito.anyString())).thenReturn(isUserAuthorized);
+        SAML2LoginAPIAuthenticatorCmd.saml2FailedLoginRedirectUrl = new 
ConfigKey<String>("Advanced", String.class, "saml2.failed.login.redirect.url", 
configurationValue,
+                "The URL to redirect the SAML2 login failed message (the 
default vaulue is empty).", true);
+        UserAccountVO userAccount = new UserAccountVO();
+        userAccount.setExternalEntity(entity);
+        userAccount.setId(0l);
+        return userAccount;
+    }
+
+    private void 
verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(boolean 
expectServerApiException, boolean hasThrownServerApiException, int 
timesOfSendRedirect,
+            int timesOfConfigDao) throws IOException {
+        Mockito.verify(resp, 
Mockito.times(timesOfSendRedirect)).sendRedirect(Mockito.anyString());
+        Assert.assertEquals(expectServerApiException, 
hasThrownServerApiException);
+    }
+
+    private boolean 
runTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(UserAccountVO 
userAccount) throws IOException {
+        try {
+            cmdSpy.whenFailToAuthenticateThrowExceptionOrRedirectToUrl(null, 
"responseType", resp, new IssuerBuilder().buildObject(), userAccount);
+        } catch (ServerApiException e) {
+            return true;
+        }
+        return false;
+    }
+
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> Redirect saml2 failed login message to a configurable URL
> ---------------------------------------------------------
>
>                 Key: CLOUDSTACK-9976
>                 URL: https://issues.apache.org/jira/browse/CLOUDSTACK-9976
>             Project: CloudStack
>          Issue Type: New Feature
>      Security Level: Public(Anyone can view this level - this is the 
> default.) 
>            Reporter: Gabriel Beims Bräscher
>            Assignee: Gabriel Beims Bräscher
>            Priority: Minor
>             Fix For: 4.10.1.0
>
>         Attachments: samlLoginResponse.png
>
>
> When a user fails to authenticate with SAML2, it returns an error page 
> showing the content of the attached image.
> To make it more user-friendly and customizable, one could configure a 
> desirable URL to redirect when such authentication failure happens.
> This ticket proposes a global settings variable 
> (saml2.failed.login.redirect.url). If null, the SAML2 authentication flow 
> does not change from the current; however, if the user configures an URL then 
> ACS redirects to that URL.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to