mirror of https://github.com/apache/jclouds.git
Merge pull request #359 from andreisavu/keypairs-and-passwords
Fix registerSSHKeyPair and implement getPasswordForVirtualMachine
This commit is contained in:
commit
ad724de243
|
@ -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
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* 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 {
|
||||
|
||||
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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -18,15 +18,10 @@
|
|||
*/
|
||||
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;
|
||||
import org.jclouds.cloudstack.filters.ReEncodeQueryWithDefaultURLEncoder;
|
||||
import org.jclouds.cloudstack.options.ListSSHKeyPairsOptions;
|
||||
import org.jclouds.rest.annotations.ExceptionParser;
|
||||
import org.jclouds.rest.annotations.OnlyElement;
|
||||
|
@ -37,6 +32,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.
|
||||
*
|
||||
|
@ -65,6 +66,7 @@ public interface SSHKeyPairAsyncClient {
|
|||
@QueryParams(keys = "command", values = "registerSSHKeyPair")
|
||||
@SelectJson("keypair")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RequestFilters(ReEncodeQueryWithDefaultURLEncoder.class)
|
||||
ListenableFuture<SshKeyPair> registerSSHKeyPair(@QueryParam("name") String name, @QueryParam("publickey") String publicKey);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Long> resetPasswordForVirtualMachine(@QueryParam("id") long id);
|
||||
|
||||
/**
|
||||
* @see VirtualMachineClient#getEncryptedPasswordForVirtualMachine
|
||||
*/
|
||||
@GET
|
||||
@QueryParams(keys = "command", values = "getVMPassword")
|
||||
@SelectJson("encryptedpassword")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
ListenableFuture<String> getEncryptedPasswordForVirtualMachine(@QueryParam("id") long id);
|
||||
|
||||
/**
|
||||
* @see VirtualMachineClient#changeServiceForVirtualMachine
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
* <p/>
|
||||
|
@ -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 encrypted password
|
||||
*/
|
||||
String getEncryptedPasswordForVirtualMachine(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.
|
||||
|
|
|
@ -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<String, String> decodedParams = ModifyRequest.parseQueryToMap(request.getEndpoint().getQuery());
|
||||
Multimap<String, String> decodedParams = ModifyRequest.parseQueryToMap(request.getEndpoint().getRawQuery());
|
||||
addSigningParams(decodedParams);
|
||||
String stringToSign = createStringToSign(request, decodedParams);
|
||||
String signature = sign(stringToSign);
|
||||
|
|
|
@ -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<UriBuilder> builders;
|
||||
|
||||
@Inject
|
||||
public ReEncodeQueryWithDefaultURLEncoder(Provider<UriBuilder> builders) {
|
||||
this.builders = builders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||
UriBuilder builder = builders.get();
|
||||
builder.uri(request.getEndpoint());
|
||||
Multimap<String, String> map = ModifyRequest.parseQueryToMap(request.getEndpoint().getRawQuery());
|
||||
builder.replaceQuery("");
|
||||
for (String key : map.keySet())
|
||||
builder.queryParam(key, getOnlyElement(map.get(key)));
|
||||
return request.toBuilder().endpoint(builder.build()).build();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<EncryptedPasswordAndPrivateKey, LoginCredentials> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,30 +18,38 @@
|
|||
*/
|
||||
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.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.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;
|
||||
|
||||
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;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test(groups = "live", testName = "CloudStackExperimentLiveTest")
|
||||
|
@ -62,7 +70,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);
|
||||
|
@ -122,4 +130,46 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted()
|
||||
throws RunNodesException, NoSuchAlgorithmException, CertificateException {
|
||||
// final Map<String, String> sshKey = SshKeys.generate();
|
||||
// final String publicKey = sshKey.get("public");
|
||||
|
||||
String keyPairName = prefix + "-windows-keypair";
|
||||
client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName);
|
||||
// client.getSSHKeyPairClient().registerSSHKeyPair(keyPairName, publicKey);
|
||||
|
||||
SshKeyPair keyPair = 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));
|
||||
|
||||
String encryptedPassword = client.getVirtualMachineClient()
|
||||
.getEncryptedPasswordForVirtualMachine(Long.parseLong(node.getId()));
|
||||
|
||||
Crypto crypto = new BouncyCastleCrypto();
|
||||
WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto);
|
||||
|
||||
assertEquals(passwordDecrypt.apply(
|
||||
new EncryptedPasswordAndPrivateKey(encryptedPassword, keyPair.getPrivateKey())).getPassword(),
|
||||
"bX7vvptvw");
|
||||
|
||||
} finally {
|
||||
if (node != null) {
|
||||
computeContext.getComputeService().destroyNode(node.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 BaseCloudStackAsyncClientTest<SSH
|
|||
assertSaxResponseParserClassEquals(method, null);
|
||||
assertExceptionParserClassEquals(method, MapHttp4xxCodesToExceptions.class);
|
||||
|
||||
checkFilters(httpRequest);
|
||||
assertEquals(httpRequest.getFilters().size(), 2);
|
||||
assertEquals(httpRequest.getFilters().get(0).getClass(), QuerySigner.class);
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,7 +134,6 @@ public class SSHKeyPairAsyncClientTest extends BaseCloudStackAsyncClientTest<SSH
|
|||
assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
|
||||
|
||||
checkFilters(httpRequest);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 com.sun.jersey.api.uri.UriComponent;
|
||||
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 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<SSHKeyPairClient> {
|
||||
|
||||
@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.<String, String>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.<String, String>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.<String, String>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=" + UriComponent.encode(publicKey, UriComponent.Type.QUERY_PARAM) +
|
||||
"&apiKey=identity&signature=g/6BXLnnvOMlKQBp1yM7GKlvfus%3D"))
|
||||
.headers(
|
||||
ImmutableMultimap.<String, String>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();
|
||||
}
|
||||
}
|
|
@ -18,17 +18,19 @@
|
|||
*/
|
||||
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;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Tests behavior of {@code SSHKeyPairClient}
|
||||
*
|
||||
|
@ -47,6 +49,7 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest {
|
|||
client.getSSHKeyPairClient().deleteSSHKeyPair(keyPairName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListSSHKeyPairs() {
|
||||
final Set<SshKeyPair> sshKeyPairs = client.getSSHKeyPairClient().listSSHKeyPairs();
|
||||
for (SshKeyPair sshKeyPair : sshKeyPairs) {
|
||||
|
@ -54,8 +57,10 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDeleteSSHKeyPair() {
|
||||
sshKeyPair = client.getSSHKeyPairClient().createSSHKeyPair(keyPairName);
|
||||
assertNotNull(sshKeyPair.getPrivateKey());
|
||||
checkSSHKeyPair(sshKeyPair);
|
||||
client.getSSHKeyPairClient().deleteSSHKeyPair(sshKeyPair.getName());
|
||||
|
||||
|
@ -65,25 +70,26 @@ public class SSHKeyPairClientLiveTest extends BaseCloudStackClientLiveTest {
|
|||
sshKeyPair = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterDeleteSSHKeyPair() {
|
||||
final Map<String, String> sshKey = SshKeys.generate();
|
||||
final String publicKey = sshKey.get("public");
|
||||
|
||||
sshKeyPair = client.getSSHKeyPairClient().registerSSHKeyPair(keyPairName, publicKey);
|
||||
assertNull(sshKeyPair.getPrivateKey());
|
||||
checkSSHKeyPair(sshKeyPair);
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* Test the CloudStack VirtualMachineClientClient
|
||||
*
|
||||
* @author Andrei Savu
|
||||
*/
|
||||
@Test(groups = "unit", testName = "VirtualMachineClientExpectTest")
|
||||
public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpectTest<VirtualMachineClient> {
|
||||
|
||||
public void testGetPasswordForVirtualMachineWhenResponseIs2xx() throws NoSuchAlgorithmException, CertificateException {
|
||||
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.<String, String>builder()
|
||||
.put("Accept", "application/json")
|
||||
.build())
|
||||
.build(),
|
||||
HttpResponse.builder()
|
||||
.statusCode(200)
|
||||
.payload(payloadFromResource("/getvmpasswordresponse.json"))
|
||||
.build());
|
||||
|
||||
String actual = client.getEncryptedPasswordForVirtualMachine(1L);
|
||||
String expected = "EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\n" +
|
||||
"pQ0LZlQBZDd5/ac0NSoM6tAX3H30pYxNw4t2f9u8aJ48oOEvufgGxTTHnM9qHXD04lt+Ouql6i2q\r\n" +
|
||||
"HxBqCxFkMZEla3LFieE=\r\n";
|
||||
|
||||
assertEquals(actual, expected);
|
||||
|
||||
Crypto crypto = new BouncyCastleCrypto();
|
||||
WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto);
|
||||
|
||||
assertEquals(passwordDecrypt.apply(
|
||||
new EncryptedPasswordAndPrivateKey(actual, privateKey)).getPassword(), "bX7vvptvw");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VirtualMachineClient clientFrom(CloudStackContext context) {
|
||||
return context.getProviderSpecificContext().getApi().getVirtualMachineClient();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* 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 com.sun.jersey.api.uri.UriComponent;
|
||||
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=" +
|
||||
UriComponent.encode(input, UriComponent.Type.QUERY_PARAM));
|
||||
}
|
||||
}
|
|
@ -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<SshKeyPair> {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SelectJson("keypair")
|
||||
@SelectJson("sshkeypair")
|
||||
public Set<SshKeyPair> expected() {
|
||||
return ImmutableSet.<SshKeyPair> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"} } }
|
|
@ -0,0 +1,2 @@
|
|||
{ "getvmpasswordresponse" : { "password" :
|
||||
{"encryptedpassword":"EFOwm8icZ4sEib4y6ntVHUKHZJQrGBdyPkL1L9lpFHYhs3JfAtL5E5bxBP5Er27bJyOZPjKFcInX\r\npQ0LZlQBZDd5/ac0NSoM6tAX3H30pYxNw4t2f9u8aJ48oOEvufgGxTTHnM9qHXD04lt+Ouql6i2q\r\nHxBqCxFkMZEla3LFieE=\r\n"} } }
|
|
@ -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"} ] } }
|
||||
{ "listsshkeypairsresponse" : { "count":1 ,"sshkeypair" : [
|
||||
{"name":"jclouds-keypair","fingerprint":"1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b"} ] } }
|
|
@ -0,0 +1,2 @@
|
|||
{ "registersshkeypairresponse" : { "keypair" :
|
||||
{"name":"jclouds-keypair","fingerprint":"8f:f1:91:2d:b1:a8:51:f1:79:cf:c4:31:c4:14:9d:81"} } }
|
|
@ -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<String, String> params,
|
||||
|
|
|
@ -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
|
||||
|
@ -84,7 +85,7 @@ public class ModifyRequestTest {
|
|||
assertEquals(
|
||||
ModifyRequest.putHeaders(request, ImmutableMultimap.of("foo", "baz")),
|
||||
HttpRequest.builder().method("GET").endpoint(URI.create("http://foo"))
|
||||
.headers(ImmutableMultimap.<String, String> builder().put("foo", "bar").put("foo", "baz").build())
|
||||
.headers(ImmutableMultimap.<String, String>builder().put("foo", "bar").put("foo", "baz").build())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@ -105,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
|
||||
|
@ -130,4 +131,20 @@ 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";
|
||||
|
||||
Set<String> expected = ImmutableSet.of(key);
|
||||
|
||||
Multimap<String, String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue