From 8524022e507a57137967e09dafa1caae4d157ccc Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 01:12:37 +0200 Subject: [PATCH 1/7] Work in progress on fixing registerSSHKeyPair and implementing getPasswordForVirtualMachine --- .../CloudStackComputeServiceAdapter.java | 50 ++--- .../features/SSHKeyPairAsyncClient.java | 12 +- .../features/VirtualMachineAsyncClient.java | 23 ++- .../features/VirtualMachineClient.java | 17 +- .../compute/CloudStackExperimentLiveTest.java | 62 +++++- .../features/SSHKeyPairClientExpectTest.java | 192 ++++++++++++++++++ .../features/SSHKeyPairClientLiveTest.java | 17 +- .../parse/ListSSHKeyPairsResponseTest.java | 13 +- .../resources/createsshkeypairresponse.json | 4 + .../resources/listsshkeypairsresponse.json | 3 +- .../resources/registersshkeypairresponse.json | 2 + 11 files changed, 322 insertions(+), 73 deletions(-) create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientExpectTest.java create mode 100644 apis/cloudstack/src/test/resources/createsshkeypairresponse.json create mode 100644 apis/cloudstack/src/test/resources/registersshkeypairresponse.json diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java index 64f8325cae..34d29c9c5b 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java @@ -18,29 +18,14 @@ */ package org.jclouds.cloudstack.compute.strategy; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Predicates.and; -import static com.google.common.collect.Iterables.filter; -import static com.google.common.collect.Iterables.getOnlyElement; -import static org.jclouds.cloudstack.options.DeployVirtualMachineOptions.Builder.displayName; -import static org.jclouds.cloudstack.predicates.NetworkPredicates.defaultNetworkInZone; -import static org.jclouds.cloudstack.predicates.NetworkPredicates.supportsStaticNAT; -import static org.jclouds.cloudstack.predicates.TemplatePredicates.isReady; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutionException; - -import javax.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; import com.google.common.base.Throwables; -import com.google.common.collect.Iterables; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; import org.jclouds.cloudstack.CloudStackClient; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; import org.jclouds.cloudstack.domain.AsyncCreateResponse; @@ -65,13 +50,20 @@ import org.jclouds.domain.Credentials; import org.jclouds.domain.LoginCredentials; import org.jclouds.logging.Logger; -import com.google.common.base.Predicate; -import com.google.common.base.Supplier; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSet.Builder; -import com.google.common.collect.Sets; -import com.google.common.primitives.Ints; +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.filter; +import static org.jclouds.cloudstack.predicates.TemplatePredicates.isReady; /** * defines the connection between the {@link CloudStackClient} implementation diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java index c2501d8166..9a8e4f589e 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java @@ -18,12 +18,6 @@ */ package org.jclouds.cloudstack.features; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import java.util.Set; - import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.filters.AuthenticationFilter; @@ -37,6 +31,12 @@ import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.util.Set; + /** * Provides asynchronous access to CloudStack SSHKeyPair features. * diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java index fec2366804..dedb0decbd 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java @@ -18,13 +18,7 @@ */ package org.jclouds.cloudstack.features; -import java.util.Set; - -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; - +import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.AsyncCreateResponse; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.filters.AuthenticationFilter; @@ -39,7 +33,11 @@ import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; -import com.google.common.util.concurrent.ListenableFuture; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.util.Set; /** * Provides asynchronous access to cloudstack via their REST API. @@ -121,6 +119,15 @@ public interface VirtualMachineAsyncClient { @Consumes(MediaType.APPLICATION_JSON) ListenableFuture resetPasswordForVirtualMachine(@QueryParam("id") long id); + /** + * @see VirtualMachineClient#getPasswordForVirtualMachine + */ + @GET + @QueryParams(keys = "command", values = "getVMPassword") + @SelectJson("jobid") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture getPasswordForVirtualMachine(@QueryParam("id") long id); + /** * @see VirtualMachineClient#changeServiceForVirtualMachine */ diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java index 6474bac86a..fee4bab708 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java @@ -18,15 +18,15 @@ */ package org.jclouds.cloudstack.features; -import java.util.Set; -import java.util.concurrent.TimeUnit; - import org.jclouds.cloudstack.domain.AsyncCreateResponse; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.options.DeployVirtualMachineOptions; import org.jclouds.cloudstack.options.ListVirtualMachinesOptions; import org.jclouds.concurrent.Timeout; +import java.util.Set; +import java.util.concurrent.TimeUnit; + /** * Provides synchronous access to CloudStack VirtualMachine features. *

@@ -110,6 +110,17 @@ public interface VirtualMachineClient { */ Long resetPasswordForVirtualMachine(long id); + + /** + * Return an encrypted password for the virtual machine. The command + * is asynchronous. + * + * @param id + * the ID of the virtual machine + * @return job id related to getting the encrypted password + */ + Long getPasswordForVirtualMachine(long id); + /** * Changes the service offering for a virtual machine. The virtual machine * must be in a "Stopped" state for this command to take effect. diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index 705514c83b..e4eb8e4a8b 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -18,17 +18,6 @@ */ package org.jclouds.cloudstack.compute; -import static com.google.common.collect.Iterables.concat; -import static com.google.common.collect.Iterables.get; -import static com.google.common.collect.Sets.newTreeSet; -import static org.jclouds.cloudstack.options.CreateNetworkOptions.Builder.vlan; -import static org.jclouds.cloudstack.options.ListNetworkOfferingsOptions.Builder.specifyVLAN; - -import java.net.URI; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; import org.jclouds.cloudstack.domain.Network; import org.jclouds.cloudstack.domain.TrafficType; @@ -38,8 +27,23 @@ import org.jclouds.compute.RunNodesException; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.predicates.NodePredicates; +import org.jclouds.crypto.SshKeys; +import org.testng.Assert; import org.testng.annotations.Test; +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.get; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Sets.newTreeSet; +import static org.jclouds.cloudstack.options.CreateNetworkOptions.Builder.vlan; +import static org.jclouds.cloudstack.options.ListNetworkOfferingsOptions.Builder.specifyVLAN; + /** * * @author Adrian Cole @@ -122,4 +126,40 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { } } + @Test + public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException { + final Map sshKey = SshKeys.generate(); + final String publicKey = sshKey.get("public"); + + String keyPairName = prefix + "-windows-keypair"; + client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName); + // client.getSSHKeyPairClient().registerSSHKeyPair(keyPairName, publicKey); + client.getSSHKeyPairClient().createSSHKeyPair(keyPairName); + + String group = prefix + "-windows-test"; + Template template = computeContext.getComputeService().templateBuilder() + .imageId("290").locationId("1") + .options(new CloudStackTemplateOptions().setupStaticNat(false).keyPair(keyPairName)) + .build(); + + NodeMetadata node = null; + try { + node = getOnlyElement(computeContext.getComputeService() + .createNodesInGroup(group, 1, template)); + + long jobId = client.getVirtualMachineClient() + .getPasswordForVirtualMachine(Long.parseLong(node.getId())); + // TODO: extrect the password from the async response + + Assert.fail("Password: ..."); + + } finally { + if (node != null) { + computeContext.getComputeService().destroyNode(node.getId()); + } + + } + + } + } \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientExpectTest.java new file mode 100644 index 0000000000..f441582d53 --- /dev/null +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientExpectTest.java @@ -0,0 +1,192 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.features; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import org.jclouds.cloudstack.CloudStackContext; +import org.jclouds.cloudstack.domain.SshKeyPair; +import org.jclouds.crypto.SshKeys; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.testng.annotations.Test; + +import java.net.URI; +import java.net.URLEncoder; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Test the CloudStack SSHKeyPairClient + * + * @author Andrei Savu + */ +@Test(groups = "unit", testName = "SSHKeyPairClientExpectTest") +public class SSHKeyPairClientExpectTest extends BaseCloudStackRestClientExpectTest { + + @Test + public void testListAndGetSSHKeyPairsWhenResponseIs2xx() { + HttpResponse response = HttpResponse.builder() + .statusCode(200) + .payload(payloadFromResource("/listsshkeypairsresponse.json")) + .build(); + + SSHKeyPairClient client = requestSendsResponse(HttpRequest.builder() + .method("GET") + .endpoint( + URI.create("http://localhost:8080/client/api?response=json&" + + "command=listSSHKeyPairs&apiKey=identity&signature=9Mz1e7xf3vdH3QrDrvWm5eiRsjc%3D")) + .headers( + ImmutableMultimap.builder() + .put("Accept", "application/json") + .build()) + .build(), response); + + assertEquals(client.listSSHKeyPairs(), ImmutableSet.of( + SshKeyPair.builder().name("jclouds-keypair") + .fingerprint("1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b").build())); + + client = requestSendsResponse(HttpRequest.builder() + .method("GET") + .endpoint( + URI.create("http://localhost:8080/client/api?response=json&command=listSSHKeyPairs&" + + "name=jclouds-keypair&apiKey=identity&signature=vYFm%2BwYIxwpjyk3xLjjGBzSkLRc%3D")) + .headers( + ImmutableMultimap.builder() + .put("Accept", "application/json") + .build()) + .build(), response); + + assertEquals(client.getSSHKeyPair("jclouds-keypair"), + SshKeyPair.builder().name("jclouds-keypair") + .fingerprint("1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b").build()); + } + + @Test + public void testCreateSSHKeyPairsWhenResponseIs2xx() { + SSHKeyPairClient client = requestSendsResponse( + HttpRequest.builder() + .method("GET") + .endpoint( + URI.create("http://localhost:8080/client/api?response=json&command=createSSHKeyPair&" + + "name=jclouds-keypair&apiKey=identity&signature=8wk32PZF44jrBLH2HLel22%2BqMC4%3D")) + .headers( + ImmutableMultimap.builder() + .put("Accept", "application/json") + .build()) + .build(), + HttpResponse.builder() + .statusCode(200) + .payload(payloadFromResource("/createsshkeypairresponse.json")) + .build()); + + SshKeyPair actual = client.createSSHKeyPair("jclouds-keypair"); + SshKeyPair expected = SshKeyPair.builder().name("jclouds-keypair") + .fingerprint("1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b") + .privateKey("-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXgIBAAKBgQDZo/EF4Ew1uEW0raz7vCs28lBwy0UKV2Xr606gaEgxO7h9mSXZ\n" + + "4x2K/KQ1NMnrbjppxGycLh9EKPWAO3ezFULAyuOZW4Fy+xRS8+3MAijxBJY/KBgl\n" + + "x5rJm2ILumRkTNkMlLGCSBb9SOqYRN1VpOy7kn3StzU9LdJ/snKVE2JLHQIDAQAB\n" + + "AoGBAMnL5okKRd9xcsBqYIAxIuiZmNhcwTErhEdRMOAukPGFbDSYsa3rldLvGdpz\n" + + "jd2LoQG8rO/LHBZ429kASqZzyiV+NvcgH+tFNJSVAigjSICfhEKF9PY2TiAkrg7S\n" + + "GyJgAjpPWQc2sQh0dE8EPEtBiq4ibXfMTDmbs1d/vnfdwtQJAkEA+AX5Y+xgWj74\n" + + "dYETmNLyLhNZpftLizEfIYj7lCVhsbFwVb8jbM1m8n8bxwGjls1w/ico1CWcQna+\n" + + "UnAfA8kJvwJBAOCj0YgDKpYd0OLQhvI3212J9QcQpJEkDOTYiMwXNHCNMKRpoF47\n" + + "MPPX+GG8YzUiQAi9/OG4pDKCjzQWE/ebiiMCQQCssnQ5WICqtggIwYykr9VDseON\n" + + "SFIMpHJ5xkjumazRrqx6eDGxc8BH/6uWwRRoT7pqrVeniFyqhsX03u8pkpU/AkBj\n" + + "WfCcwBHArNUqy2EzlWKuvwogosq16oTNXbs60HR/5uIBhTnJE1K2NemDiGc0I77A\n" + + "Xw6N4jS0piuhtLYGB8OTAkEA50abdbduXWcr62Z6E8G/6LNFaNg0uBuVgwSHtJMd\n" + + "dNeUtVDHQCHSf3tvxXTAtaB9PCnGOfgm/dyYWEMf3rMoHQ==\n" + + "-----END RSA PRIVATE KEY-----\n") + .build(); + + assertEquals(actual, expected); + assertEquals(SshKeys.fingerprintPrivateKey(actual.getPrivateKey()), expected.getFingerprint()); + } + + @Test + public void testRegisterSSHKeyPairWhenResponseIs2xx() { + String publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9kDOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + + String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEpQIBAAKCAQEAnPdN7cMVNs3EJyCXV7+tUcGTWVveLiHfas9PH4LASbAjpxzS\n" + + "AuPi/ZAzgNiB1fXFHxAwZtQXuU26WnVsEm0v9ORPhQER9vzAjvd9lIHClKuTxspk\n" + + "tNuPXywgT3Iuc6C4hubyMpvtYo9YS/5x75CvfTSMHu4qPi5qfTnBdz07Jt0YIeU9\n" + + "PXERxl/YaK6dFaAFWvbyicegtfyBB1ZxvKdGDz2tZZnpr8eZDQRIsqDUDWGt+BIh\n" + + "ZHCXz+q6jYL2PIf4+JxPUl6FAgv4On64TBTDi9Lc2Kr89C3M6ptToeEZ0p9zvV+E\n" + + "fd+KdvKqgKG3t9+4ZpS1HW+h+c7n/jcAodB3WQIDAQABAoIBAQCX+iKr2LzLiUMo\n" + + "lzexsFbB1+kxFe/zPryxD/QOEGzZa/+5KAB25+q5k0sqr3ZWkVXAk84pYaVut0F9\n" + + "oD95P9q1A/GyV6zrNSHDywD+Lv0VMWMtkH0dV5Bjl7fY9DbhoXXIuAc81Rhs21mk\n" + + "isIKME6Zra0VrYedGRfmE2usZc7F+rrnJeWs2edk1Q/lBLIe/v+NfRrO0fpHPu8S\n" + + "9/kbVM3fUwHXxVTbvzZjjerQcLyEr4nT53DcSQJcm3e2DGsdRr5FBxkOXlcWElew\n" + + "pbGM+RiF7RJvPW8lrmGj4y7Eo7TmfW8Yc5MM5A/PcvvxuRTRurmqOA5Wl1Bsp8/o\n" + + "PEU/p9G5AoGBANcBOz0vSj+NOFip9gbc2WPVFpaoCT51DBQsT9R4kxe34Ltbwqaj\n" + + "QXMiBjgereSM/KXTriA/Lhkj09YI5OAgk64PXcmDc2urMiFlewqxld79GDLAFwqn\n" + + "nsEm1YTjY8wujw2J5Fbp7BZFHCrfld5L8xhgSb135YEa1/4LGOg+o6FDAoGBALrl\n" + + "GL/v8ZDc2l/GpGsOA7360s9lRUhCTlQ86am8Lw/AdMSdpi9Is3yCdZx1NWDpUEKz\n" + + "MBQTfiEEzpYlujvdUQNyQ4JGuhU/J7JEqEP2rfXaXjn0PIThkWFuNRkyK6Pz0rsT\n" + + "4YJQouI7PCDE3BZxY4WYZ4uBZpCf3YC5SZiwtl0zAoGBAJGNnNwD+sDhSscDcLIe\n" + + "qvDh3iPp6DAnLyEtCnItmm7RJcvRCAqltPZLj2hIpLJ4G8XrcxMTkpKkZZGdfcyZ\n" + + "YUDR2E1Gt0mpoQto1w5bQLmwH8SjtDWbWmcqchw/kF03G9MviaypOhGtga8opB3U\n" + + "zuKutN0WoQFw+c5bFuaLGV1fAoGABdFLy+20H0ZApeqRA6QUCb3dAges+GrX9VdQ\n" + + "DrCE5oCfId+mZKJms+F7t7sORk386ZaaUIWqz2xO4e2atnJVKz5LS6rX8AFfQvVQ\n" + + "J41uLND3TeaEW76Jv/amQHqHUTstvBUKV/waleAyJvL5xtkQt//eeUE16BqR0ofx\n" + + "+obFpnECgYEAuDT1vH9JcGhD/iX4qLhS1xS1fXJh4IYvt8bg8oLRyRBqF6x9uhx3\n" + + "6v+WQaKHyGvebWRN+SKAsKQHsh8a7Iy7xZdZmQ8v9j4DcYwJMb7ksV//R2kXAPGL\n" + + "BTfRj1MSI+6AsuVY/YF1O2AfGneP+Zn5bQwYzQkxOYjzF9bhZz3IniE=\n" + + "-----END RSA PRIVATE KEY-----\n"; + + // Compute the fingerprint by using the following command: ssh-keygen -lf key.pub + String expectedFingerprint = "8f:f1:91:2d:b1:a8:51:f1:79:cf:c4:31:c4:14:9d:81"; + + assertTrue(SshKeys.privateKeyMatchesPublicKey(privateKey, publicKey)); + assertEquals(SshKeys.fingerprintPublicKey(publicKey), expectedFingerprint); + assertEquals(SshKeys.fingerprintPrivateKey(privateKey), expectedFingerprint); + + SSHKeyPairClient client = requestSendsResponse( + HttpRequest.builder() + .method("GET") + .endpoint( + URI.create("http://localhost:8080/client/api?response=json&command=registerSSHKeyPair&" + + "name=jclouds-keypair&publickey=" + URLEncoder.encode(publicKey) + + "&apiKey=identity&signature=x6kHcaqhJW%2B7iMV4nLCRkm05AQ4%3D")) + .headers( + ImmutableMultimap.builder() + .put("Accept", "application/json") + .build()) + .build(), + HttpResponse.builder() + .statusCode(200) + .payload(payloadFromResource("/registersshkeypairresponse.json")) + .build()); + + SshKeyPair actual = client.registerSSHKeyPair("jclouds-keypair", publicKey); + SshKeyPair expected = SshKeyPair.builder().name("jclouds-keypair") + .fingerprint(expectedFingerprint).build(); + + assertEquals(actual, expected); + assertEquals(expectedFingerprint, expected.getFingerprint()); + } + + @Override + protected SSHKeyPairClient clientFrom(CloudStackContext context) { + return context.getProviderSpecificContext().getApi().getSSHKeyPairClient(); + } +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java index 0bd7ca361b..dfd61fcf01 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java @@ -18,17 +18,17 @@ */ package org.jclouds.cloudstack.features; -import static org.testng.Assert.assertEquals; - -import java.util.Map; -import java.util.Set; - import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.crypto.SshKeys; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.Map; +import java.util.Set; + +import static org.testng.Assert.assertEquals; + /** * Tests behavior of {@code SSHKeyPairClient} * @@ -47,6 +47,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName); } + @Test public void testListSSHKeyPairs() { final Set sshKeyPairs = client.getSSHKeyPairClient().listSSHKeyPairs(); for (SshKeyPair sshKeyPair : sshKeyPairs) { @@ -54,6 +55,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { } } + @Test public void testCreateDeleteSSHKeyPair() { sshKeyPair = client.getSSHKeyPairClient().createSSHKeyPair(keyPairName); checkSSHKeyPair(sshKeyPair); @@ -65,6 +67,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { sshKeyPair = null; } + @Test public void testRegisterDeleteSSHKeyPair() { final Map sshKey = SshKeys.generate(); final String publicKey = sshKey.get("public"); @@ -74,9 +77,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName); assertEquals(client.getSSHKeyPairClient().getSSHKeyPair(sshKeyPair.getName()), null); - - //FIXME: somehow the fingerprints aren't matching, so leaving this commented out for now - // assertEquals(SshKeys.fingerprintPublicKey(publicKey), sshKeyPair.getFingerprint()); + assertEquals(SshKeys.fingerprintPublicKey(publicKey), sshKeyPair.getFingerprint()); sshKeyPair = null; } diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/parse/ListSSHKeyPairsResponseTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/parse/ListSSHKeyPairsResponseTest.java index 72ede73f0d..0e85803779 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/parse/ListSSHKeyPairsResponseTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/parse/ListSSHKeyPairsResponseTest.java @@ -18,8 +18,9 @@ */ package org.jclouds.cloudstack.parse; -import java.util.Set; - +import com.google.common.collect.ImmutableSet; +import com.google.inject.Guice; +import com.google.inject.Injector; import org.jclouds.cloudstack.config.CloudStackParserModule; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.json.BaseSetParserTest; @@ -27,9 +28,7 @@ import org.jclouds.json.config.GsonModule; import org.jclouds.rest.annotations.SelectJson; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableSet; -import com.google.inject.Guice; -import com.google.inject.Injector; +import java.util.Set; /** * @author Adrian Cole @@ -57,10 +56,10 @@ public class ListSSHKeyPairsResponseTest extends BaseSetParserTest { } @Override - @SelectJson("keypair") + @SelectJson("sshkeypair") public Set expected() { return ImmutableSet. of(SshKeyPair.builder().name("jclouds-keypair") - .fingerprint("43:6a:bd:46:e0:3d:3a:8d:ab:69:25:bb:b9:ca:9d:17").build()); + .fingerprint("1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b").build()); } } diff --git a/apis/cloudstack/src/test/resources/createsshkeypairresponse.json b/apis/cloudstack/src/test/resources/createsshkeypairresponse.json new file mode 100644 index 0000000000..65d68ea0fa --- /dev/null +++ b/apis/cloudstack/src/test/resources/createsshkeypairresponse.json @@ -0,0 +1,4 @@ +{ "createsshkeypairresponse" : { "keypair" : { + "name":"jclouds-keypair", + "fingerprint":"1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b", + "privatekey":"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDZo/EF4Ew1uEW0raz7vCs28lBwy0UKV2Xr606gaEgxO7h9mSXZ\n4x2K/KQ1NMnrbjppxGycLh9EKPWAO3ezFULAyuOZW4Fy+xRS8+3MAijxBJY/KBgl\nx5rJm2ILumRkTNkMlLGCSBb9SOqYRN1VpOy7kn3StzU9LdJ/snKVE2JLHQIDAQAB\nAoGBAMnL5okKRd9xcsBqYIAxIuiZmNhcwTErhEdRMOAukPGFbDSYsa3rldLvGdpz\njd2LoQG8rO/LHBZ429kASqZzyiV+NvcgH+tFNJSVAigjSICfhEKF9PY2TiAkrg7S\nGyJgAjpPWQc2sQh0dE8EPEtBiq4ibXfMTDmbs1d/vnfdwtQJAkEA+AX5Y+xgWj74\ndYETmNLyLhNZpftLizEfIYj7lCVhsbFwVb8jbM1m8n8bxwGjls1w/ico1CWcQna+\nUnAfA8kJvwJBAOCj0YgDKpYd0OLQhvI3212J9QcQpJEkDOTYiMwXNHCNMKRpoF47\nMPPX+GG8YzUiQAi9/OG4pDKCjzQWE/ebiiMCQQCssnQ5WICqtggIwYykr9VDseON\nSFIMpHJ5xkjumazRrqx6eDGxc8BH/6uWwRRoT7pqrVeniFyqhsX03u8pkpU/AkBj\nWfCcwBHArNUqy2EzlWKuvwogosq16oTNXbs60HR/5uIBhTnJE1K2NemDiGc0I77A\nXw6N4jS0piuhtLYGB8OTAkEA50abdbduXWcr62Z6E8G/6LNFaNg0uBuVgwSHtJMd\ndNeUtVDHQCHSf3tvxXTAtaB9PCnGOfgm/dyYWEMf3rMoHQ==\n-----END RSA PRIVATE KEY-----\n"} } } \ No newline at end of file diff --git a/apis/cloudstack/src/test/resources/listsshkeypairsresponse.json b/apis/cloudstack/src/test/resources/listsshkeypairsresponse.json index 3a61027b94..37d0deface 100644 --- a/apis/cloudstack/src/test/resources/listsshkeypairsresponse.json +++ b/apis/cloudstack/src/test/resources/listsshkeypairsresponse.json @@ -1 +1,2 @@ -{ "listsshkeypairsresponse" : { "count":1 ,"keypair" : [ {"name":"jclouds-keypair","fingerprint":"43:6a:bd:46:e0:3d:3a:8d:ab:69:25:bb:b9:ca:9d:17"} ] } } \ No newline at end of file +{ "listsshkeypairsresponse" : { "count":1 ,"sshkeypair" : [ + {"name":"jclouds-keypair","fingerprint":"1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b"} ] } } \ No newline at end of file diff --git a/apis/cloudstack/src/test/resources/registersshkeypairresponse.json b/apis/cloudstack/src/test/resources/registersshkeypairresponse.json new file mode 100644 index 0000000000..d9b52e5ec2 --- /dev/null +++ b/apis/cloudstack/src/test/resources/registersshkeypairresponse.json @@ -0,0 +1,2 @@ +{ "registersshkeypairresponse" : { "keypair" : + {"name":"jclouds-keypair","fingerprint":"2f:4e:95:2f:f3:80:ee:21:72:a8:b4:9c:57:01:0b:3a"} } } \ No newline at end of file From 6704bed6dd346b7d1d2ea85daef33957435a185d Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 11:41:26 +0200 Subject: [PATCH 2/7] Implemented getEncryptedPasswordForVirtualMachine --- .../cloudstack/domain/EncryptedPassword.java | 73 ++++++++++++++++ .../features/VirtualMachineAsyncClient.java | 7 +- .../features/VirtualMachineClient.java | 5 +- .../compute/CloudStackExperimentLiveTest.java | 19 ++-- .../VirtualMachineClientExpectTest.java | 87 +++++++++++++++++++ .../test/resources/getvmpasswordresponse.json | 2 + .../resources/registersshkeypairresponse.json | 2 +- 7 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java create mode 100644 apis/cloudstack/src/test/resources/getvmpasswordresponse.json diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java new file mode 100644 index 0000000000..8d9e43214c --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java @@ -0,0 +1,73 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.domain; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Andrei Savu + */ +public class EncryptedPassword implements Comparable { + + @SerializedName("encryptedpassword") + private String encryptedPassword; + + public EncryptedPassword(String encryptedPassword) { + this.encryptedPassword = encryptedPassword; + } + + EncryptedPassword() { /* for serializer */ } + + /** + * @return the string representation of the encrypted password + */ + public String getEncryptedPassword() { + return encryptedPassword; + } + + @Override + public int hashCode() { + return encryptedPassword.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EncryptedPassword that = (EncryptedPassword) o; + + if (encryptedPassword != null ? !encryptedPassword.equals(that.encryptedPassword) : that.encryptedPassword != null) + return false; + + return true; + } + + @Override + public String toString() { + return "EncryptedPassword{" + + "encryptedPassword='" + encryptedPassword + '\'' + + '}'; + } + + @Override + public int compareTo(EncryptedPassword arg0) { + return encryptedPassword.compareTo(arg0.getEncryptedPassword()); + } +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java index dedb0decbd..4809178b53 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java @@ -20,6 +20,7 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.AsyncCreateResponse; +import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.filters.AuthenticationFilter; import org.jclouds.cloudstack.options.DeployVirtualMachineOptions; @@ -120,13 +121,13 @@ public interface VirtualMachineAsyncClient { ListenableFuture resetPasswordForVirtualMachine(@QueryParam("id") long id); /** - * @see VirtualMachineClient#getPasswordForVirtualMachine + * @see VirtualMachineClient#getEncryptedPasswordForVirtualMachine */ @GET @QueryParams(keys = "command", values = "getVMPassword") - @SelectJson("jobid") + @SelectJson("password") @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture getPasswordForVirtualMachine(@QueryParam("id") long id); + ListenableFuture getEncryptedPasswordForVirtualMachine(@QueryParam("id") long id); /** * @see VirtualMachineClient#changeServiceForVirtualMachine diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java index fee4bab708..e460ad593d 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java @@ -19,6 +19,7 @@ package org.jclouds.cloudstack.features; import org.jclouds.cloudstack.domain.AsyncCreateResponse; +import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.options.DeployVirtualMachineOptions; import org.jclouds.cloudstack.options.ListVirtualMachinesOptions; @@ -117,9 +118,9 @@ public interface VirtualMachineClient { * * @param id * the ID of the virtual machine - * @return job id related to getting the encrypted password + * @return encrypted password */ - Long getPasswordForVirtualMachine(long id); + EncryptedPassword getEncryptedPasswordForVirtualMachine(long id); /** * Changes the service offering for a virtual machine. The virtual machine diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index e4eb8e4a8b..c69674eedc 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -19,7 +19,9 @@ package org.jclouds.cloudstack.compute; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; +import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.Network; +import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.domain.TrafficType; import org.jclouds.cloudstack.features.BaseCloudStackClientLiveTest; import org.jclouds.cloudstack.options.ListNetworksOptions; @@ -27,12 +29,10 @@ import org.jclouds.compute.RunNodesException; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.predicates.NodePredicates; -import org.jclouds.crypto.SshKeys; import org.testng.Assert; import org.testng.annotations.Test; import java.net.URI; -import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -128,13 +128,14 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { @Test public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException { - final Map sshKey = SshKeys.generate(); - final String publicKey = sshKey.get("public"); + // final Map sshKey = SshKeys.generate(); + // final String publicKey = sshKey.get("public"); String keyPairName = prefix + "-windows-keypair"; client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName); // client.getSSHKeyPairClient().registerSSHKeyPair(keyPairName, publicKey); - client.getSSHKeyPairClient().createSSHKeyPair(keyPairName); + + SshKeyPair keyPair = client.getSSHKeyPairClient().createSSHKeyPair(keyPairName); String group = prefix + "-windows-test"; Template template = computeContext.getComputeService().templateBuilder() @@ -147,11 +148,11 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { node = getOnlyElement(computeContext.getComputeService() .createNodesInGroup(group, 1, template)); - long jobId = client.getVirtualMachineClient() - .getPasswordForVirtualMachine(Long.parseLong(node.getId())); - // TODO: extrect the password from the async response + EncryptedPassword password = client.getVirtualMachineClient() + .getEncryptedPasswordForVirtualMachine(Long.parseLong(node.getId())); - Assert.fail("Password: ..."); + Assert.fail("Private key:" + keyPair.getPrivateKey() + "\nPassword: " + password.getEncryptedPassword()); + // TODO: decrypt the password } finally { if (node != null) { diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java new file mode 100644 index 0000000000..b12c15df2a --- /dev/null +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java @@ -0,0 +1,87 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.features; + +import com.google.common.collect.ImmutableMultimap; +import org.jclouds.cloudstack.CloudStackContext; +import org.jclouds.cloudstack.domain.EncryptedPassword; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.testng.annotations.Test; + +import java.net.URI; + +import static org.testng.Assert.assertEquals; + +/** + * Test the CloudStack VirtualMachineClientClient + * + * @author Andrei Savu + */ +@Test(groups = "unit", testName = "VirtualMachineClientExpectTest") +public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpectTest { + + public void testGetPasswordForVirtualMachineWhenResponseIs2xx() { + String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXgIBAAKBgQDnaPKhTNgw7qPJVp3qsT+7XhhAbip25a0AnUgq8Fb9LPcZk00p\n" + + "jm+m4JrKmDWKZWrHMNBhCNHMzvV9KrAXUMzL4s7mdEicbxTKratTYoyJM7a87bcZ\n" + + "xr+Gtoq4tm031Cix3LKyJUB0iSVU5V/Zx4QcaF5+FWcYMVI26x2Eaz+O7wIDAQAB\n" + + "AoGBAOI8sDkSL6pnJKmKjQkOEQjVjVAwZEOpd+HJ4uxX3DPY6huO7zlZj77Oh4ba\n" + + "GD4duK7VAmRbgwGAtHCSc2XYEN7ICnfkQrm+3Q8nS824Sz21WlzdCxKDFkDcC1wK\n" + + "RjE7SwXN1Kj8Xq8Vpf+z6OzHatSRZD85JM3u0/QCksOJTVIBAkEA9OpycYTuUYjC\n" + + "2pLrO5kkl0nIHbNPvFNZyle19AsHH0z/ClV8DiFtGQpwhqwCoWT0cTmSACPD/quA\n" + + "hdc2mvV+4QJBAPHiBi/7qDpJldLLvK5ALbn1yRaPSDXLccvFV4FkSS9b/2+mOM2a\n" + + "8JkolVCzImxAm0ZZDZeAGKJj1RZDsMIP188CQCfZKWus7DWZ4dI8S0e0IA75czTZ\n" + + "4uRKT3arlLAzRyJhnbFpvThzWdPULgDLZdYqndb6PfYF27LI5q1gGcNWpCECQQCB\n" + + "r8/ldiZyafW8eaQGQT7DD7brM5Nh1FyFBp+uLljW3ZqNADBAfKw3Uf0MsZ7pL5KR\n" + + "GzogWnvaxXAAafahdeEdAkEAzBT+UcxFmcPUO33PnuuiX5KIqThc6aHjjH5O7yzO\n" + + "m4Et9JwQiSgcPBmNY5NKPgmcpvUi9jDylSUV0VUu436RpQ==\n" + + "-----END RSA PRIVATE KEY-----"; + + VirtualMachineClient client = requestSendsResponse( + HttpRequest.builder() + .method("GET") + .endpoint( + URI.create("http://localhost:8080/client/api?response=json&" + + "command=getVMPassword&id=1&apiKey=identity&signature=SVA2r1KRj4yG03rATMLPZWS%2BKnw%3D")) + .headers( + ImmutableMultimap.builder() + .put("Accept", "application/json") + .build()) + .build(), + HttpResponse.builder() + .statusCode(200) + .payload(payloadFromResource("/getvmpasswordresponse.json")) + .build()); + + EncryptedPassword actual = client.getEncryptedPasswordForVirtualMachine(1L); + EncryptedPassword expected = new EncryptedPassword("EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\n" + + "pQ0LZlQBZDd5/ac0NSoM6tAX3H30pYxNw4t2f9u8aJ48oOEvufgGxTTHnM9qHXD04lt+Ouql6i2q\r\n" + + "HxBqCxFkMZEla3LFieE=\r\n"); + + assertEquals(actual, expected); + + // TODO: decrypt the returned password using the private key + } + + @Override + protected VirtualMachineClient clientFrom(CloudStackContext context) { + return context.getProviderSpecificContext().getApi().getVirtualMachineClient(); + } +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/resources/getvmpasswordresponse.json b/apis/cloudstack/src/test/resources/getvmpasswordresponse.json new file mode 100644 index 0000000000..693018f15b --- /dev/null +++ b/apis/cloudstack/src/test/resources/getvmpasswordresponse.json @@ -0,0 +1,2 @@ +{ "getvmpasswordresponse" : { "password" : + {"encryptedpassword":"EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\npQ0LZlQBZDd5/ac0NSoM6tAX3H30pYxNw4t2f9u8aJ48oOEvufgGxTTHnM9qHXD04lt+Ouql6i2q\r\nHxBqCxFkMZEla3LFieE=\r\n"} } } \ No newline at end of file diff --git a/apis/cloudstack/src/test/resources/registersshkeypairresponse.json b/apis/cloudstack/src/test/resources/registersshkeypairresponse.json index d9b52e5ec2..1c3f35e418 100644 --- a/apis/cloudstack/src/test/resources/registersshkeypairresponse.json +++ b/apis/cloudstack/src/test/resources/registersshkeypairresponse.json @@ -1,2 +1,2 @@ { "registersshkeypairresponse" : { "keypair" : - {"name":"jclouds-keypair","fingerprint":"2f:4e:95:2f:f3:80:ee:21:72:a8:b4:9c:57:01:0b:3a"} } } \ No newline at end of file + {"name":"jclouds-keypair","fingerprint":"8f:f1:91:2d:b1:a8:51:f1:79:cf:c4:31:c4:14:9d:81"} } } \ No newline at end of file From 88465a3eba7ef2ebbcdad975cb66d0b54ea5310b Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 12:28:06 +0200 Subject: [PATCH 3/7] Added password decryption functionality for Windows hosts --- .../EncryptedPasswordAndPrivateKey.java | 103 ++++++++++++++++++ ...dowsLoginCredentialsFromEncryptedData.java | 61 +++++++++++ .../compute/CloudStackExperimentLiveTest.java | 32 ++++-- .../VirtualMachineClientExpectTest.java | 14 ++- 4 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java new file mode 100644 index 0000000000..51b3a116af --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java @@ -0,0 +1,103 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.domain; + +/** + * @author Andrei Savu + */ +public class EncryptedPasswordAndPrivateKey { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String encryptedPassword; + private String privateKey; + + public Builder encryptedPassword(String encryptedPassword) { + this.encryptedPassword = encryptedPassword; + return this; + } + + public Builder encryptedPassword(EncryptedPassword password) { + return encryptedPassword(password.getEncryptedPassword()); + } + + public Builder privateKey(String privateKey) { + this.privateKey = privateKey; + return this; + } + + public EncryptedPasswordAndPrivateKey build() { + return new EncryptedPasswordAndPrivateKey(encryptedPassword, privateKey); + } + } + + private final String encryptedPassword; + private final String privateKey; + + public EncryptedPasswordAndPrivateKey(String encryptedPassword, String privateKey) { + this.encryptedPassword = encryptedPassword; + this.privateKey = privateKey; + } + + /** + * @return the encrypted password String representation + */ + public String getEncryptedPassword() { + return encryptedPassword; + } + + /** + * @return get the string representation of the private key + */ + public String getPrivateKey() { + return privateKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EncryptedPasswordAndPrivateKey that = (EncryptedPasswordAndPrivateKey) o; + + if (encryptedPassword != null ? !encryptedPassword.equals(that.encryptedPassword) : that.encryptedPassword != null) + return false; + if (privateKey != null ? !privateKey.equals(that.privateKey) : that.privateKey != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = encryptedPassword != null ? encryptedPassword.hashCode() : 0; + result = 31 * result + (privateKey != null ? privateKey.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "EncryptedPasswordAndPrivateKey{" + + "encryptedPassword='" + encryptedPassword + '\'' + + ", privateKey='" + privateKey + '\'' + + '}'; + } +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java new file mode 100644 index 0000000000..eb618740c6 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java @@ -0,0 +1,61 @@ +package org.jclouds.cloudstack.functions; + +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; +import org.jclouds.crypto.Crypto; +import org.jclouds.crypto.Pems; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.encryption.internal.Base64; +import org.jclouds.javax.annotation.Nullable; + +import javax.crypto.Cipher; +import java.nio.charset.Charset; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.KeySpec; + +/** + * Given an encrypted Windows Administrator password and the decryption key, return a LoginCredentials instance. + * + * @author Richard Downer, Andrei Savu + */ +@Singleton +public class WindowsLoginCredentialsFromEncryptedData implements Function { + + private final Crypto crypto; + + @Inject + public WindowsLoginCredentialsFromEncryptedData(Crypto crypto) { + this.crypto = crypto; + } + + @Override + public LoginCredentials apply(@Nullable EncryptedPasswordAndPrivateKey dataAndKey) { + if (dataAndKey == null) + return null; + + try { + KeySpec keySpec = Pems.privateKeySpec(dataAndKey.getPrivateKey()); + KeyFactory kf = crypto.rsaKeyFactory(); + PrivateKey privKey = kf.generatePrivate(keySpec); + + Cipher cipher = crypto.cipher("RSA/NONE/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privKey); + byte[] cipherText = Base64.decode(dataAndKey.getEncryptedPassword()); + byte[] plainText = cipher.doFinal(cipherText); + String password = new String(plainText, Charset.forName("ASCII")); + + return LoginCredentials.builder() + .user("Administrator") + .password(password) + .noPrivateKey() + .build(); + + } catch (Exception e) { + throw Throwables.propagate(e); + } + } +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index c69674eedc..fcd3aafa00 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -20,19 +20,24 @@ package org.jclouds.cloudstack.compute; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; import org.jclouds.cloudstack.domain.EncryptedPassword; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; import org.jclouds.cloudstack.domain.Network; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.domain.TrafficType; import org.jclouds.cloudstack.features.BaseCloudStackClientLiveTest; +import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData; import org.jclouds.cloudstack.options.ListNetworksOptions; import org.jclouds.compute.RunNodesException; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.predicates.NodePredicates; -import org.testng.Assert; +import org.jclouds.crypto.Crypto; +import org.jclouds.encryption.bouncycastle.BouncyCastleCrypto; import org.testng.annotations.Test; import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -43,9 +48,9 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.newTreeSet; import static org.jclouds.cloudstack.options.CreateNetworkOptions.Builder.vlan; import static org.jclouds.cloudstack.options.ListNetworkOfferingsOptions.Builder.specifyVLAN; +import static org.testng.Assert.assertEquals; /** - * * @author Adrian Cole */ @Test(groups = "live", testName = "CloudStackExperimentLiveTest") @@ -66,7 +71,7 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { // Warning: the vlan id is not set in the response - using an workaround URI broadcastUri = URI.create("vlan://" + vlanId); - for(Network net : networks) { + for (Network net : networks) { if (broadcastUri.equals(net.getBroadcastURI())) { long jobId = domainAdminContext.getApi().getNetworkClient().deleteNetwork(net.getId()); adminJobComplete.apply(jobId); @@ -98,14 +103,14 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { // find a network offering that supports vlans in our zone long offeringId = get( - context.getApi().getOfferingClient().listNetworkOfferings(specifyVLAN(true).zoneId(zoneId)), 0).getId(); + context.getApi().getOfferingClient().listNetworkOfferings(specifyVLAN(true).zoneId(zoneId)), 0).getId(); // create an arbitrary network network = domainAdminContext.getApi() - .getNetworkClient() + .getNetworkClient() // startIP/endIP/netmask/gateway must be specified together - .createNetworkInZone(zoneId, offeringId, group, group, - vlan(vlanId).startIP("192.168.1.2").netmask("255.255.255.0").gateway("192.168.1.1")); + .createNetworkInZone(zoneId, offeringId, group, group, + vlan(vlanId).startIP("192.168.1.2").netmask("255.255.255.0").gateway("192.168.1.1")); // set options to specify this network id template.getOptions().as(CloudStackTemplateOptions.class).networkId(network.getId()); @@ -127,7 +132,8 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { } @Test - public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException { + public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() + throws RunNodesException, NoSuchAlgorithmException, CertificateException { // final Map sshKey = SshKeys.generate(); // final String publicKey = sshKey.get("public"); @@ -147,12 +153,16 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { try { node = getOnlyElement(computeContext.getComputeService() .createNodesInGroup(group, 1, template)); - + EncryptedPassword password = client.getVirtualMachineClient() .getEncryptedPasswordForVirtualMachine(Long.parseLong(node.getId())); - Assert.fail("Private key:" + keyPair.getPrivateKey() + "\nPassword: " + password.getEncryptedPassword()); - // TODO: decrypt the password + Crypto crypto = new BouncyCastleCrypto(); + WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); + + assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() + .encryptedPassword(password).privateKey(keyPair.getPrivateKey()).build()) + .getPassword(), "bX7vvptvw"); } finally { if (node != null) { diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java index b12c15df2a..cb25ff5787 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java @@ -21,11 +21,17 @@ package org.jclouds.cloudstack.features; import com.google.common.collect.ImmutableMultimap; import org.jclouds.cloudstack.CloudStackContext; import org.jclouds.cloudstack.domain.EncryptedPassword; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; +import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData; +import org.jclouds.crypto.Crypto; +import org.jclouds.encryption.bouncycastle.BouncyCastleCrypto; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.testng.annotations.Test; import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import static org.testng.Assert.assertEquals; @@ -37,7 +43,7 @@ import static org.testng.Assert.assertEquals; @Test(groups = "unit", testName = "VirtualMachineClientExpectTest") public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpectTest { - public void testGetPasswordForVirtualMachineWhenResponseIs2xx() { + public void testGetPasswordForVirtualMachineWhenResponseIs2xx() throws NoSuchAlgorithmException, CertificateException { String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXgIBAAKBgQDnaPKhTNgw7qPJVp3qsT+7XhhAbip25a0AnUgq8Fb9LPcZk00p\n" + "jm+m4JrKmDWKZWrHMNBhCNHMzvV9KrAXUMzL4s7mdEicbxTKratTYoyJM7a87bcZ\n" + @@ -77,7 +83,11 @@ public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpe assertEquals(actual, expected); - // TODO: decrypt the returned password using the private key + Crypto crypto = new BouncyCastleCrypto(); + WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); + + assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() + .encryptedPassword(actual).privateKey(privateKey).build()).getPassword(), "bX7vvptvw"); } @Override From ebfecc672a73f96f3ed2109bccbcc23b4fa4c309 Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 14:25:04 +0200 Subject: [PATCH 4/7] Added a few more tests and update ReEncodeQueryWithDefaultURLEncoderTest so that it fails --- .../features/SSHKeyPairAsyncClient.java | 2 + .../ReEncodeQueryWithDefaultURLEncoder.java | 57 +++++++++++++++++ .../compute/CloudStackExperimentLiveTest.java | 2 +- ...eEncodeQueryWithJavaNetURLEncoderTest.java | 63 +++++++++++++++++++ .../jclouds/http/utils/ModifyRequestTest.java | 38 +++++++---- .../java/org/jclouds/util/Strings2Test.java | 19 ++++-- 6 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java index 9a8e4f589e..04c14bd4ba 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java @@ -21,6 +21,7 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.filters.AuthenticationFilter; +import org.jclouds.cloudstack.filters.ReEncodeQueryWithDefaultURLEncoder; import org.jclouds.cloudstack.options.ListSSHKeyPairsOptions; import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.OnlyElement; @@ -65,6 +66,7 @@ public interface SSHKeyPairAsyncClient { @QueryParams(keys = "command", values = "registerSSHKeyPair") @SelectJson("keypair") @Consumes(MediaType.APPLICATION_JSON) + @RequestFilters(ReEncodeQueryWithDefaultURLEncoder.class) ListenableFuture registerSSHKeyPair(@QueryParam("name") String name, @QueryParam("publickey") String publicKey); /** diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java new file mode 100644 index 0000000000..c221c89077 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java @@ -0,0 +1,57 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.filters; + +import com.google.common.collect.Multimap; +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.utils.ModifyRequest; + +import javax.ws.rs.core.UriBuilder; + +import static com.google.common.collect.Iterables.getOnlyElement; + +/** + * By default, jclouds controls encoding based on rules which are different + * + * @author Adrian Cole + */ +public class ReEncodeQueryWithDefaultURLEncoder implements HttpRequestFilter { + private final Provider builders; + + @Inject + public ReEncodeQueryWithDefaultURLEncoder(Provider builders) { + this.builders = builders; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + UriBuilder builder = builders.get(); + builder.uri(request.getEndpoint()); + Multimap map = ModifyRequest.parseQueryToMap(request.getEndpoint().getQuery()); + builder.replaceQuery(""); + for (String key : map.keySet()) + builder.queryParam(key, getOnlyElement(map.get(key))); + return request.toBuilder().endpoint(builder.build()).build(); + } + +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index fcd3aafa00..641b1259c3 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -131,7 +131,7 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { } } - @Test + @Test(enabled = false) public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException, NoSuchAlgorithmException, CertificateException { // final Map sshKey = SshKeys.generate(); diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java new file mode 100644 index 0000000000..0d1b58d7a4 --- /dev/null +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java @@ -0,0 +1,63 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.cloudstack.filters; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.sun.jersey.api.uri.UriBuilderImpl; +import org.jclouds.http.HttpRequest; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.net.URLEncoder; + +import static org.testng.Assert.assertEquals; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ReEncodeQueryWithJavaNetURLEncoder") +public class ReEncodeQueryWithJavaNetURLEncoderTest { + + @Test + public void testReUrlEncode() { + String input = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + + String defaultJcloudsEncodedRequest = "http://localhost?foo=" + Strings2.urlEncode(input); + @SuppressWarnings("deprecation") + String defaultEncodedRequest = "http://localhost?foo=" + URLEncoder.encode(input); + assert !defaultJcloudsEncodedRequest.equals(defaultEncodedRequest); + + HttpRequest request = new HttpRequest("GET", URI.create("http://localhost?foo=" + Strings2.urlEncode(input))); + request = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(UriBuilder.class).to(UriBuilderImpl.class); + } + + }).getInstance(ReEncodeQueryWithDefaultURLEncoder.class).filter(request); + assertEquals(request.getEndpoint().toASCIIString(), "http://localhost?foo=" + URLEncoder.encode(input)); + } +} diff --git a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java index fd9985a209..fa9fd3409d 100644 --- a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java +++ b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java @@ -18,22 +18,23 @@ */ package org.jclouds.http.utils; -import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap; -import static org.testng.Assert.assertEquals; - -import java.net.URI; - -import javax.ws.rs.core.MediaType; - -import org.jclouds.http.HttpRequest; -import org.jclouds.io.Payload; -import org.jclouds.io.Payloads; -import org.testng.annotations.Test; - import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import javax.ws.rs.core.MediaType; +import java.net.URI; +import java.util.Set; + +import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap; +import static org.testng.Assert.assertEquals; /** * @author Adrian Cole @@ -130,4 +131,17 @@ public class ModifyRequestTest { assert valueForSig.equals("123") : "Expected the value for 'v' to be '123', found: " + valueForSig; } + @Test + public void testParseQueryEncodedWithDefaultJavaEncoder() { + String key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + String queryString = "publickey=" + Strings2.urlEncode(key); + Multimap parsedMap = parseQueryToMap(queryString); + + Set expected = ImmutableSet.of(Strings2.urlEncode(key)); + assertEquals(parsedMap.get("publickey"), expected); + } + } diff --git a/core/src/test/java/org/jclouds/util/Strings2Test.java b/core/src/test/java/org/jclouds/util/Strings2Test.java index c94a4c17aa..4e0b32085b 100644 --- a/core/src/test/java/org/jclouds/util/Strings2Test.java +++ b/core/src/test/java/org/jclouds/util/Strings2Test.java @@ -18,11 +18,12 @@ */ package org.jclouds.util; -import static org.testng.Assert.assertEquals; - +import com.google.common.collect.ImmutableMap; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableMap; +import static org.jclouds.util.Strings2.urlDecode; +import static org.jclouds.util.Strings2.urlEncode; +import static org.testng.Assert.assertEquals; /** * @author Adrian Cole @@ -36,13 +37,21 @@ public class Strings2Test { } public void testNoDoubleEncode() { - assertEquals(Strings2.urlEncode("/read-tests/%73%6f%6d%65%20%66%69%6c%65", '/'), + assertEquals(urlEncode("/read-tests/%73%6f%6d%65%20%66%69%6c%65", '/'), "/read-tests/%73%6f%6d%65%20%66%69%6c%65"); - assertEquals(Strings2.urlEncode("/read-tests/ tep", '/'), "/read-tests/%20tep"); + assertEquals(urlEncode("/read-tests/ tep", '/'), "/read-tests/%20tep"); } public void testReplaceTokens() { assertEquals(Strings2.replaceTokens("hello {where}", ImmutableMap.of("where", "world")), "hello world"); } + public void testUrlEncodeDecodeShouldGiveTheSameString() { + String actual = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + assertEquals(actual, urlDecode(urlEncode(actual))); + } + } From 5856f466e4eac84aebead938a9b6cdf83ae5922c Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 16:04:45 +0200 Subject: [PATCH 5/7] Fixed double query parameter & URL encoding / decoding bugs --- .../cloudstack/filters/QuerySigner.java | 33 ++++++------ .../ReEncodeQueryWithDefaultURLEncoder.java | 2 +- .../features/SSHKeyPairAsyncClientTest.java | 15 +++--- .../features/SSHKeyPairClientExpectTest.java | 6 +-- ...eEncodeQueryWithJavaNetURLEncoderTest.java | 4 +- .../org/jclouds/http/utils/ModifyRequest.java | 30 +++++------ .../jclouds/http/utils/ModifyRequestTest.java | 51 ++++++++++--------- 7 files changed, 72 insertions(+), 69 deletions(-) diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/QuerySigner.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/QuerySigner.java index c03a1e47e7..1e8573f595 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/QuerySigner.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/QuerySigner.java @@ -18,23 +18,16 @@ */ package org.jclouds.cloudstack.filters; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.util.Map.Entry; - -import javax.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; -import javax.ws.rs.core.UriBuilder; - +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Multimap; import org.jclouds.Constants; import org.jclouds.crypto.Crypto; import org.jclouds.crypto.CryptoStreams; import org.jclouds.http.HttpException; import org.jclouds.http.HttpRequest; -import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpUtils; import org.jclouds.http.internal.SignatureWire; import org.jclouds.http.utils.ModifyRequest; @@ -43,11 +36,15 @@ import org.jclouds.logging.Logger; import org.jclouds.rest.RequestSigner; import org.jclouds.util.Strings2; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Multimap; +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.ws.rs.core.UriBuilder; +import java.util.Map.Entry; + +import static com.google.common.base.Preconditions.checkNotNull; /** * @@ -85,7 +82,7 @@ public class QuerySigner implements AuthenticationFilter, RequestSigner { public HttpRequest filter(HttpRequest request) throws HttpException { checkNotNull(request, "request must be present"); - Multimap decodedParams = ModifyRequest.parseQueryToMap(request.getEndpoint().getQuery()); + Multimap decodedParams = ModifyRequest.parseQueryToMap(request.getEndpoint().getRawQuery()); addSigningParams(decodedParams); String stringToSign = createStringToSign(request, decodedParams); String signature = sign(stringToSign); diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java index c221c89077..06058535a2 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java @@ -47,7 +47,7 @@ public class ReEncodeQueryWithDefaultURLEncoder implements HttpRequestFilter { public HttpRequest filter(HttpRequest request) throws HttpException { UriBuilder builder = builders.get(); builder.uri(request.getEndpoint()); - Multimap map = ModifyRequest.parseQueryToMap(request.getEndpoint().getQuery()); + Multimap map = ModifyRequest.parseQueryToMap(request.getEndpoint().getRawQuery()); builder.replaceQuery(""); for (String key : map.keySet()) builder.queryParam(key, getOnlyElement(map.get(key))); diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClientTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClientTest.java index 6ddc763d5b..c60dcfc94d 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClientTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClientTest.java @@ -18,12 +18,9 @@ */ package org.jclouds.cloudstack.features; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URLEncoder; - import com.google.common.base.Functions; import com.google.inject.TypeLiteral; +import org.jclouds.cloudstack.filters.QuerySigner; import org.jclouds.cloudstack.options.ListSSHKeyPairsOptions; import org.jclouds.crypto.SshKeys; import org.jclouds.functions.IdentityFunction; @@ -37,6 +34,12 @@ import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.testng.annotations.Test; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URLEncoder; + +import static org.testng.Assert.assertEquals; + /** * Tests behavior of {@code SSHKeyPairAsyncClient} * @@ -112,7 +115,8 @@ public class SSHKeyPairAsyncClientTest extends BaseCloudStackAsyncClientTestbuilder() .put("Accept", "application/json") diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java index 0d1b58d7a4..c4c9f13e43 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java @@ -21,6 +21,7 @@ package org.jclouds.cloudstack.filters; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.sun.jersey.api.uri.UriBuilderImpl; +import com.sun.jersey.api.uri.UriComponent; import org.jclouds.http.HttpRequest; import org.jclouds.util.Strings2; import org.testng.annotations.Test; @@ -58,6 +59,7 @@ public class ReEncodeQueryWithJavaNetURLEncoderTest { } }).getInstance(ReEncodeQueryWithDefaultURLEncoder.class).filter(request); - assertEquals(request.getEndpoint().toASCIIString(), "http://localhost?foo=" + URLEncoder.encode(input)); + assertEquals(request.getEndpoint().toASCIIString(), "http://localhost?foo=" + + UriComponent.encode(input, UriComponent.Type.QUERY_PARAM)); } } diff --git a/core/src/main/java/org/jclouds/http/utils/ModifyRequest.java b/core/src/main/java/org/jclouds/http/utils/ModifyRequest.java index 522e16ef29..7dc82f3da4 100644 --- a/core/src/main/java/org/jclouds/http/utils/ModifyRequest.java +++ b/core/src/main/java/org/jclouds/http/utils/ModifyRequest.java @@ -18,20 +18,6 @@ */ package org.jclouds.http.utils; -import static com.google.common.base.Preconditions.checkNotNull; -import static org.jclouds.io.Payloads.newUrlEncodedFormPayload; - -import java.net.URI; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Map; - -import org.jclouds.javax.annotation.Nullable; -import javax.ws.rs.core.UriBuilder; - -import org.jclouds.http.HttpRequest; -import org.jclouds.util.Strings2; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; @@ -39,6 +25,18 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; +import org.jclouds.http.HttpRequest; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.util.Strings2; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.io.Payloads.newUrlEncodedFormPayload; /** * @@ -159,7 +157,7 @@ public class ModifyRequest { else map.put(in, null); } else { - String[] parts = Strings2.urlDecode(in).split("&"); + String[] parts = in.split("&"); for (String part : parts) { parseKeyValueFromStringToMap(part, map); } @@ -172,7 +170,7 @@ public class ModifyRequest { int indexOfFirstEquals = stringToParse.indexOf('='); String key = indexOfFirstEquals == -1 ? stringToParse : stringToParse.substring(0, indexOfFirstEquals); String value = indexOfFirstEquals == -1 ? null : stringToParse.substring(indexOfFirstEquals + 1); - map.put(key, value); + map.put(key, Strings2.urlDecode(value)); } public static String makeQueryLine(Multimap params, diff --git a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java index fa9fd3409d..d38676fe84 100644 --- a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java +++ b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java @@ -46,56 +46,56 @@ public class ModifyRequestTest { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")).build(); assertEquals(ModifyRequest.endpoint(request, URI.create("http://bar")), HttpRequest.builder().method("GET") - .endpoint(URI.create("http://bar")).build()); + .endpoint(URI.create("http://bar")).build()); } public void testReplaceHeader() { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "bar")).build(); + .headers(ImmutableMultimap.of("foo", "bar")).build(); assertEquals( - ModifyRequest.replaceHeader(request, "foo", "baz"), - HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "baz")).build()); + ModifyRequest.replaceHeader(request, "foo", "baz"), + HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) + .headers(ImmutableMultimap.of("foo", "baz")).build()); } public void testRemoveHeader() { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "bar")).build(); + .headers(ImmutableMultimap.of("foo", "bar")).build(); assertEquals(ModifyRequest.removeHeader(request, "foo"), - HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")).build()); + HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")).build()); } public void testReplaceHeaders() { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "bar", "rabbit", "tree")).build(); + .headers(ImmutableMultimap.of("foo", "bar", "rabbit", "tree")).build(); assertEquals( - ModifyRequest.replaceHeaders(request, - ImmutableMultimap.of("foo", "bar", "rabbit", "robot", "robert", "baz")), - HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "bar", "rabbit", "robot", "robert", "baz")).build()); + ModifyRequest.replaceHeaders(request, + ImmutableMultimap.of("foo", "bar", "rabbit", "robot", "robert", "baz")), + HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) + .headers(ImmutableMultimap.of("foo", "bar", "rabbit", "robot", "robert", "baz")).build()); } public void testPutHeadersAddsAnotherValue() { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap.of("foo", "bar")).build(); + .headers(ImmutableMultimap.of("foo", "bar")).build(); assertEquals( - ModifyRequest.putHeaders(request, ImmutableMultimap.of("foo", "baz")), - HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .headers(ImmutableMultimap. builder().put("foo", "bar").put("foo", "baz").build()) - .build()); + ModifyRequest.putHeaders(request, ImmutableMultimap.of("foo", "baz")), + HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) + .headers(ImmutableMultimap.builder().put("foo", "bar").put("foo", "baz").build()) + .build()); } public void testPutFormParamsAddsAnotherValue() { HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://foo")) - .payload(Payloads.newStringPayload("foo=bar")).build(); + .payload(Payloads.newStringPayload("foo=bar")).build(); Payload payload = Payloads.newStringPayload("foo=bar&foo=baz"); payload.getContentMetadata().setContentType(MediaType.APPLICATION_FORM_URLENCODED); assertEquals(ModifyRequest.putFormParams(request, ImmutableMultimap.of("foo", "baz")), HttpRequest.builder() - .method("GET").endpoint(URI.create("http://foo")).payload(payload).build()); + .method("GET").endpoint(URI.create("http://foo")).payload(payload).build()); } public void testParseBase64InForm() { @@ -106,8 +106,8 @@ public class ModifyRequestTest { expects.put("Value", "dGVzdA=="); expects.put("InstanceId", "1"); assertEquals( - expects, - parseQueryToMap("Version=2010-06-15&Action=ModifyInstanceAttribute&Attribute=userData&Value=dGVzdA%3D%3D&InstanceId=1")); + parseQueryToMap("Version=2010-06-15&Action=ModifyInstanceAttribute&Attribute=userData&Value=dGVzdA%3D%3D&InstanceId=1"), + expects); } @Test @@ -137,10 +137,13 @@ public class ModifyRequestTest { "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; - String queryString = "publickey=" + Strings2.urlEncode(key); - Multimap parsedMap = parseQueryToMap(queryString); - Set expected = ImmutableSet.of(Strings2.urlEncode(key)); + Set expected = ImmutableSet.of(key); + + Multimap parsedMap = parseQueryToMap("a=1&b=1+2&publickey=" + Strings2.urlEncode(key)); + assertEquals(parsedMap.get("publickey"), expected); + + parsedMap = parseQueryToMap("publickey=" + Strings2.urlEncode(key)); assertEquals(parsedMap.get("publickey"), expected); } From 7ea0e8629fcda8492fed521cf9f440e2379c9f7a Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 16:16:26 +0200 Subject: [PATCH 6/7] Fixed and improved live tests for SSHKeyPairClient --- .../cloudstack/features/SSHKeyPairClientLiveTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java index dfd61fcf01..e6f38c08d4 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SSHKeyPairClientLiveTest.java @@ -28,6 +28,8 @@ import java.util.Map; import java.util.Set; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; /** * Tests behavior of {@code SSHKeyPairClient} @@ -58,6 +60,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { @Test public void testCreateDeleteSSHKeyPair() { sshKeyPair = client.getSSHKeyPairClient().createSSHKeyPair(keyPairName); + assertNotNull(sshKeyPair.getPrivateKey()); checkSSHKeyPair(sshKeyPair); client.getSSHKeyPairClient().deleteSSHKeyPair(sshKeyPair.getName()); @@ -73,6 +76,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { final String publicKey = sshKey.get("public"); sshKeyPair = client.getSSHKeyPairClient().registerSSHKeyPair(keyPairName, publicKey); + assertNull(sshKeyPair.getPrivateKey()); checkSSHKeyPair(sshKeyPair); client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName); @@ -84,7 +88,8 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest { protected void checkSSHKeyPair(SshKeyPair pair) { assert pair.getName() != null : pair; - assertEquals(pair.toString(), client.getSSHKeyPairClient().getSSHKeyPair(pair.getName()).toString()); + assertEquals(pair.getFingerprint(), + client.getSSHKeyPairClient().getSSHKeyPair(pair.getName()).getFingerprint()); } } From 5dbaf0ceb22a6bb8fb9735e098ebac8e4f97c3c0 Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Thu, 9 Feb 2012 17:44:12 +0200 Subject: [PATCH 7/7] Replace EncryptedPassword with a plain old String --- .../cloudstack/domain/EncryptedPassword.java | 73 ------------------- .../EncryptedPasswordAndPrivateKey.java | 27 ------- .../features/VirtualMachineAsyncClient.java | 5 +- .../features/VirtualMachineClient.java | 3 +- .../compute/CloudStackExperimentLiveTest.java | 9 +-- .../VirtualMachineClientExpectTest.java | 11 ++- 6 files changed, 12 insertions(+), 116 deletions(-) delete mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java deleted file mode 100644 index 8d9e43214c..0000000000 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPassword.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds 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.jclouds.cloudstack.domain; - -import com.google.gson.annotations.SerializedName; - -/** - * @author Andrei Savu - */ -public class EncryptedPassword implements Comparable { - - @SerializedName("encryptedpassword") - private String encryptedPassword; - - public EncryptedPassword(String encryptedPassword) { - this.encryptedPassword = encryptedPassword; - } - - EncryptedPassword() { /* for serializer */ } - - /** - * @return the string representation of the encrypted password - */ - public String getEncryptedPassword() { - return encryptedPassword; - } - - @Override - public int hashCode() { - return encryptedPassword.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - EncryptedPassword that = (EncryptedPassword) o; - - if (encryptedPassword != null ? !encryptedPassword.equals(that.encryptedPassword) : that.encryptedPassword != null) - return false; - - return true; - } - - @Override - public String toString() { - return "EncryptedPassword{" + - "encryptedPassword='" + encryptedPassword + '\'' + - '}'; - } - - @Override - public int compareTo(EncryptedPassword arg0) { - return encryptedPassword.compareTo(arg0.getEncryptedPassword()); - } -} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java index 51b3a116af..40ecd2a476 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java @@ -23,33 +23,6 @@ package org.jclouds.cloudstack.domain; */ public class EncryptedPasswordAndPrivateKey { - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private String encryptedPassword; - private String privateKey; - - public Builder encryptedPassword(String encryptedPassword) { - this.encryptedPassword = encryptedPassword; - return this; - } - - public Builder encryptedPassword(EncryptedPassword password) { - return encryptedPassword(password.getEncryptedPassword()); - } - - public Builder privateKey(String privateKey) { - this.privateKey = privateKey; - return this; - } - - public EncryptedPasswordAndPrivateKey build() { - return new EncryptedPasswordAndPrivateKey(encryptedPassword, privateKey); - } - } - private final String encryptedPassword; private final String privateKey; diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java index 4809178b53..d44b694dd9 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineAsyncClient.java @@ -20,7 +20,6 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.AsyncCreateResponse; -import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.filters.AuthenticationFilter; import org.jclouds.cloudstack.options.DeployVirtualMachineOptions; @@ -125,9 +124,9 @@ public interface VirtualMachineAsyncClient { */ @GET @QueryParams(keys = "command", values = "getVMPassword") - @SelectJson("password") + @SelectJson("encryptedpassword") @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture getEncryptedPasswordForVirtualMachine(@QueryParam("id") long id); + ListenableFuture getEncryptedPasswordForVirtualMachine(@QueryParam("id") long id); /** * @see VirtualMachineClient#changeServiceForVirtualMachine diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java index e460ad593d..3414ee062b 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VirtualMachineClient.java @@ -19,7 +19,6 @@ package org.jclouds.cloudstack.features; import org.jclouds.cloudstack.domain.AsyncCreateResponse; -import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.options.DeployVirtualMachineOptions; import org.jclouds.cloudstack.options.ListVirtualMachinesOptions; @@ -120,7 +119,7 @@ public interface VirtualMachineClient { * the ID of the virtual machine * @return encrypted password */ - EncryptedPassword getEncryptedPasswordForVirtualMachine(long id); + String getEncryptedPasswordForVirtualMachine(long id); /** * Changes the service offering for a virtual machine. The virtual machine diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index 641b1259c3..923fb916b2 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -19,7 +19,6 @@ package org.jclouds.cloudstack.compute; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; -import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; import org.jclouds.cloudstack.domain.Network; import org.jclouds.cloudstack.domain.SshKeyPair; @@ -154,15 +153,15 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { node = getOnlyElement(computeContext.getComputeService() .createNodesInGroup(group, 1, template)); - EncryptedPassword password = client.getVirtualMachineClient() + String encryptedPassword = client.getVirtualMachineClient() .getEncryptedPasswordForVirtualMachine(Long.parseLong(node.getId())); Crypto crypto = new BouncyCastleCrypto(); WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); - assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() - .encryptedPassword(password).privateKey(keyPair.getPrivateKey()).build()) - .getPassword(), "bX7vvptvw"); + assertEquals(passwordDecrypt.apply( + new EncryptedPasswordAndPrivateKey(encryptedPassword, keyPair.getPrivateKey())).getPassword(), + "bX7vvptvw"); } finally { if (node != null) { diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java index cb25ff5787..495a6eb4a8 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java @@ -20,7 +20,6 @@ package org.jclouds.cloudstack.features; import com.google.common.collect.ImmutableMultimap; import org.jclouds.cloudstack.CloudStackContext; -import org.jclouds.cloudstack.domain.EncryptedPassword; import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData; import org.jclouds.crypto.Crypto; @@ -76,18 +75,18 @@ public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpe .payload(payloadFromResource("/getvmpasswordresponse.json")) .build()); - EncryptedPassword actual = client.getEncryptedPasswordForVirtualMachine(1L); - EncryptedPassword expected = new EncryptedPassword("EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\n" + + String actual = client.getEncryptedPasswordForVirtualMachine(1L); + String expected = "EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\n" + "pQ0LZlQBZDd5/ac0NSoM6tAX3H30pYxNw4t2f9u8aJ48oOEvufgGxTTHnM9qHXD04lt+Ouql6i2q\r\n" + - "HxBqCxFkMZEla3LFieE=\r\n"); + "HxBqCxFkMZEla3LFieE=\r\n"; assertEquals(actual, expected); Crypto crypto = new BouncyCastleCrypto(); WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); - assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() - .encryptedPassword(actual).privateKey(privateKey).build()).getPassword(), "bX7vvptvw"); + assertEquals(passwordDecrypt.apply( + new EncryptedPasswordAndPrivateKey(actual, privateKey)).getPassword(), "bX7vvptvw"); } @Override