mirror of https://github.com/apache/cloudstack.git
CLOUDSTACK-8701: Add listandswitchsamlaccount API test and add boundary checks
- Adds unit test for ListAndSwitchSAMLAccountCmd
- Checks and logs in user only if they are enabled
- If saml user switches to a locked account, send appropriate error message
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
(cherry picked from commit b30977911d)
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
fcbee609b4
commit
25ccf4126d
|
|
@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command;
|
|||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserAccount;
|
||||
|
|
@ -50,6 +51,7 @@ import javax.inject.Inject;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -131,7 +133,14 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
|||
final User user = _userDao.findByUuid(userUuid);
|
||||
final Domain domain = _domainDao.findByUuid(domainUuid);
|
||||
final UserAccount nextUserAccount = _accountService.getUserAccountById(user.getId());
|
||||
if (!nextUserAccount.getUsername().equals(currentUserAccount.getUsername())
|
||||
if (nextUserAccount != null && !nextUserAccount.getAccountState().equals(Account.State.enabled.toString())) {
|
||||
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
|
||||
"The requested user account is locked and cannot be switched to, please contact your administrator.",
|
||||
params, responseType));
|
||||
}
|
||||
if (nextUserAccount == null
|
||||
|| !nextUserAccount.getAccountState().equals(Account.State.enabled.toString())
|
||||
|| !nextUserAccount.getUsername().equals(currentUserAccount.getUsername())
|
||||
|| !nextUserAccount.getExternalEntity().equals(currentUserAccount.getExternalEntity())
|
||||
|| (nextUserAccount.getDomainId() != domain.getId())
|
||||
|| (nextUserAccount.getSource() != User.Source.SAML2)) {
|
||||
|
|
@ -147,7 +156,8 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
|||
resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
|
||||
return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
} catch (CloudAuthenticationException | IOException exception) {
|
||||
s_logger.debug("Failed to switch to request SAML user account due to: " + exception.getMessage());
|
||||
}
|
||||
} else {
|
||||
List<UserAccountVO> switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(), currentUserAccount.getExternalEntity());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
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;
|
||||
|
|
@ -293,28 +294,32 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||
UserAccount userAccount = null;
|
||||
List<UserAccountVO> possibleUserAccounts = _userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue());
|
||||
if (possibleUserAccounts != null && possibleUserAccounts.size() > 0) {
|
||||
// By default, log into the first user account
|
||||
// Log into the first enabled user account
|
||||
// Users can switch to other allowed accounts later
|
||||
userAccount = possibleUserAccounts.get(0);
|
||||
for (UserAccountVO possibleUserAccount: possibleUserAccounts) {
|
||||
if (possibleUserAccount.getAccountState().equals(Account.State.enabled.toString())) {
|
||||
userAccount = possibleUserAccount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, please contact your administrator",
|
||||
"Your authenticated user is not authorized for SAML Single Sign-On, please contact your administrator",
|
||||
params, responseType));
|
||||
}
|
||||
|
||||
if (userAccount != null) {
|
||||
try {
|
||||
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());
|
||||
return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
try {
|
||||
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());
|
||||
return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
|
||||
}
|
||||
} catch (CloudAuthenticationException | IOException exception) {
|
||||
s_logger.debug("SAML Login failed to log in the user due to: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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 org.apache.cloudstack.api.command;
|
||||
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserAccountVO;
|
||||
import com.cloud.user.UserVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import junit.framework.TestCase;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ApiServerService;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.response.LoginCmdResponse;
|
||||
import org.apache.cloudstack.saml.SAML2AuthManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
|
||||
@Mock
|
||||
ApiServerService apiServer;
|
||||
|
||||
@Mock
|
||||
SAML2AuthManager samlAuthManager;
|
||||
|
||||
@Mock
|
||||
AccountService accountService;
|
||||
|
||||
@Mock
|
||||
UserAccountDao userAccountDao;
|
||||
|
||||
@Mock
|
||||
UserDao userDao;
|
||||
|
||||
@Mock
|
||||
DomainDao domainDao;
|
||||
|
||||
@Mock
|
||||
HttpSession session;
|
||||
|
||||
@Mock
|
||||
HttpServletResponse resp;
|
||||
|
||||
@Mock
|
||||
HttpServletRequest req;
|
||||
|
||||
@Test
|
||||
public void testListAndSwitchSAMLAccountCmd() throws Exception {
|
||||
// Setup
|
||||
final Map<String, Object[]> params = new HashMap<String, Object[]>();
|
||||
final String sessionKeyValue = "someSessionIDValue";
|
||||
Mockito.when(session.getAttribute(ApiConstants.SESSIONKEY)).thenReturn(sessionKeyValue);
|
||||
Mockito.when(session.getAttribute("userid")).thenReturn(2L);
|
||||
params.put(ApiConstants.USER_ID, new String[]{"2"});
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"1"});
|
||||
Mockito.when(userDao.findByUuid(Mockito.anyString())).thenReturn(new UserVO(2L));
|
||||
Mockito.when(domainDao.findByUuid(Mockito.anyString())).thenReturn(new DomainVO());
|
||||
|
||||
// Mock/field setup
|
||||
ListAndSwitchSAMLAccountCmd cmd = new ListAndSwitchSAMLAccountCmd();
|
||||
Field apiServerField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_apiServer");
|
||||
apiServerField.setAccessible(true);
|
||||
apiServerField.set(cmd, apiServer);
|
||||
|
||||
Field managerField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_samlAuthManager");
|
||||
managerField.setAccessible(true);
|
||||
managerField.set(cmd, samlAuthManager);
|
||||
|
||||
Field accountServiceField = BaseCmd.class.getDeclaredField("_accountService");
|
||||
accountServiceField.setAccessible(true);
|
||||
accountServiceField.set(cmd, accountService);
|
||||
|
||||
Field userAccountDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_userAccountDao");
|
||||
userAccountDaoField.setAccessible(true);
|
||||
userAccountDaoField.set(cmd, userAccountDao);
|
||||
|
||||
Field userDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_userDao");
|
||||
userDaoField.setAccessible(true);
|
||||
userDaoField.set(cmd, userDao);
|
||||
|
||||
Field domainDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_domainDao");
|
||||
domainDaoField.setAccessible(true);
|
||||
domainDaoField.set(cmd, domainDao);
|
||||
|
||||
// invalid session test
|
||||
try {
|
||||
cmd.authenticate("command", params, null, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
|
||||
} finally {
|
||||
Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
|
||||
}
|
||||
|
||||
// invalid sessionkey value test
|
||||
params.put(ApiConstants.SESSIONKEY, new String[]{"someOtherValue"});
|
||||
try {
|
||||
Mockito.when(session.isNew()).thenReturn(false);
|
||||
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
|
||||
} finally {
|
||||
Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
|
||||
}
|
||||
|
||||
// valid sessionkey value test
|
||||
params.put(ApiConstants.SESSIONKEY, new String[]{sessionKeyValue});
|
||||
try {
|
||||
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
assertEquals(exception.getErrorCode(), ApiErrorCode.ACCOUNT_ERROR);
|
||||
} finally {
|
||||
Mockito.verify(accountService, Mockito.times(1)).getUserAccountById(Mockito.anyLong());
|
||||
}
|
||||
|
||||
// valid sessionkey, invalid useraccount type (non-saml) value test
|
||||
UserAccountVO mockedUserAccount = new UserAccountVO();
|
||||
mockedUserAccount.setId(2L);
|
||||
mockedUserAccount.setAccountState(Account.State.enabled.toString());
|
||||
mockedUserAccount.setUsername("someUsername");
|
||||
mockedUserAccount.setExternalEntity("some IDP ID");
|
||||
mockedUserAccount.setDomainId(0L);
|
||||
mockedUserAccount.setSource(User.Source.UNKNOWN);
|
||||
Mockito.when(accountService.getUserAccountById(Mockito.anyLong())).thenReturn(mockedUserAccount);
|
||||
try {
|
||||
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
assertEquals(exception.getErrorCode(), ApiErrorCode.ACCOUNT_ERROR);
|
||||
} finally {
|
||||
// accountService should have been called twice by now, for this case and the case above
|
||||
Mockito.verify(accountService, Mockito.times(2)).getUserAccountById(Mockito.anyLong());
|
||||
}
|
||||
|
||||
// all valid test
|
||||
mockedUserAccount.setSource(User.Source.SAML2);
|
||||
Mockito.when(accountService.getUserAccountById(Mockito.anyLong())).thenReturn(mockedUserAccount);
|
||||
Mockito.when(apiServer.verifyUser(Mockito.anyLong())).thenReturn(true);
|
||||
LoginCmdResponse loginCmdResponse = new LoginCmdResponse();
|
||||
loginCmdResponse.setUserId("1");
|
||||
loginCmdResponse.setDomainId("1");
|
||||
loginCmdResponse.setType("1");
|
||||
loginCmdResponse.setUsername("userName");
|
||||
loginCmdResponse.setAccount("someAccount");
|
||||
loginCmdResponse.setFirstName("firstName");
|
||||
loginCmdResponse.setLastName("lastName");
|
||||
loginCmdResponse.setSessionKey("newSessionKeyString");
|
||||
Mockito.when(apiServer.loginUser(Mockito.any(HttpSession.class), Mockito.anyString(), Mockito.anyString(),
|
||||
Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn(loginCmdResponse);
|
||||
try {
|
||||
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
fail("SAML list and switch account API failed to pass for all valid data: " + exception.getMessage());
|
||||
} finally {
|
||||
// accountService should have been called 4 times by now, for this case twice and 2 for cases above
|
||||
Mockito.verify(accountService, Mockito.times(4)).getUserAccountById(Mockito.anyLong());
|
||||
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAPIType() {
|
||||
Assert.assertTrue(new ListAndSwitchSAMLAccountCmd().getAPIType() == APIAuthenticationType.READONLY_API);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue