From b6d106c7a1a9433605693e2de193ef1a1f7e96ed Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Tue, 31 Jan 2012 04:11:26 +0200 Subject: [PATCH 1/3] Implemented login / logout APIs --- .../cloudstack/CloudStackAsyncClient.java | 7 + .../jclouds/cloudstack/CloudStackClient.java | 11 +- .../CloudStackPropertiesBuilder.java | 6 +- .../config/CloudStackRestClientModule.java | 3 + .../cloudstack/domain/LoginResponse.java | 270 ++++++++++++++++++ .../features/SessionAsyncClient.java | 93 ++++++ .../cloudstack/features/SessionClient.java | 89 ++++++ .../cloudstack/functions/HashToMD5.java | 49 ++++ .../cloudstack/util/CloudStackUtil.java | 46 +++ .../features/SessionClientLiveTest.java | 87 ++++++ 10 files changed, 656 insertions(+), 5 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/LoginResponse.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java index baf433b5e1..0a983f7e41 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java @@ -34,6 +34,7 @@ import org.jclouds.cloudstack.features.NetworkAsyncClient; import org.jclouds.cloudstack.features.OfferingAsyncClient; import org.jclouds.cloudstack.features.SSHKeyPairAsyncClient; import org.jclouds.cloudstack.features.SecurityGroupAsyncClient; +import org.jclouds.cloudstack.features.SessionAsyncClient; import org.jclouds.cloudstack.features.SnapshotAsyncClient; import org.jclouds.cloudstack.features.TemplateAsyncClient; import org.jclouds.cloudstack.features.VMGroupAsyncClient; @@ -184,4 +185,10 @@ public interface CloudStackAsyncClient { */ @Delegate SnapshotAsyncClient getSnapshotClient(); + + /** + * Provides asynchronous access to Sessions + */ + @Delegate + SessionAsyncClient getSessionClient(); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java index 82741a32aa..fc153e06a3 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java @@ -18,8 +18,6 @@ */ package org.jclouds.cloudstack; -import java.util.concurrent.TimeUnit; - import org.jclouds.cloudstack.features.AccountClient; import org.jclouds.cloudstack.features.AddressClient; import org.jclouds.cloudstack.features.AsyncJobClient; @@ -41,10 +39,13 @@ import org.jclouds.cloudstack.features.TemplateClient; import org.jclouds.cloudstack.features.VMGroupClient; import org.jclouds.cloudstack.features.VirtualMachineClient; import org.jclouds.cloudstack.features.VolumeClient; +import org.jclouds.cloudstack.features.SessionClient; import org.jclouds.cloudstack.features.ZoneClient; import org.jclouds.concurrent.Timeout; import org.jclouds.rest.annotations.Delegate; +import java.util.concurrent.TimeUnit; + /** * Provides synchronous access to CloudStack. *

@@ -187,4 +188,10 @@ public interface CloudStackClient { */ @Delegate SnapshotClient getSnapshotClient(); + + /** + * Provides synchronous access to Sessions + */ + @Delegate + SessionClient getSessionClient(); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackPropertiesBuilder.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackPropertiesBuilder.java index 0238bc0bac..61dafad385 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackPropertiesBuilder.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackPropertiesBuilder.java @@ -18,12 +18,12 @@ */ package org.jclouds.cloudstack; -import static org.jclouds.Constants.PROPERTY_API_VERSION; -import static org.jclouds.Constants.PROPERTY_ENDPOINT; +import org.jclouds.PropertiesBuilder; import java.util.Properties; -import org.jclouds.PropertiesBuilder; +import static org.jclouds.Constants.PROPERTY_API_VERSION; +import static org.jclouds.Constants.PROPERTY_ENDPOINT; /** * Builds properties used in cloudstack Clients diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java index 68023513f3..999f7025b7 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java @@ -96,6 +96,8 @@ import org.jclouds.cloudstack.features.SSHKeyPairAsyncClient; import org.jclouds.cloudstack.features.SSHKeyPairClient; import org.jclouds.cloudstack.features.SecurityGroupAsyncClient; import org.jclouds.cloudstack.features.SecurityGroupClient; +import org.jclouds.cloudstack.features.SessionAsyncClient; +import org.jclouds.cloudstack.features.SessionClient; import org.jclouds.cloudstack.features.SnapshotAsyncClient; import org.jclouds.cloudstack.features.SnapshotClient; import org.jclouds.cloudstack.features.TemplateAsyncClient; @@ -175,6 +177,7 @@ public class CloudStackRestClientModule extends RestClientModule { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String userName; + private long userId; + private String password; + private long domainId; + private long timeout; + private String accountName; + private String firstName; + private String lastName; + private Account.Type accountType; + private String timezone; + private String timezoneOffset; + private String sessionKey; + + public Builder userName(String userName) { + this.userName = userName; + return this; + } + + public Builder userId(long userId) { + this.userId = userId; + return this; + } + + public Builder password(String password) { + this.password = password; + return this; + } + + public Builder domainId(long domainId) { + this.domainId = domainId; + return this; + } + + public Builder timeout(long timeout) { + this.timeout = timeout; + return this; + } + + public Builder accountName(String accountName) { + this.accountName = accountName; + return this; + } + + public Builder firstName(String firstName) { + this.firstName = firstName; + return this; + } + + public Builder lastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Builder accountType(Account.Type accountType) { + this.accountType = accountType; + return this; + } + + public Builder timezone(String timezone) { + this.timezone = timezone; + return this; + } + + public Builder timezoneOffset(String timezoneOffset) { + this.timezoneOffset = timezoneOffset; + return this; + } + + public Builder sessionKey(String sessionKey) { + this.sessionKey = sessionKey; + return this; + } + + public LoginResponse build() { + return new LoginResponse(userName, userId, password, domainId, timeout, accountName, + firstName, lastName, accountType, timezone, timezoneOffset, sessionKey); + } + } + + // for deserialization + LoginResponse() { } + + @SerializedName("username") + private String userName; + @SerializedName("userid") + private long userId; + private String password; + @SerializedName("domainid") + private long domainId; + private long timeout; + @SerializedName("account") + private String accountName; + @SerializedName("firstname") + private String firstName; + @SerializedName("lastname") + private String lastName; + @SerializedName("type") + private Account.Type accountType; + private String timezone; + @SerializedName("timezoneoffset") + private String timezoneOffset; + @SerializedName("sessionkey") + private String sessionKey; + + public LoginResponse(String userName, long userId, String password, long domainId, long timeout, + String accountName, String firstName, String lastName, Account.Type accountType, + String timezone, String timezoneOffset, String sessionKey) { + this.userName = userName; + this.userId = userId; + this.password = password; + this.domainId = domainId; + this.timeout = timeout; + this.accountName = accountName; + this.firstName = firstName; + this.lastName = lastName; + this.accountType = accountType; + this.timezone = timezone; + this.timezoneOffset = timezoneOffset; + this.sessionKey = sessionKey; + } + + public String getUserName() { + return userName; + } + + public long getUserId() { + return userId; + } + + public String getPassword() { + return password; + } + + public long getDomainId() { + return domainId; + } + + public long getTimeout() { + return timeout; + } + + public String getAccountName() { + return accountName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public Account.Type getAccountType() { + return accountType; + } + + public String getTimezone() { + return timezone; + } + + public String getTimezoneOffset() { + return timezoneOffset; + } + + public String getSessionKey() { + return sessionKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LoginResponse loginResponse = (LoginResponse) o; + + if (domainId != loginResponse.domainId) return false; + if (timeout != loginResponse.timeout) return false; + if (userId != loginResponse.userId) return false; + if (accountName != null ? !accountName.equals(loginResponse.accountName) : loginResponse.accountName != null) return false; + if (accountType != loginResponse.accountType) return false; + if (firstName != null ? !firstName.equals(loginResponse.firstName) : loginResponse.firstName != null) return false; + if (lastName != null ? !lastName.equals(loginResponse.lastName) : loginResponse.lastName != null) return false; + if (password != null ? !password.equals(loginResponse.password) : loginResponse.password != null) return false; + if (sessionKey != null ? !sessionKey.equals(loginResponse.sessionKey) : loginResponse.sessionKey != null) return false; + if (timezone != null ? !timezone.equals(loginResponse.timezone) : loginResponse.timezone != null) return false; + if (timezoneOffset != null ? !timezoneOffset.equals(loginResponse.timezoneOffset) : loginResponse.timezoneOffset != null) + return false; + if (userName != null ? !userName.equals(loginResponse.userName) : loginResponse.userName != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userName != null ? userName.hashCode() : 0; + result = 31 * result + (int) (userId ^ (userId >>> 32)); + result = 31 * result + (password != null ? password.hashCode() : 0); + result = 31 * result + (int) (domainId ^ (domainId >>> 32)); + result = 31 * result + (int) (timeout ^ (timeout >>> 32)); + result = 31 * result + (accountName != null ? accountName.hashCode() : 0); + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + (accountType != null ? accountType.hashCode() : 0); + result = 31 * result + (timezone != null ? timezone.hashCode() : 0); + result = 31 * result + (timezoneOffset != null ? timezoneOffset.hashCode() : 0); + result = 31 * result + (sessionKey != null ? sessionKey.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "LoginResponse{" + + "userName='" + userName + '\'' + + ", userId=" + userId + + ", password='" + password + '\'' + + ", domainId=" + domainId + + ", timeout=" + timeout + + ", accountName='" + accountName + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", accountType=" + accountType + + ", timezone='" + timezone + '\'' + + ", timezoneOffset='" + timezoneOffset + '\'' + + ", sessionKey='" + sessionKey + '\'' + + '}'; + } + + @Override + public int compareTo(LoginResponse arg0) { + return sessionKey.compareTo(arg0.getSessionKey()); + } + +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java new file mode 100644 index 0000000000..43e5d8f1c7 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java @@ -0,0 +1,93 @@ +/** + * 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.util.concurrent.ListenableFuture; +import org.jclouds.cloudstack.domain.Account; +import org.jclouds.cloudstack.domain.LoginResponse; +import org.jclouds.cloudstack.functions.HashToMD5; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.ParamParser; +import org.jclouds.rest.annotations.QueryParams; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; + +import javax.ws.rs.Consumes; +import javax.ws.rs.CookieParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +/** + * Provides asynchronous access to Cloudstack Sessions + *

+ * + * @see org.jclouds.cloudstack.features.SessionClient + * @see + * @author Andrei Savu + */ +@QueryParams(keys = "response", values = "json") +public interface SessionAsyncClient { + + /** + * @see SessionClient#loginWithHashedPassword + */ + @GET + @QueryParams(keys = "command", values = "login") + @SelectJson("loginresponse") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture loginWithHashedPassword(@QueryParam("username") String userName, + @QueryParam("password") String hashedPassword, @QueryParam("domain") String domainOrEmpty); + + /** + * @see SessionClient#loginWithPlainTextPassword + */ + @GET + @QueryParams(keys = "command", values = "login") + @SelectJson("loginresponse") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture loginWithPlainTextPassword(@QueryParam("username") String userName, + @QueryParam("password") @ParamParser(HashToMD5.class) String plainTextPassword, + @QueryParam("domain") String domainOrEmpty); + + /** + * @see SessionClient#getAccountByName + */ + @GET + @QueryParams(keys = "comand", values = "listAccounts") + @SelectJson("account") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + @HeaderParam(HttpHeaders.COOKIE) + ListenableFuture getAccountByName(@QueryParam("name") String accountName, + @CookieParam("sessionKey") @QueryParam("sessionkey") String sessionKey); + + /** + * @see SessionClient#logout + */ + @GET + @QueryParams(keys = "command", values = "logout") + @ExceptionParser(ReturnVoidOnNotFoundOr404.class) + ListenableFuture logout(@QueryParam("sessionkey") String sessionKey); +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java new file mode 100644 index 0000000000..db8d56b878 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java @@ -0,0 +1,89 @@ +/** + * 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 org.jclouds.cloudstack.domain.Account; +import org.jclouds.cloudstack.domain.LoginResponse; +import org.jclouds.concurrent.Timeout; + +import java.util.concurrent.TimeUnit; + +/** + * Provides synchronous access to CloudStack Sessions + *

+ * + * @see + * @author Andrei Savu + */ +@Timeout(duration = 120, timeUnit = TimeUnit.SECONDS) +public interface SessionClient { + + /** + * Logs a user into Cloudstack. A successful login attempt will generate a JSESSIONID + * cookie value that can be passed in subsequent Query command calls until the "logout" + * command has been issued or the session has expired. + * + * @param userName + * user account name + * @param hashedPassword + * hashed password (by default MD5) + * @param domainOrEmpty + * domain name, if empty defaults to ROOT + * @return + * login response with session key or null + */ + LoginResponse loginWithHashedPassword(String userName, String hashedPassword, String domainOrEmpty); + + /** + * Logs a user into Cloudstack. A successful login attempt will generate a session key + * cookie value that can be passed in subsequent Query command calls until the "logout" + * command has been issued or the session has expired. + * + * @param userName + * user account name + * @param plainTextPassword + * plain text password + * @param domainOrEmpty + * domain name, if empty defaults to ROOT + * @return + * login response with session key or null + */ + LoginResponse loginWithPlainTextPassword(String userName, String plainTextPassword, String domainOrEmpty); + + /** + * Retrieve an account by name using the session key for login + * + * @param accountName + * account name + * @param sessionKey + * a valid session key + * @return + * account instance or null + */ + Account getAccountByName(String accountName, String sessionKey); + + /** + * Logs out the user by invalidating the session key + * + * @param sessionKey + * user session key + */ + Void logout(String sessionKey); + +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java new file mode 100644 index 0000000000..fb759c4763 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java @@ -0,0 +1,49 @@ +/** + * 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.functions; + +import com.google.common.base.Function; +import com.google.common.io.InputSupplier; +import org.jclouds.crypto.CryptoStreams; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Hash a string to MD5 (hex representation) + * + * @author Andrei Savu + */ +public class HashToMD5 implements Function { + + @Override + public String apply(final Object input) { + try { + return CryptoStreams.md5Hex(new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return new ByteArrayInputStream(String.class.cast(input).getBytes()); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java new file mode 100644 index 0000000000..0ee4e379a2 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java @@ -0,0 +1,46 @@ +/** + * 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.util; + +import org.jclouds.cloudstack.domain.ApiKeyPair; + +import java.net.URI; + +/** + * @author Andrei Savu + */ +public class CloudStackUtil { + + /** + * Retrieve the API key pair for a given CloudStack user + * + * @param endpoint + * CloudStack API endpoint (e.g. http://72.52.126.25/client/api/) + * @param userName + * User account name + * @param password + * User password + * @param domain + * Domain name. If empty defaults to ROOT + * @return + */ + public static ApiKeyPair getApiKeyPairForUser(URI endpoint, String userName, String password, String domain) { + return null; + } +} diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java new file mode 100644 index 0000000000..972443a8b6 --- /dev/null +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.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.base.Predicate; +import org.jclouds.cloudstack.domain.Account; +import org.jclouds.cloudstack.domain.LoginResponse; +import org.jclouds.cloudstack.domain.User; +import org.testng.annotations.Test; + +import static com.google.common.collect.Iterables.find; +import static org.testng.Assert.assertNotNull; +import static org.testng.AssertJUnit.assertEquals; + +/** + * Tests behavior of {@code SessionClient} + * + * @author Andrei Savu + */ +@Test(groups = "live", singleThreaded = true, testName = "SessionClientLiveTest") +public class SessionClientLiveTest extends BaseCloudStackClientLiveTest { + + private final String USER = "jcloud"; + private final String PLAIN_TEXT_PASSWORD = "jcl0ud"; + private final String PASSWORD_MD5_HASH = "30e14b3727225d833aad2206acea1275"; + private final String DOMAIN = "/Partners/jCloud"; + + private LoginResponse loginResponse; + + @Test(enabled = true) + public void testLoginWithPlainTextPassword() throws Exception { + LoginResponse response = client.getSessionClient() + .loginWithPlainTextPassword(USER, PLAIN_TEXT_PASSWORD, DOMAIN); + + assertNotNull(response); + assertNotNull(response.getSessionKey()); + } + + @Test(enabled = true) + public void testLoginWithHashedPassword() throws Exception { + loginResponse = client.getSessionClient() + .loginWithHashedPassword(USER, PASSWORD_MD5_HASH, DOMAIN); + + assertNotNull(loginResponse); + assertNotNull(loginResponse.getSessionKey()); + } + + @Test(dependsOnMethods = "testLoginWithHashedPassword") + public void testRetrieveUserInfoWithSessionKey() throws Exception { + Account account = client.getSessionClient() + .getAccountByName(loginResponse.getAccountName(), loginResponse.getSessionKey()); + + assertNotNull(account); + assertEquals(account.getName(), loginResponse.getAccountName()); + + User currentUser = find(account.getUsers(), new Predicate() { + @Override + public boolean apply(User user) { + return user.getId() == loginResponse.getUserId(); + } + }); + assertNotNull(currentUser); + assertEquals(currentUser.getName(), loginResponse.getUserName()); + assertEquals(currentUser.getDomainId(), loginResponse.getDomainId()); + } + + @Test(dependsOnMethods = "testRetrieveUserInfoWithSessionKey") + public void testLogout() throws Exception { + client.getSessionClient().logout(loginResponse.getSessionKey()); + } +} From 2b8e2a4c010c8361f1c2e4877124dbcd4a06f7b0 Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Tue, 31 Jan 2012 13:12:37 +0200 Subject: [PATCH 2/3] Added a set of changes as requested by Adrian --- .../features/SessionAsyncClient.java | 31 +++--------- .../cloudstack/features/SessionClient.java | 28 +++-------- .../cloudstack/functions/HashToMD5.java | 49 ------------------- .../{CloudStackUtil.java => ApiKeyPairs.java} | 2 +- .../features/SessionClientLiveTest.java | 19 ++----- .../org/jclouds/crypto/CryptoStreams.java | 35 ++++++++----- 6 files changed, 44 insertions(+), 120 deletions(-) delete mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java rename apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/{CloudStackUtil.java => ApiKeyPairs.java} (97%) diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java index 43e5d8f1c7..2c7a147346 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java @@ -21,16 +21,13 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.Account; import org.jclouds.cloudstack.domain.LoginResponse; -import org.jclouds.cloudstack.functions.HashToMD5; import org.jclouds.rest.annotations.ExceptionParser; -import org.jclouds.rest.annotations.ParamParser; import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import javax.ws.rs.Consumes; -import javax.ws.rs.CookieParam; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.QueryParam; @@ -49,30 +46,18 @@ import javax.ws.rs.core.MediaType; public interface SessionAsyncClient { /** - * @see SessionClient#loginWithHashedPassword + * @see SessionClient#loginUserInDomainWithHashOfPassword */ @GET @QueryParams(keys = "command", values = "login") @SelectJson("loginresponse") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture loginWithHashedPassword(@QueryParam("username") String userName, - @QueryParam("password") String hashedPassword, @QueryParam("domain") String domainOrEmpty); + ListenableFuture loginUserInDomainWithHashOfPassword(@QueryParam("username") String userName, + @QueryParam("domain") String domain, @QueryParam("password") String hashedPassword); /** - * @see SessionClient#loginWithPlainTextPassword - */ - @GET - @QueryParams(keys = "command", values = "login") - @SelectJson("loginresponse") - @Consumes(MediaType.APPLICATION_JSON) - @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture loginWithPlainTextPassword(@QueryParam("username") String userName, - @QueryParam("password") @ParamParser(HashToMD5.class) String plainTextPassword, - @QueryParam("domain") String domainOrEmpty); - - /** - * @see SessionClient#getAccountByName + * @see SessionClient#getAccountByNameUsingSession */ @GET @QueryParams(keys = "comand", values = "listAccounts") @@ -80,14 +65,14 @@ public interface SessionAsyncClient { @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) @HeaderParam(HttpHeaders.COOKIE) - ListenableFuture getAccountByName(@QueryParam("name") String accountName, - @CookieParam("sessionKey") @QueryParam("sessionkey") String sessionKey); + ListenableFuture getAccountByNameUsingSession(@QueryParam("name") String accountName, + @QueryParam("sessionkey") String sessionKey); /** - * @see SessionClient#logout + * @see SessionClient#logoutUser */ @GET @QueryParams(keys = "command", values = "logout") @ExceptionParser(ReturnVoidOnNotFoundOr404.class) - ListenableFuture logout(@QueryParam("sessionkey") String sessionKey); + ListenableFuture logoutUser(@QueryParam("sessionkey") String sessionKey); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java index db8d56b878..853d7cddf6 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java @@ -39,32 +39,18 @@ public interface SessionClient { * cookie value that can be passed in subsequent Query command calls until the "logout" * command has been issued or the session has expired. * - * @param userName - * user account name - * @param hashedPassword - * hashed password (by default MD5) - * @param domainOrEmpty - * domain name, if empty defaults to ROOT - * @return - * login response with session key or null - */ - LoginResponse loginWithHashedPassword(String userName, String hashedPassword, String domainOrEmpty); - - /** - * Logs a user into Cloudstack. A successful login attempt will generate a session key - * cookie value that can be passed in subsequent Query command calls until the "logout" - * command has been issued or the session has expired. + * * * @param userName * user account name - * @param plainTextPassword - * plain text password - * @param domainOrEmpty + * @param domain * domain name, if empty defaults to ROOT + * @param hashedPassword + * hashed password (by default MD5) * @return * login response with session key or null */ - LoginResponse loginWithPlainTextPassword(String userName, String plainTextPassword, String domainOrEmpty); + LoginResponse loginUserInDomainWithHashOfPassword(String userName, String domain, String hashedPassword); /** * Retrieve an account by name using the session key for login @@ -76,7 +62,7 @@ public interface SessionClient { * @return * account instance or null */ - Account getAccountByName(String accountName, String sessionKey); + Account getAccountByNameUsingSession(String accountName, String sessionKey); /** * Logs out the user by invalidating the session key @@ -84,6 +70,6 @@ public interface SessionClient { * @param sessionKey * user session key */ - Void logout(String sessionKey); + void logoutUser(String sessionKey); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java deleted file mode 100644 index fb759c4763..0000000000 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/HashToMD5.java +++ /dev/null @@ -1,49 +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.functions; - -import com.google.common.base.Function; -import com.google.common.io.InputSupplier; -import org.jclouds.crypto.CryptoStreams; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Hash a string to MD5 (hex representation) - * - * @author Andrei Savu - */ -public class HashToMD5 implements Function { - - @Override - public String apply(final Object input) { - try { - return CryptoStreams.md5Hex(new InputSupplier() { - @Override - public InputStream getInput() throws IOException { - return new ByteArrayInputStream(String.class.cast(input).getBytes()); - } - }); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/ApiKeyPairs.java similarity index 97% rename from apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java rename to apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/ApiKeyPairs.java index 0ee4e379a2..1846d22e80 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/CloudStackUtil.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/util/ApiKeyPairs.java @@ -25,7 +25,7 @@ import java.net.URI; /** * @author Andrei Savu */ -public class CloudStackUtil { +public class ApiKeyPairs { /** * Retrieve the API key pair for a given CloudStack user diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java index 972443a8b6..30172aec16 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java @@ -25,6 +25,7 @@ import org.jclouds.cloudstack.domain.User; import org.testng.annotations.Test; import static com.google.common.collect.Iterables.find; +import static org.jclouds.crypto.CryptoStreams.md5Hex; import static org.testng.Assert.assertNotNull; import static org.testng.AssertJUnit.assertEquals; @@ -38,24 +39,14 @@ public class SessionClientLiveTest extends BaseCloudStackClientLiveTest { private final String USER = "jcloud"; private final String PLAIN_TEXT_PASSWORD = "jcl0ud"; - private final String PASSWORD_MD5_HASH = "30e14b3727225d833aad2206acea1275"; - private final String DOMAIN = "/Partners/jCloud"; + private final String DOMAIN = "Partners/jCloud"; private LoginResponse loginResponse; - @Test(enabled = true) - public void testLoginWithPlainTextPassword() throws Exception { - LoginResponse response = client.getSessionClient() - .loginWithPlainTextPassword(USER, PLAIN_TEXT_PASSWORD, DOMAIN); - - assertNotNull(response); - assertNotNull(response.getSessionKey()); - } - @Test(enabled = true) public void testLoginWithHashedPassword() throws Exception { loginResponse = client.getSessionClient() - .loginWithHashedPassword(USER, PASSWORD_MD5_HASH, DOMAIN); + .loginUserInDomainWithHashOfPassword(USER, DOMAIN, md5Hex(PLAIN_TEXT_PASSWORD)); assertNotNull(loginResponse); assertNotNull(loginResponse.getSessionKey()); @@ -64,7 +55,7 @@ public class SessionClientLiveTest extends BaseCloudStackClientLiveTest { @Test(dependsOnMethods = "testLoginWithHashedPassword") public void testRetrieveUserInfoWithSessionKey() throws Exception { Account account = client.getSessionClient() - .getAccountByName(loginResponse.getAccountName(), loginResponse.getSessionKey()); + .getAccountByNameUsingSession(loginResponse.getAccountName(), loginResponse.getSessionKey()); assertNotNull(account); assertEquals(account.getName(), loginResponse.getAccountName()); @@ -82,6 +73,6 @@ public class SessionClientLiveTest extends BaseCloudStackClientLiveTest { @Test(dependsOnMethods = "testRetrieveUserInfoWithSessionKey") public void testLogout() throws Exception { - client.getSessionClient().logout(loginResponse.getSessionKey()); + client.getSessionClient().logoutUser(loginResponse.getSessionKey()); } } diff --git a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java index ad3ae8e280..6c810cae10 100644 --- a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java +++ b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java @@ -18,8 +18,17 @@ */ package org.jclouds.crypto; -import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.io.ByteProcessor; +import com.google.common.io.ByteStreams; +import com.google.common.io.InputSupplier; +import org.jclouds.encryption.internal.Base64; +import org.jclouds.io.InputSuppliers; +import javax.crypto.Mac; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -28,17 +37,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import javax.crypto.Mac; - -import org.jclouds.encryption.internal.Base64; -import org.jclouds.io.InputSuppliers; - -import com.google.common.annotations.Beta; -import com.google.common.base.Charsets; -import com.google.common.base.Throwables; -import com.google.common.io.ByteProcessor; -import com.google.common.io.ByteStreams; -import com.google.common.io.InputSupplier; +import static com.google.common.base.Preconditions.checkNotNull; /** * functions related to but not in {@link com.google.common.io.ByteStreams} @@ -89,6 +88,18 @@ public class CryptoStreams { return hex(md5(supplier)); } + /** + * @see #md5Hex + */ + public static String md5Hex(final String in) throws IOException { + return md5Hex(new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return new ByteArrayInputStream(in.getBytes()); + } + }); + } + /** * @see #md5 * @see #base64 From c34aaa50b5bf539607410faa15892c90b12415ba Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Tue, 31 Jan 2012 15:04:42 +0200 Subject: [PATCH 3/3] Include the JSESSIONID as part of the LoginResponse --- .../cloudstack/domain/LoginResponse.java | 36 +++++++++++++++- .../features/SessionAsyncClient.java | 10 ++--- .../cloudstack/features/SessionClient.java | 4 +- .../ParseLoginResponseFromHttpResponse.java | 41 +++++++++++++++++++ .../features/SessionClientLiveTest.java | 29 ++++++------- 5 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/ParseLoginResponseFromHttpResponse.java diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/LoginResponse.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/LoginResponse.java index ad3dfb117f..351b9f7f4d 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/LoginResponse.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/LoginResponse.java @@ -46,7 +46,25 @@ public class LoginResponse implements Comparable { private String timezone; private String timezoneOffset; private String sessionKey; + private String jSessionId; + public Builder copyOf(LoginResponse r) { + this.userName = r.userName; + this.userId = r.userId; + this.password = r.password; + this.domainId = r.domainId; + this.timeout = r.timeout; + this.accountName = r.accountName; + this.firstName = r.firstName; + this.lastName = r.lastName; + this.accountType = r.accountType; + this.timezone = r.timezone; + this.timezoneOffset = r.timezoneOffset; + this.sessionKey = r.sessionKey; + this.jSessionId = r.jSessionId; + return this; + } + public Builder userName(String userName) { this.userName = userName; return this; @@ -106,10 +124,15 @@ public class LoginResponse implements Comparable { this.sessionKey = sessionKey; return this; } + + public Builder jSessionId(String jSessionId) { + this.jSessionId = jSessionId; + return this; + } public LoginResponse build() { return new LoginResponse(userName, userId, password, domainId, timeout, accountName, - firstName, lastName, accountType, timezone, timezoneOffset, sessionKey); + firstName, lastName, accountType, timezone, timezoneOffset, sessionKey, jSessionId); } } @@ -137,10 +160,11 @@ public class LoginResponse implements Comparable { private String timezoneOffset; @SerializedName("sessionkey") private String sessionKey; + private String jSessionId; public LoginResponse(String userName, long userId, String password, long domainId, long timeout, String accountName, String firstName, String lastName, Account.Type accountType, - String timezone, String timezoneOffset, String sessionKey) { + String timezone, String timezoneOffset, String sessionKey, String jSessionId) { this.userName = userName; this.userId = userId; this.password = password; @@ -153,6 +177,7 @@ public class LoginResponse implements Comparable { this.timezone = timezone; this.timezoneOffset = timezoneOffset; this.sessionKey = sessionKey; + this.jSessionId = jSessionId; } public String getUserName() { @@ -202,6 +227,10 @@ public class LoginResponse implements Comparable { public String getSessionKey() { return sessionKey; } + + public String getJSessionId() { + return jSessionId; + } @Override public boolean equals(Object o) { @@ -223,6 +252,7 @@ public class LoginResponse implements Comparable { if (timezoneOffset != null ? !timezoneOffset.equals(loginResponse.timezoneOffset) : loginResponse.timezoneOffset != null) return false; if (userName != null ? !userName.equals(loginResponse.userName) : loginResponse.userName != null) return false; + if (jSessionId != null ? !jSessionId.equals(loginResponse.jSessionId) : loginResponse.jSessionId != null) return false; return true; } @@ -241,6 +271,7 @@ public class LoginResponse implements Comparable { result = 31 * result + (timezone != null ? timezone.hashCode() : 0); result = 31 * result + (timezoneOffset != null ? timezoneOffset.hashCode() : 0); result = 31 * result + (sessionKey != null ? sessionKey.hashCode() : 0); + result = 31 * result + (jSessionId != null ? jSessionId.hashCode() : 0); return result; } @@ -259,6 +290,7 @@ public class LoginResponse implements Comparable { ", timezone='" + timezone + '\'' + ", timezoneOffset='" + timezoneOffset + '\'' + ", sessionKey='" + sessionKey + '\'' + + ", jSessionId='" + jSessionId + '\'' + '}'; } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java index 2c7a147346..452e6a3a76 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionAsyncClient.java @@ -21,17 +21,18 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.Account; import org.jclouds.cloudstack.domain.LoginResponse; +import org.jclouds.cloudstack.functions.ParseLoginResponseFromHttpResponse; import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.QueryParams; +import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import javax.ws.rs.Consumes; +import javax.ws.rs.CookieParam; import javax.ws.rs.GET; -import javax.ws.rs.HeaderParam; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; /** @@ -50,7 +51,7 @@ public interface SessionAsyncClient { */ @GET @QueryParams(keys = "command", values = "login") - @SelectJson("loginresponse") + @ResponseParser(ParseLoginResponseFromHttpResponse.class) @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) ListenableFuture loginUserInDomainWithHashOfPassword(@QueryParam("username") String userName, @@ -64,9 +65,8 @@ public interface SessionAsyncClient { @SelectJson("account") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - @HeaderParam(HttpHeaders.COOKIE) ListenableFuture getAccountByNameUsingSession(@QueryParam("name") String accountName, - @QueryParam("sessionkey") String sessionKey); + @QueryParam("sessionkey") String sessionKey, @CookieParam("JSESSIONID") String jSessionId); /** * @see SessionClient#logoutUser diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java index 853d7cddf6..f21239873e 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SessionClient.java @@ -59,10 +59,12 @@ public interface SessionClient { * account name * @param sessionKey * a valid session key + * @param jSessionId + * the JSESSIONID cookie returned by the server * @return * account instance or null */ - Account getAccountByNameUsingSession(String accountName, String sessionKey); + Account getAccountByNameUsingSession(String accountName, String sessionKey, String jSessionId); /** * Logs out the user by invalidating the session key diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/ParseLoginResponseFromHttpResponse.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/ParseLoginResponseFromHttpResponse.java new file mode 100644 index 0000000000..557a2764ad --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/ParseLoginResponseFromHttpResponse.java @@ -0,0 +1,41 @@ +package org.jclouds.cloudstack.functions; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; +import org.jclouds.cloudstack.domain.LoginResponse; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseFirstJsonValueNamed; +import org.jclouds.json.internal.GsonWrapper; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.get; +import static com.google.common.collect.Iterables.getOnlyElement; + +/** + * @author Andrei Savu + */ +public class ParseLoginResponseFromHttpResponse implements Function { + + private ParseFirstJsonValueNamed parser; + + @Inject + ParseLoginResponseFromHttpResponse(GsonWrapper gson) { + this.parser = new ParseFirstJsonValueNamed(checkNotNull(gson, "gsonWrapper"), + new TypeLiteral(){}, "loginresponse"); + } + + @Override + public LoginResponse apply(HttpResponse response) { + checkNotNull(response, "response"); + + LoginResponse login = parser.apply(response); + checkNotNull(login, "loginResponse"); + + String jSessionId = get(Splitter.on("=").split(get(Splitter.on(";").trimResults().split( + getOnlyElement(response.getHeaders().get("Set-Cookie"))), 0)), 1); + + return LoginResponse.builder().copyOf(login).jSessionId(jSessionId).build(); + } +} diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java index 30172aec16..1b44bf5edf 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SessionClientLiveTest.java @@ -41,38 +41,39 @@ public class SessionClientLiveTest extends BaseCloudStackClientLiveTest { private final String PLAIN_TEXT_PASSWORD = "jcl0ud"; private final String DOMAIN = "Partners/jCloud"; - private LoginResponse loginResponse; + private LoginResponse login; @Test(enabled = true) - public void testLoginWithHashedPassword() throws Exception { - loginResponse = client.getSessionClient() + public void testLoginWithHashOfPassword() throws Exception { + login = client.getSessionClient() .loginUserInDomainWithHashOfPassword(USER, DOMAIN, md5Hex(PLAIN_TEXT_PASSWORD)); - assertNotNull(loginResponse); - assertNotNull(loginResponse.getSessionKey()); + assertNotNull(login); + assertNotNull(login.getSessionKey()); + assertNotNull(login.getJSessionId()); } - @Test(dependsOnMethods = "testLoginWithHashedPassword") + @Test(dependsOnMethods = "testLoginWithHashOfPassword") public void testRetrieveUserInfoWithSessionKey() throws Exception { - Account account = client.getSessionClient() - .getAccountByNameUsingSession(loginResponse.getAccountName(), loginResponse.getSessionKey()); + Account account = client.getSessionClient().getAccountByNameUsingSession( + login.getAccountName(), login.getSessionKey(), login.getJSessionId()); assertNotNull(account); - assertEquals(account.getName(), loginResponse.getAccountName()); + assertEquals(account.getName(), login.getAccountName()); User currentUser = find(account.getUsers(), new Predicate() { @Override public boolean apply(User user) { - return user.getId() == loginResponse.getUserId(); + return user.getId() == login.getUserId(); } }); assertNotNull(currentUser); - assertEquals(currentUser.getName(), loginResponse.getUserName()); - assertEquals(currentUser.getDomainId(), loginResponse.getDomainId()); + assertEquals(currentUser.getName(), login.getUserName()); + assertEquals(currentUser.getDomainId(), login.getDomainId()); } - + @Test(dependsOnMethods = "testRetrieveUserInfoWithSessionKey") public void testLogout() throws Exception { - client.getSessionClient().logoutUser(loginResponse.getSessionKey()); + client.getSessionClient().logoutUser(login.getSessionKey()); } }