[
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)