From 6601bb189bbb9be3f75ada68df7b98a67f55179d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Weing=C3=A4rtner?= Date: Wed, 24 Aug 2016 20:06:47 -0300 Subject: [PATCH] Add support to avoid reply attacks. The support should be activated by default at the Apache CloudStack Client, and it can be configured the time that the request is valid. The default validity configuration is 30 seconds. It should also be able to override that configuration in a per request mode. This closes #20 --- .../client/ApacheCloudStackClient.java | 63 ++++++++++++++- .../client/ApacheCloudStackClientTest.java | 81 +++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/main/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClient.java b/src/main/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClient.java index a6d219f..ee80438 100644 --- a/src/main/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClient.java +++ b/src/main/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClient.java @@ -32,8 +32,12 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Objects; @@ -91,11 +95,23 @@ public class ApacheCloudStackClient { private static final String APACHE_CLOUDSTACK_API_ENDPOINT = "/api"; /** - * This flag indicates if we are going to validade the server certificate in case of HTTPS connections. + * This flag indicates if we are going to validate the server certificate in case of HTTPS connections. * The default value is 'true', meaning that we always validate the server HTTPS certificate. */ protected boolean validateServerHttpsCertificate = true; + /** + * The validity time of the ACS request. + * The default value is {@value #requestValidity} . + */ + private int requestValidity = 30; + + /** + * This parameter controls if the expiration of requests is activated or not. + * It is activated by default. The validity of requests if defined by {@value #requestValidity} property. + */ + private boolean shouldRequestsExpire = true; + private Gson gson = new GsonBuilder().create(); private Logger logger = LoggerFactory.getLogger(getClass()); @@ -418,10 +434,47 @@ protected List createSortedCommandQueryList if (StringUtils.isNotBlank(this.apacheCloudStackUser.getApiKey())) { queryCommand.add(new ApacheCloudStackApiCommandParameter("apiKey", this.apacheCloudStackUser.getApiKey())); } + configureRequestExpiration(queryCommand); Collections.sort(queryCommand); return queryCommand; } + /** + * This method configures the request expiration if needed. + * It uses the value defined at {@link #requestValidity} to determine until when the request is valid. + * It also uses the parameter {@link #shouldRequestsExpire} to decide if it has to configure or not the validity of the request. + * Moreover, if the 'apacheCloudStackRequestList' contains the 'expires' it will only add a parameter called 'signatureVersion=3', in order to enable that override. + */ + protected void configureRequestExpiration(List apacheCloudStackRequestList) { + boolean isOverridingExpirationConfigs = apacheCloudStackRequestList.contains(new ApacheCloudStackApiCommandParameter("expires", StringUtils.EMPTY)); + if (!isOverridingExpirationConfigs && !shouldRequestsExpire) { + return; + } + apacheCloudStackRequestList.add(new ApacheCloudStackApiCommandParameter("signatureVersion", 3)); + if (isOverridingExpirationConfigs) { + return; + } + String expirationDataAsSring = createExpirationDate(); + apacheCloudStackRequestList.add(new ApacheCloudStackApiCommandParameter("expires", expirationDataAsSring)); + } + + /** + * This method creates the expiration date as a string according to the ISO 8601. + */ + protected String createExpirationDate() { + DateFormat acsIso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + return acsIso8601DateFormat.format(getExpirationDate()); + } + + /** + * Creates the expiration date, by adding the {@link #requestValidity} to the current time. + */ + protected Date getExpirationDate() { + Calendar now = Calendar.getInstance(); + now.add(Calendar.SECOND, requestValidity); + return now.getTime(); + } + /** * It executes the given request and converts the result into an object of the given type. * This method will change the response type to 'JSON'. To execute the request, it uses the method {@link #executeRequest(ApacheCloudStackRequest)}. @@ -436,4 +489,12 @@ public T executeRequest(ApacheCloudStackRequest request, Class clazz) { public void setValidateServerHttpsCertificate(boolean validateServerHttpsCertificate) { this.validateServerHttpsCertificate = validateServerHttpsCertificate; } + + public void setRequestValidity(int requestValidity) { + this.requestValidity = requestValidity; + } + + public void setShouldRequestsExpire(boolean shouldRequestsExpire) { + this.shouldRequestsExpire = shouldRequestsExpire; + } } diff --git a/src/test/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClientTest.java b/src/test/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClientTest.java index 423a144..bd21075 100644 --- a/src/test/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClientTest.java +++ b/src/test/java/br/com/autonomiccs/apacheCloudStack/client/ApacheCloudStackClientTest.java @@ -25,7 +25,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -285,6 +287,7 @@ public void getUrlEncodedValueTestValueWithSpaces() { } @Test + @SuppressWarnings("unchecked") public void createSortedCommandQueryListTestWithApiKey() { ApacheCloudStackRequest apacheCloudStackRequestMock = Mockito.mock(ApacheCloudStackRequest.class); Set params = new HashSet<>(); @@ -292,10 +295,12 @@ public void createSortedCommandQueryListTestWithApiKey() { params.add(new ApacheCloudStackApiCommandParameter("param1", "value1")); Mockito.doReturn(params).when(apacheCloudStackRequestMock).getParameters(); Mockito.when(apacheCloudStackClient.apacheCloudStackUser.getApiKey()).thenReturn("apiKey"); + Mockito.doNothing().when(apacheCloudStackClient).configureRequestExpiration(Mockito.anyList()); List sortedCommandQueryList = apacheCloudStackClient.createSortedCommandQueryList(apacheCloudStackRequestMock); Mockito.verify(apacheCloudStackRequestMock).getParameters(); + Mockito.verify(apacheCloudStackClient).configureRequestExpiration(Mockito.anyList()); Assert.assertEquals(3, sortedCommandQueryList.size()); Assert.assertEquals("apiKey", sortedCommandQueryList.get(0).getName()); @@ -304,6 +309,7 @@ public void createSortedCommandQueryListTestWithApiKey() { } @Test + @SuppressWarnings("unchecked") public void createSortedCommandQueryListTestWithourApiKey() { ApacheCloudStackRequest apacheCloudStackRequestMock = Mockito.mock(ApacheCloudStackRequest.class); Set params = new HashSet<>(); @@ -311,10 +317,12 @@ public void createSortedCommandQueryListTestWithourApiKey() { params.add(new ApacheCloudStackApiCommandParameter("param1", "value1")); Mockito.doReturn(params).when(apacheCloudStackRequestMock).getParameters(); Mockito.when(apacheCloudStackClient.apacheCloudStackUser.getApiKey()).thenReturn(null); + Mockito.doNothing().when(apacheCloudStackClient).configureRequestExpiration(Mockito.anyList()); List sortedCommandQueryList = apacheCloudStackClient.createSortedCommandQueryList(apacheCloudStackRequestMock); Mockito.verify(apacheCloudStackRequestMock).getParameters(); + Mockito.verify(apacheCloudStackClient).configureRequestExpiration(Mockito.anyList()); Assert.assertEquals(2, sortedCommandQueryList.size()); Assert.assertEquals("command", sortedCommandQueryList.get(0).getName()); @@ -552,4 +560,77 @@ public void executeUserLogoutTest() { inOrder.verify(apacheCloudStackClient).createApacheCloudStackApiUrlRequest(Mockito.any(ApacheCloudStackRequest.class), Mockito.eq(false)); inOrder.verify(apacheCloudStackClient).executeRequestGetResponseAsString(Mockito.eq(urlRequest), Mockito.any(CloseableHttpClient.class), Mockito.any(HttpContext.class)); } + + @Test + public void configureRequestExpirationTestRequestsShouldNotExpire() { + apacheCloudStackClient.setShouldRequestsExpire(false); + + ArrayList arrayList = new ArrayList<>(); + apacheCloudStackClient.configureRequestExpiration(arrayList); + + Assert.assertEquals(0, arrayList.size()); + } + + @Test + public void configureRequestExpirationTestRequestsShouldNotExpireUsingOverride() { + apacheCloudStackClient.setShouldRequestsExpire(false); + + ArrayList arrayList = new ArrayList<>(); + arrayList.add(new ApacheCloudStackApiCommandParameter("expires", "2011-10-10T12:00:00+0530")); + + apacheCloudStackClient.configureRequestExpiration(arrayList); + + Assert.assertEquals(2, arrayList.size()); + Mockito.verify(apacheCloudStackClient, Mockito.never()).createExpirationDate(); + } + + @Test + public void configureRequestExpirationTestRequestsShouldExpireUsingOverride() { + apacheCloudStackClient.setShouldRequestsExpire(true); + + ArrayList arrayList = new ArrayList<>(); + ApacheCloudStackApiCommandParameter expirationParameter = new ApacheCloudStackApiCommandParameter("expires", "2011-10-10T12:00:00+0530"); + arrayList.add(expirationParameter); + + apacheCloudStackClient.configureRequestExpiration(arrayList); + + Assert.assertEquals(2, arrayList.size()); + Assert.assertEquals(expirationParameter, arrayList.get(0)); + Mockito.verify(apacheCloudStackClient, Mockito.never()).createExpirationDate(); + } + + @Test + public void configureRequestExpirationTestRequestsShouldExpireWithoutOverride() { + apacheCloudStackClient.setShouldRequestsExpire(true); + + ArrayList arrayList = new ArrayList<>(); + + String expirationDate = "2011-10-10T12:00:00+0530"; + Mockito.doReturn(expirationDate).when(apacheCloudStackClient).createExpirationDate(); + + apacheCloudStackClient.configureRequestExpiration(arrayList); + + Assert.assertEquals(2, arrayList.size()); + Assert.assertEquals("signatureVersion", arrayList.get(0).getName()); + Assert.assertEquals(3, arrayList.get(0).getValue()); + Assert.assertEquals("expires", arrayList.get(1).getName()); + Assert.assertEquals(expirationDate, arrayList.get(1).getValue()); + + Mockito.verify(apacheCloudStackClient).createExpirationDate(); + } + + @Test + public void createExpirationDateTest() { + Calendar someMomentInTimeSpace = Calendar.getInstance(); + someMomentInTimeSpace.set(1999, 12, 31, 23, 59, 59); + someMomentInTimeSpace.set(Calendar.MILLISECOND, 0); + Mockito.doReturn(someMomentInTimeSpace.getTime()).when(apacheCloudStackClient).getExpirationDate(); + + String expirationDate = apacheCloudStackClient.createExpirationDate(); + + String expectedExpirationDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(someMomentInTimeSpace.getTime()); + Assert.assertEquals(expectedExpirationDate, expirationDate); + + Mockito.verify(apacheCloudStackClient).getExpirationDate(); + } }