Merge remote-tracking branch 'origin/inetaddress'

- Tested locally against unit tests
- TravisCI build passed: https://travis-ci.org/apache/cloudstack/builds/41990351
- Manual QA passed for basic auth and saml auth using default IDP settings

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>

Conflicts:
	server/src/com/cloud/api/ApiServlet.java
This commit is contained in:
Rohit Yadav 2014-11-25 14:31:23 +05:30
commit 7ff31f1b22
20 changed files with 60 additions and 61 deletions

View File

@ -19,13 +19,14 @@ package org.apache.cloudstack.api;
import com.cloud.exception.CloudAuthenticationException;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.net.InetAddress;
public interface ApiServerService {
public boolean verifyRequest(Map<String, Object[]> requestParameters, Long userId) throws ServerApiException;
public Long fetchDomainId(String domainUUID);
public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, String loginIpAddress,
public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, InetAddress loginIpAddress,
Map<String, Object[]> requestParameters) throws CloudAuthenticationException;
public void logoutUser(long userId);

View File

@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
/*
* APIAuthenticator is an interface that defines method that
@ -35,7 +36,7 @@ import java.util.Map;
* */
public interface APIAuthenticator {
public String authenticate(String command, Map<String, Object[]> params,
HttpSession session, String remoteAddress, String responseType,
HttpSession session, InetAddress remoteAddress, String responseType,
StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException;
public APIAuthenticationType getAPIType();

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.network.contrail.management;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@ -231,7 +232,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public UserAccount authenticateUser(String arg0, String arg1, Long arg2, String arg3, Map<String, Object[]> arg4) {
public UserAccount authenticateUser(String arg0, String arg1, Long arg2, InetAddress arg3, Map<String, Object[]> arg4) {
// TODO Auto-generated method stub
return null;
}

View File

@ -72,6 +72,7 @@ import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
@APICommand(name = "getSPMetadata", description = "Returns SAML2 CloudStack Service Provider MetaData", responseObject = SAMLMetaDataResponse.class, entityType = {})
public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthenticator {
@ -103,7 +104,7 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
}
@Override
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletResponse resp) throws ServerApiException {
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletResponse resp) throws ServerApiException {
SAMLMetaDataResponse response = new SAMLMetaDataResponse();
response.setResponseName(getCommandName());

View File

@ -67,6 +67,7 @@ import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.FactoryConfigurationError;
import java.io.IOException;
import java.net.URLEncoder;
import java.net.InetAddress;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@ -160,7 +161,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
}
@Override
public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final InetAddress remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
try {
if (!params.containsKey("SAMLResponse") && !params.containsKey("SAMLart")) {
String idpUrl = null;

View File

@ -50,6 +50,7 @@ import javax.xml.stream.FactoryConfigurationError;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
@APICommand(name = "samlSlo", description = "SAML Global Log Out API", responseObject = LogoutCmdResponse.class, entityType = {})
public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
@ -83,7 +84,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
}
@Override
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
auditTrailSb.append("=== SAML SLO Logging out ===");
LogoutCmdResponse response = new LogoutCmdResponse();
response.setDescription("success");

View File

@ -41,6 +41,8 @@ import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.net.InetAddress;
import java.net.UnknownHostException;
@RunWith(MockitoJUnitRunner.class)
public class GetServiceProviderMetaDataCmdTest {
@ -58,7 +60,7 @@ public class GetServiceProviderMetaDataCmdTest {
HttpServletResponse resp;
@Test
public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, UnknownHostException {
GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
@ -77,7 +79,7 @@ public class GetServiceProviderMetaDataCmdTest {
Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
String result = cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
String result = cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
Assert.assertTrue(result.contains("md:EntityDescriptor"));
Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getServiceProviderId();

View File

@ -65,6 +65,7 @@ import java.lang.reflect.Field;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.net.InetAddress;
@RunWith(MockitoJUnitRunner.class)
public class SAML2LoginAPIAuthenticatorCmdTest {
@ -171,14 +172,14 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
Map<String, Object[]> params = new HashMap<String, Object[]>();
// SSO redirection test
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
cmd.authenticate("command", params, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
// SSO SAMLResponse verification test, this should throw ServerApiException for auth failure
params.put(SAMLUtils.SAML_RESPONSE, new String[]{"Some String"});
Mockito.stub(cmd.processSAMLResponse(Mockito.anyString())).toReturn(buildMockResponse());
try {
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
cmd.authenticate("command", params, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
} catch (ServerApiException ignored) {
}
Mockito.verify(configDao, Mockito.atLeastOnce()).getValue(Mockito.anyString());

View File

@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.security.cert.X509Certificate;
import java.net.InetAddress;
@RunWith(MockitoJUnitRunner.class)
public class SAML2LogoutAPIAuthenticatorCmdTest {
@ -81,7 +82,7 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
Mockito.when(configDao.getValue(Mockito.anyString())).thenReturn("someString");
cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), resp);
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
Mockito.verify(session, Mockito.atLeastOnce()).getAttribute(Mockito.anyString());
}

View File

@ -82,6 +82,7 @@
<cs.aws.sdk.version>1.3.22</cs.aws.sdk.version>
<cs.lang.version>2.6</cs.lang.version>
<cs.commons-io.version>1.4</cs.commons-io.version>
<cs.commons-validator.version>1.4.0</cs.commons-validator.version>
<cs.reflections.version>0.9.8</cs.reflections.version>
<cs.java-ipv6.version>0.10</cs.java-ipv6.version>
<cs.replace.properties>build/replace.properties</cs.replace.properties>
@ -233,6 +234,11 @@
<artifactId>commons-codec</artifactId>
<version>${cs.codec.version}</version>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>${cs.commons-validator.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>

View File

@ -982,7 +982,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
}
@Override
public ResponseObject loginUser(final HttpSession session, final String username, final String password, Long domainId, final String domainPath, final String loginIpAddress,
public ResponseObject loginUser(final HttpSession session, final String username, final String password, Long domainId, final String domainPath, final InetAddress loginIpAddress,
final Map<String, Object[]> requestParameters) throws CloudAuthenticationException {
// We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist
// we will default to ROOT

View File

@ -20,6 +20,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -188,7 +189,7 @@ public class ApiServlet extends HttpServlet {
}
try {
responseString = apiAuthenticator.authenticate(command, params, session, remoteAddress, responseType, auditTrailSb, resp);
responseString = apiAuthenticator.authenticate(command, params, session, InetAddress.getByName(remoteAddress), responseType, auditTrailSb, resp);
} catch (ServerApiException e) {
httpResponseCode = e.getErrorCode().getHttpCode();
responseString = e.getMessage();
@ -330,14 +331,14 @@ public class ApiServlet extends HttpServlet {
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
return null;
}
if(NetUtils.isValidIp(ip)) {
if(NetUtils.isValidIp(ip) || NetUtils.isValidIpv6(ip)) {
return ip;
}
//it could be possible to have multiple IPs in HTTP header, this happens if there are multiple proxy in between
//the client and the servlet, so parse the client IP
String[] ips = ip.split(",");
for(String i : ips) {
if(NetUtils.isValidIp(i.trim())) {
if(NetUtils.isValidIp(i.trim()) || NetUtils.isValidIpv6(i.trim())) {
return i.trim();
}
}

View File

@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
@APICommand(name = "login", description = "Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {})
public class DefaultLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
@ -103,7 +104,7 @@ public class DefaultLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthe
}
@Override
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
// FIXME: ported from ApiServlet, refactor and cleanup
final String[] username = (String[])params.get(ApiConstants.USERNAME);

View File

@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
@APICommand(name = "logout", description = "Logs out the user", responseObject = LogoutCmdResponse.class, entityType = {})
public class DefaultLogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
@ -60,7 +61,7 @@ public class DefaultLogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuth
}
@Override
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
auditTrailSb.append("=== Logging out ===");
LogoutCmdResponse response = new LogoutCmdResponse();
response.setDescription("success");

View File

@ -18,6 +18,7 @@ package com.cloud.user;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
@ -71,7 +72,7 @@ public interface AccountManager extends AccountService {
* made, and the signature itself in the single sign-on case
* @return a user object, null if the user failed to authenticate
*/
UserAccount authenticateUser(String username, String password, Long domainId, String loginIpAddress, Map<String, Object[]> requestParameters);
UserAccount authenticateUser(String username, String password, Long domainId, InetAddress loginIpAddress, Map<String, Object[]> requestParameters);
/**
* Locate a user by their apiKey

View File

@ -17,6 +17,7 @@
package com.cloud.user;
import java.net.URLEncoder;
import java.net.InetAddress;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
@ -1972,7 +1973,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
@Override
public UserAccount authenticateUser(String username, String password, Long domainId, String loginIpAddress, Map<String, Object[]> requestParameters) {
public UserAccount authenticateUser(String username, String password, Long domainId, InetAddress loginIpAddress, Map<String, Object[]> requestParameters) {
UserAccount user = null;
if (password != null) {
user = getUserAccount(username, password, domainId, requestParameters);
@ -2080,13 +2081,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
if (s_logger.isDebugEnabled()) {
s_logger.debug("User: " + username + " in domain " + domainId + " has successfully logged in");
}
if (NetUtils.isValidIp(loginIpAddress)) {
ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, "user has logged in from IP Address " +
ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, "user has logged in from IP Address " +
loginIpAddress);
} else {
ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN,
"user has logged in. The IP Address cannot be determined");
}
return user;
} else {
if (s_logger.isDebugEnabled()) {

View File

@ -45,6 +45,8 @@ import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
@RunWith(MockitoJUnitRunner.class)
@ -87,7 +89,7 @@ public class ApiServletTest {
@SuppressWarnings("unchecked")
@Before
public void setup() throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException, IOException {
IllegalArgumentException, IllegalAccessException, IOException, UnknownHostException {
servlet = new ApiServlet();
responseWriter = new StringWriter();
Mockito.when(response.getWriter()).thenReturn(
@ -103,7 +105,7 @@ public class ApiServletTest {
Mockito.when(authManager.getAPIAuthenticator(Mockito.anyString())).thenReturn(authenticator);
Mockito.when(authenticator.authenticate(Mockito.anyString(), Mockito.anyMap(), Mockito.isA(HttpSession.class),
Mockito.anyString(), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class))).thenReturn("{\"loginresponse\":{}");
Mockito.same(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class))).thenReturn("{\"loginresponse\":{}");
Field authManagerField = ApiServlet.class.getDeclaredField("_authManager");
authManagerField.setAccessible(true);
@ -198,7 +200,7 @@ public class ApiServletTest {
@SuppressWarnings("unchecked")
@Test
public void processRequestInContextLogout() {
public void processRequestInContextLogout() throws UnknownHostException {
Mockito.when(request.getMethod()).thenReturn("GET");
Mockito.when(request.getSession(Mockito.anyBoolean())).thenReturn(
session);
@ -215,13 +217,13 @@ public class ApiServletTest {
Mockito.verify(authManager).getAPIAuthenticator("logout");
Mockito.verify(authenticator).authenticate(Mockito.anyString(), Mockito.anyMap(), Mockito.isA(HttpSession.class),
Mockito.anyString(), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class));
Mockito.eq(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class));
Mockito.verify(session).invalidate();
}
@SuppressWarnings("unchecked")
@Test
public void processRequestInContextLogin() {
public void processRequestInContextLogin() throws UnknownHostException {
Mockito.when(request.getMethod()).thenReturn("GET");
Mockito.when(request.getSession(Mockito.anyBoolean())).thenReturn(
session);
@ -237,7 +239,7 @@ public class ApiServletTest {
Mockito.verify(authManager).getAPIAuthenticator("login");
Mockito.verify(authenticator).authenticate(Mockito.anyString(), Mockito.anyMap(), Mockito.isA(HttpSession.class),
Mockito.anyString(), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class));
Mockito.eq(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletResponse.class));
}
@Test

View File

@ -18,6 +18,7 @@ package com.cloud.user;
import java.util.List;
import java.util.Map;
import java.net.InetAddress;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
@ -250,7 +251,7 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco
}
@Override
public UserAccount authenticateUser(String username, String password, Long domainId, String loginIpAddress, Map<String, Object[]> requestParameters) {
public UserAccount authenticateUser(String username, String password, Long domainId, InetAddress loginIpAddress, Map<String, Object[]> requestParameters) {
return null;
}

View File

@ -58,6 +58,10 @@
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>

View File

@ -22,7 +22,6 @@ package com.cloud.utils.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InterfaceAddress;
@ -43,6 +42,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.apache.commons.validator.routines.InetAddressValidator;
import org.apache.log4j.Logger;
import com.googlecode.ipv6.IPv6Address;
@ -520,35 +520,9 @@ public class NetUtils {
}
public static boolean isValidIp(final String ip) {
final String[] ipAsList = ip.split("\\.");
InetAddressValidator validator = InetAddressValidator.getInstance();
// The IP address must have four octets
if (Array.getLength(ipAsList) != 4) {
return false;
}
for (int i = 0; i < 4; i++) {
// Each octet must be an integer
final String octetString = ipAsList[i];
int octet;
try {
octet = Integer.parseInt(octetString);
} catch (final Exception e) {
return false;
}
// Each octet must be between 0 and 255, inclusive
if (octet < 0 || octet > 255) {
return false;
}
// Each octetString must have between 1 and 3 characters
if (octetString.length() < 1 || octetString.length() > 3) {
return false;
}
}
// IP is good, return true
return true;
return validator.isValidInet4Address(ip);
}
public static boolean isValidCIDR(final String cidr) {