diff --git a/api/src/com/cloud/user/AccountService.java b/api/src/com/cloud/user/AccountService.java
index b88338704d1..ff7c432d562 100644
--- a/api/src/com/cloud/user/AccountService.java
+++ b/api/src/com/cloud/user/AccountService.java
@@ -1,178 +1,181 @@
-/**
- * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
- *
- * This software is licensed under the GNU General Public License v3 or later.
- *
- * It is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-package com.cloud.user;
-
-import java.util.List;
-
-import com.cloud.api.commands.CreateAccountCmd;
-import com.cloud.api.commands.CreateUserCmd;
-import com.cloud.api.commands.DeleteAccountCmd;
-import com.cloud.api.commands.DeleteUserCmd;
-import com.cloud.api.commands.DisableAccountCmd;
-import com.cloud.api.commands.DisableUserCmd;
-import com.cloud.api.commands.EnableAccountCmd;
-import com.cloud.api.commands.EnableUserCmd;
-import com.cloud.api.commands.ListResourceLimitsCmd;
-import com.cloud.api.commands.LockUserCmd;
-import com.cloud.api.commands.UpdateAccountCmd;
-import com.cloud.api.commands.UpdateResourceLimitCmd;
-import com.cloud.api.commands.UpdateUserCmd;
-import com.cloud.configuration.ResourceLimit;
-import com.cloud.domain.Domain;
-import com.cloud.exception.ConcurrentOperationException;
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.utils.Pair;
-
-public interface AccountService {
-
- /**
- * Creates a new user, stores the password as is so encrypted passwords are recommended.
- *
- * @param cmd
- * the create command that has the username, email, password, account name, domain, timezone, etc. for creating
- * the user.
- * @return the user if created successfully, null otherwise
- */
- UserAccount createAccount(CreateAccountCmd cmd);
-
- /**
- * Deletes a user by userId
- *
- * @param cmd
- * - the delete command defining the id of the user to be deleted.
- * @return true if delete was successful, false otherwise
- */
- boolean deleteUserAccount(DeleteAccountCmd cmd);
-
- /**
- * Disables a user by userId
- *
- * @param cmd
- * the command wrapping the userId parameter
- * @return UserAccount object
- */
- UserAccount disableUser(DisableUserCmd cmd);
-
- /**
- * Enables a user
- *
- * @param cmd
- * - the command containing userId
- * @return UserAccount object
- */
- UserAccount enableUser(EnableUserCmd cmd);
-
- /**
- * Locks a user by userId. A locked user cannot access the API, but will still have running VMs/IP addresses allocated/etc.
- *
- * @param userId
- * @return UserAccount object
- */
- UserAccount lockUser(LockUserCmd cmd);
-
- /**
- * Update a user by userId
- *
- * @param userId
- * @return UserAccount object
- */
- UserAccount updateUser(UpdateUserCmd cmd);
-
- /**
- * Disables an account by accountName and domainId
- *
- * @param disabled
- * account if success
- * @return true if disable was successful, false otherwise
- */
- Account disableAccount(DisableAccountCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException;
-
- /**
- * Enables an account by accountId
- *
- * @param cmd
- * - the enableAccount command defining the accountId to be deleted.
- * @return account object
- */
- Account enableAccount(EnableAccountCmd cmd);
-
- /**
- * Locks an account by accountId. A locked account cannot access the API, but will still have running VMs/IP addresses
- * allocated/etc.
- *
- * @param cmd
- * - the LockAccount command defining the accountId to be locked.
- * @return account object
- */
- Account lockAccount(DisableAccountCmd cmd);
-
- /**
- * Updates an account name
- *
- * @param cmd
- * - the parameter containing accountId
- * @return updated account object
- */
-
- Account updateAccount(UpdateAccountCmd cmd);
-
- /**
- * Updates an existing resource limit with the specified details. If a limit doesn't exist, will create one.
- *
- * @param cmd
- * the command that wraps the domainId, accountId, type, and max parameters
- * @return the updated/created resource limit
- */
- ResourceLimit updateResourceLimit(UpdateResourceLimitCmd cmd);
-
- /**
- * Search for resource limits for the given id and/or account and/or type and/or domain.
- *
- * @param cmd
- * the command wrapping the id, type, account, and domain
- * @return a list of limits that match the criteria
- */
- List extends ResourceLimit> searchForLimits(ListResourceLimitsCmd cmd);
-
- Account getSystemAccount();
-
- User getSystemUser();
-
- User createUser(CreateUserCmd cmd);
-
- boolean deleteUser(DeleteUserCmd deleteUserCmd);
-
- boolean isAdmin(short accountType);
-
- Account finalizeOwner(Account caller, String accountName, Long domainId);
-
- Pair finalizeAccountDomainForList(Account caller, String accountName, Long domainId);
-
- Account getActiveAccount(String accountName, Long domainId);
-
- Account getActiveAccount(Long accountId);
-
- Account getAccount(Long accountId);
-
- User getActiveUser(long userId);
-
- Domain getDomain(long id);
-
- boolean isRootAdmin(short accountType);
-
-}
+/**
+ * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
+ *
+ * This software is licensed under the GNU General Public License v3 or later.
+ *
+ * It is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.cloud.user;
+
+import java.util.List;
+
+import com.cloud.api.commands.CreateAccountCmd;
+import com.cloud.api.commands.CreateUserCmd;
+import com.cloud.api.commands.DeleteAccountCmd;
+import com.cloud.api.commands.DeleteUserCmd;
+import com.cloud.api.commands.DisableAccountCmd;
+import com.cloud.api.commands.DisableUserCmd;
+import com.cloud.api.commands.EnableAccountCmd;
+import com.cloud.api.commands.EnableUserCmd;
+import com.cloud.api.commands.ListResourceLimitsCmd;
+import com.cloud.api.commands.LockUserCmd;
+import com.cloud.api.commands.UpdateAccountCmd;
+import com.cloud.api.commands.UpdateResourceLimitCmd;
+import com.cloud.api.commands.UpdateUserCmd;
+import com.cloud.configuration.ResourceLimit;
+import com.cloud.domain.Domain;
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.utils.Pair;
+
+public interface AccountService {
+
+ /**
+ * Creates a new user, stores the password as is so encrypted passwords are recommended.
+ *
+ * @param cmd
+ * the create command that has the username, email, password, account name, domain, timezone, etc. for creating
+ * the user.
+ * @return the user if created successfully, null otherwise
+ */
+ UserAccount createAccount(CreateAccountCmd cmd);
+
+ /**
+ * Deletes a user by userId
+ *
+ * @param cmd
+ * - the delete command defining the id of the user to be deleted.
+ * @return true if delete was successful, false otherwise
+ */
+ boolean deleteUserAccount(DeleteAccountCmd cmd);
+
+ /**
+ * Disables a user by userId
+ *
+ * @param cmd
+ * the command wrapping the userId parameter
+ * @return UserAccount object
+ */
+ UserAccount disableUser(DisableUserCmd cmd);
+
+ /**
+ * Enables a user
+ *
+ * @param cmd
+ * - the command containing userId
+ * @return UserAccount object
+ */
+ UserAccount enableUser(EnableUserCmd cmd);
+
+ /**
+ * Locks a user by userId. A locked user cannot access the API, but will still have running VMs/IP addresses allocated/etc.
+ *
+ * @param userId
+ * @return UserAccount object
+ */
+ UserAccount lockUser(LockUserCmd cmd);
+
+ /**
+ * Update a user by userId
+ *
+ * @param userId
+ * @return UserAccount object
+ */
+ UserAccount updateUser(UpdateUserCmd cmd);
+
+ /**
+ * Disables an account by accountName and domainId
+ *
+ * @param disabled
+ * account if success
+ * @return true if disable was successful, false otherwise
+ */
+ Account disableAccount(DisableAccountCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException;
+
+ /**
+ * Enables an account by accountId
+ *
+ * @param cmd
+ * - the enableAccount command defining the accountId to be deleted.
+ * @return account object
+ */
+ Account enableAccount(EnableAccountCmd cmd);
+
+ /**
+ * Locks an account by accountId. A locked account cannot access the API, but will still have running VMs/IP addresses
+ * allocated/etc.
+ *
+ * @param cmd
+ * - the LockAccount command defining the accountId to be locked.
+ * @return account object
+ */
+ Account lockAccount(DisableAccountCmd cmd);
+
+ /**
+ * Updates an account name
+ *
+ * @param cmd
+ * - the parameter containing accountId
+ * @return updated account object
+ */
+
+ Account updateAccount(UpdateAccountCmd cmd);
+
+ /**
+ * Updates an existing resource limit with the specified details. If a limit doesn't exist, will create one.
+ *
+ * @param cmd
+ * the command that wraps the domainId, accountId, type, and max parameters
+ * @return the updated/created resource limit
+ */
+ ResourceLimit updateResourceLimit(UpdateResourceLimitCmd cmd);
+
+ /**
+ * Search for resource limits for the given id and/or account and/or type and/or domain.
+ *
+ * @param cmd
+ * the command wrapping the id, type, account, and domain
+ * @return a list of limits that match the criteria
+ */
+ List extends ResourceLimit> searchForLimits(ListResourceLimitsCmd cmd);
+
+ Account getSystemAccount();
+
+ User getSystemUser();
+
+ User createUser(CreateUserCmd cmd);
+ boolean deleteUser(DeleteUserCmd deleteUserCmd);
+
+ boolean isAdmin(short accountType);
+
+ Account finalizeOwner(Account caller, String accountName, Long domainId);
+
+ Pair finalizeAccountDomainForList(Account caller, String accountName, Long domainId);
+
+ Account getActiveAccount(String accountName, Long domainId);
+
+ Account getActiveAccount(Long accountId);
+
+ Account getAccount(Long accountId);
+
+ User getActiveUser(long userId);
+
+ Domain getDomain(long id);
+
+ boolean isRootAdmin(short accountType);
+
+ User getActiveUserByRegistrationToken(String registrationToken);
+
+ void markUserRegistered(long userId);
+
+}
diff --git a/api/src/com/cloud/user/User.java b/api/src/com/cloud/user/User.java
index 85c55ee5ba8..5e2ebea5a17 100644
--- a/api/src/com/cloud/user/User.java
+++ b/api/src/com/cloud/user/User.java
@@ -66,5 +66,9 @@ public interface User extends OwnedBy {
public String getTimezone();
public void setTimezone(String timezone);
+
+ String getRegistrationToken();
+
+ boolean isRegistered();
}
\ No newline at end of file
diff --git a/api/src/com/cloud/user/UserAccount.java b/api/src/com/cloud/user/UserAccount.java
index c40b92d8ef2..636dce221ea 100644
--- a/api/src/com/cloud/user/UserAccount.java
+++ b/api/src/com/cloud/user/UserAccount.java
@@ -53,5 +53,9 @@ public interface UserAccount {
String getAccountState();
- String getTimezone();
+ String getTimezone();
+
+ String getRegistrationToken();
+
+ boolean isRegistered();
}
diff --git a/client/WEB-INF/web.xml b/client/WEB-INF/web.xml
index 21dd36e727d..2ad73c538b1 100644
--- a/client/WEB-INF/web.xml
+++ b/client/WEB-INF/web.xml
@@ -42,6 +42,11 @@
consoleServlet
com.cloud.servlet.ConsoleProxyServlet
+
+
+ registerCompleteServlet
+ com.cloud.servlet.RegisterCompleteServlet
+
apiServlet
@@ -52,4 +57,9 @@
consoleServlet
/console
+
+
+ registerCompleteServlet
+ /cloudkit/complete
+
diff --git a/core/src/com/cloud/user/UserAccountVO.java b/core/src/com/cloud/user/UserAccountVO.java
index 7442d6d96de..af578d76636 100644
--- a/core/src/com/cloud/user/UserAccountVO.java
+++ b/core/src/com/cloud/user/UserAccountVO.java
@@ -76,6 +76,12 @@ public class UserAccountVO implements UserAccount {
@Column(name="timezone")
private String timezone;
+
+ @Column(name="registration_token")
+ private String registrationToken = null;
+
+ @Column(name="is_registered")
+ boolean registered;
@Column(name="account_name", table="account", insertable=false, updatable=false)
private String accountName = null;
@@ -243,5 +249,24 @@ public class UserAccountVO implements UserAccount {
public void setTimezone(String timezone)
{
this.timezone = timezone;
+ }
+
+ @Override
+ public String getRegistrationToken(){
+ return registrationToken;
+ }
+
+ public void setRegistrationToken(String registrationToken)
+ {
+ this.registrationToken = registrationToken;
+ }
+
+ @Override
+ public boolean isRegistered() {
+ return registered;
+ }
+
+ public void setRegistered(boolean registered) {
+ this.registered = registered;
}
}
\ No newline at end of file
diff --git a/core/src/com/cloud/user/UserVO.java b/core/src/com/cloud/user/UserVO.java
index 6709d8d4a07..16f514c29f8 100644
--- a/core/src/com/cloud/user/UserVO.java
+++ b/core/src/com/cloud/user/UserVO.java
@@ -82,6 +82,12 @@ public class UserVO implements User {
@Column(name = "timezone")
private String timezone;
+
+ @Column(name="registration_token")
+ private String registrationToken = null;
+
+ @Column(name="is_registered")
+ boolean registered;
public UserVO() {
}
@@ -204,6 +210,25 @@ public class UserVO implements User {
public void setTimezone(String timezone) {
this.timezone = timezone;
}
+
+ @Override
+ public String getRegistrationToken(){
+ return registrationToken;
+ }
+
+ public void setRegistrationToken(String registrationToken)
+ {
+ this.registrationToken = registrationToken;
+ }
+
+ @Override
+ public boolean isRegistered() {
+ return registered;
+ }
+
+ public void setRegistered(boolean registered) {
+ this.registered = registered;
+ }
@Override
public String toString() {
diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java
index 9806e58c868..2247f103342 100755
--- a/server/src/com/cloud/api/ApiServer.java
+++ b/server/src/com/cloud/api/ApiServer.java
@@ -1,898 +1,900 @@
-/**
- * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
- *
- * This software is licensed under the GNU General Public License v3 or later.
- *
- * It is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package com.cloud.api;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.http.ConnectionClosedException;
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpServerConnection;
-import org.apache.http.HttpStatus;
-import org.apache.http.entity.BasicHttpEntity;
-import org.apache.http.impl.DefaultHttpResponseFactory;
-import org.apache.http.impl.DefaultHttpServerConnection;
-import org.apache.http.impl.NoConnectionReuseStrategy;
-import org.apache.http.impl.SocketHttpServerConnection;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.CoreConnectionPNames;
-import org.apache.http.params.CoreProtocolPNames;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.BasicHttpProcessor;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.HttpRequestHandler;
-import org.apache.http.protocol.HttpRequestHandlerRegistry;
-import org.apache.http.protocol.HttpService;
-import org.apache.http.protocol.ResponseConnControl;
-import org.apache.http.protocol.ResponseContent;
-import org.apache.http.protocol.ResponseDate;
-import org.apache.http.protocol.ResponseServer;
-import org.apache.log4j.Logger;
-
-import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.api.response.ExceptionResponse;
-import com.cloud.api.response.ListResponse;
-import com.cloud.async.AsyncJob;
-import com.cloud.async.AsyncJobManager;
-import com.cloud.async.AsyncJobVO;
-import com.cloud.cluster.StackMaid;
-import com.cloud.configuration.ConfigurationVO;
-import com.cloud.configuration.dao.ConfigurationDao;
-import com.cloud.domain.Domain;
-import com.cloud.domain.DomainVO;
-import com.cloud.event.EventUtils;
-import com.cloud.exception.CloudAuthenticationException;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.server.ManagementServer;
-import com.cloud.user.Account;
-import com.cloud.user.AccountService;
-import com.cloud.user.User;
-import com.cloud.user.UserAccount;
-import com.cloud.user.UserContext;
-import com.cloud.utils.Pair;
-import com.cloud.utils.PropertiesUtil;
-import com.cloud.utils.component.ComponentLocator;
-import com.cloud.utils.concurrency.NamedThreadFactory;
-import com.cloud.utils.db.SearchCriteria;
-import com.cloud.utils.db.Transaction;
-import com.cloud.utils.encoding.Base64;
-
-public class ApiServer implements HttpRequestHandler {
- private static final Logger s_logger = Logger.getLogger(ApiServer.class.getName());
- private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName());
-
- public static final short ADMIN_COMMAND = 1;
- public static final short DOMAIN_ADMIN_COMMAND = 4;
- public static final short RESOURCE_DOMAIN_ADMIN_COMMAND = 2;
- public static final short USER_COMMAND = 8;
- private Properties _apiCommands = null;
- private ApiDispatcher _dispatcher;
- private ManagementServer _ms = null;
- private AccountService _accountMgr = null;
- private AsyncJobManager _asyncMgr = null;
- private Account _systemAccount = null;
- private User _systemUser = null;
-
- private static int _workerCount = 0;
-
- private static ApiServer s_instance = null;
- private static List s_userCommands = null;
- private static List s_resellerCommands = null; // AKA domain-admin
- private static List s_adminCommands = null;
- private static List s_resourceDomainAdminCommands = null;
- private static List s_allCommands = null;
-
- private static ExecutorService _executor = new ThreadPoolExecutor(10, 150, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("ApiServer"));
-
- static {
- s_userCommands = new ArrayList();
- s_resellerCommands = new ArrayList();
- s_adminCommands = new ArrayList();
- s_resourceDomainAdminCommands = new ArrayList();
- s_allCommands = new ArrayList();
- }
-
- private ApiServer() {
- }
-
- public static void initApiServer(String[] apiConfig) {
- if (s_instance == null) {
- s_instance = new ApiServer();
- s_instance.init(apiConfig);
- }
- }
-
- public static ApiServer getInstance() {
- // initApiServer();
- return s_instance;
- }
-
- public Properties get_apiCommands() {
- return _apiCommands;
- }
-
- public void init(String[] apiConfig) {
- try {
- BaseCmd.setComponents(new ApiResponseHelper());
- BaseListCmd.configure();
- _apiCommands = new Properties();
- Properties preProcessedCommands = new Properties();
- if (apiConfig != null) {
- for (String configFile : apiConfig) {
- File commandsFile = PropertiesUtil.findConfigFile(configFile);
- preProcessedCommands.load(new FileInputStream(commandsFile));
- }
- for (Object key : preProcessedCommands.keySet()) {
- String preProcessedCommand = preProcessedCommands.getProperty((String) key);
- String[] commandParts = preProcessedCommand.split(";");
- _apiCommands.put(key, commandParts[0]);
- if (commandParts.length > 1) {
- try {
- short cmdPermissions = Short.parseShort(commandParts[1]);
- if ((cmdPermissions & ADMIN_COMMAND) != 0) {
- s_adminCommands.add((String) key);
- }
- if ((cmdPermissions & RESOURCE_DOMAIN_ADMIN_COMMAND) != 0) {
- s_resourceDomainAdminCommands.add((String) key);
- }
- if ((cmdPermissions & DOMAIN_ADMIN_COMMAND) != 0) {
- s_resellerCommands.add((String) key);
- }
- if ((cmdPermissions & USER_COMMAND) != 0) {
- s_userCommands.add((String) key);
- }
- } catch (NumberFormatException nfe) {
- s_logger.info("Malformed command.properties permissions value, key = " + key + ", value = " + preProcessedCommand);
- }
- }
- }
-
- s_allCommands.addAll(s_adminCommands);
- s_allCommands.addAll(s_resourceDomainAdminCommands);
- s_allCommands.addAll(s_userCommands);
- s_allCommands.addAll(s_resellerCommands);
- }
- } catch (FileNotFoundException fnfex) {
- s_logger.error("Unable to find properites file", fnfex);
- } catch (IOException ioex) {
- s_logger.error("Exception loading properties file", ioex);
- }
-
- _ms = (ManagementServer) ComponentLocator.getComponent(ManagementServer.Name);
- ComponentLocator locator = ComponentLocator.getLocator(ManagementServer.Name);
- _accountMgr = locator.getManager(AccountService.class);
- _asyncMgr = locator.getManager(AsyncJobManager.class);
- _systemAccount = _accountMgr.getSystemAccount();
- _systemUser = _accountMgr.getSystemUser();
- _dispatcher = ApiDispatcher.getInstance();
-
- int apiPort = 8096; // default port
- ConfigurationDao configDao = locator.getDao(ConfigurationDao.class);
- SearchCriteria sc = configDao.createSearchCriteria();
- sc.addAnd("name", SearchCriteria.Op.EQ, "integration.api.port");
- List values = configDao.search(sc, null);
- if ((values != null) && (values.size() > 0)) {
- ConfigurationVO apiPortConfig = values.get(0);
- apiPort = Integer.parseInt(apiPortConfig.getValue());
- }
-
- ListenerThread listenerThread = new ListenerThread(this, apiPort);
- listenerThread.start();
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Override
- public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
- // get some information for the access log...
- StringBuffer sb = new StringBuffer();
- HttpServerConnection connObj = (HttpServerConnection) context.getAttribute("http.connection");
- if (connObj instanceof SocketHttpServerConnection) {
- InetAddress remoteAddr = ((SocketHttpServerConnection) connObj).getRemoteAddress();
- sb.append(remoteAddr.toString() + " -- ");
- }
- sb.append(request.getRequestLine());
-
- try {
- String uri = request.getRequestLine().getUri();
- int requestParamsStartIndex = uri.indexOf('?');
- if (requestParamsStartIndex >= 0) {
- uri = uri.substring(requestParamsStartIndex + 1);
- }
-
- String[] paramArray = uri.split("&");
- if (paramArray.length < 1) {
- s_logger.info("no parameters received for request: " + uri + ", aborting...");
- return;
- }
-
- Map parameterMap = new HashMap();
-
- String responseType = BaseCmd.RESPONSE_TYPE_XML;
- for (String paramEntry : paramArray) {
- String[] paramValue = paramEntry.split("=");
- if (paramValue.length != 2) {
- s_logger.info("malformed parameter: " + paramEntry + ", skipping");
- continue;
- }
- if ("response".equalsIgnoreCase(paramValue[0])) {
- responseType = paramValue[1];
- } else {
- // according to the servlet spec, the parameter map should be in the form (name=String, value=String[]), so
- // parameter values will be stored in an array
- parameterMap.put(/* name */paramValue[0], /* value */new String[] { paramValue[1] });
- }
- }
- try {
- // always trust commands from API port, user context will always be UID_SYSTEM/ACCOUNT_ID_SYSTEM
- UserContext.registerContext(_systemUser.getId(), _systemAccount, null, true);
- sb.insert(0, "(userId=" + User.UID_SYSTEM + " accountId=" + Account.ACCOUNT_ID_SYSTEM + " sessionId=" + null + ") ");
- String responseText = handleRequest(parameterMap, true, responseType, sb);
- sb.append(" 200 " + ((responseText == null) ? 0 : responseText.length()));
-
- writeResponse(response, responseText, HttpStatus.SC_OK, responseType, null);
- } catch (ServerApiException se) {
- String responseText = getSerializedApiError(se.getErrorCode(), se.getDescription(), parameterMap, responseType);
- writeResponse(response, responseText, se.getErrorCode(), responseType, se.getDescription());
- sb.append(" " + se.getErrorCode() + " " + se.getDescription());
- } catch (RuntimeException e) {
- // log runtime exception like NullPointerException to help identify the source easier
- s_logger.error("Unhandled exception, ", e);
- throw e;
- }
- } finally {
- s_accessLogger.info(sb.toString());
- UserContext.unregisterContext();
- }
- }
-
- @SuppressWarnings("rawtypes")
- public String handleRequest(Map params, boolean decode, String responseType, StringBuffer auditTrailSb) throws ServerApiException {
- String response = null;
- String[] command = null;
- try {
- command = (String[]) params.get("command");
- if (command == null) {
- s_logger.error("invalid request, no command sent");
- if (s_logger.isTraceEnabled()) {
- s_logger.trace("dumping request parameters");
- for (Object key : params.keySet()) {
- String keyStr = (String) key;
- String[] value = (String[]) params.get(key);
- s_logger.trace(" key: " + keyStr + ", value: " + ((value == null) ? "'null'" : value[0]));
- }
- }
- throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "Invalid request, no command sent");
- } else {
- Map paramMap = new HashMap();
- Set keys = params.keySet();
- Iterator keysIter = keys.iterator();
- while (keysIter.hasNext()) {
- String key = (String) keysIter.next();
- if ("command".equalsIgnoreCase(key)) {
- continue;
- }
- String[] value = (String[]) params.get(key);
-
- String decodedValue = null;
- if (decode) {
- try {
- decodedValue = URLDecoder.decode(value[0], "UTF-8");
- } catch (UnsupportedEncodingException usex) {
- s_logger.warn(key + " could not be decoded, value = " + value[0]);
- throw new ServerApiException(BaseCmd.PARAM_ERROR, key + " could not be decoded, received value " + value[0]);
- } catch (IllegalArgumentException iae) {
- s_logger.warn(key + " could not be decoded, value = " + value[0]);
- throw new ServerApiException(BaseCmd.PARAM_ERROR, key + " could not be decoded, received value " + value[0] + " which contains illegal characters eg.%");
- }
- } else {
- decodedValue = value[0];
- }
- paramMap.put(key, decodedValue);
- }
- String cmdClassName = _apiCommands.getProperty(command[0]);
- if (cmdClassName != null) {
- Class> cmdClass = Class.forName(cmdClassName);
- BaseCmd cmdObj = (BaseCmd) cmdClass.newInstance();
-
- cmdObj.setResponseType(responseType);
- // This is where the command is either serialized, or directly dispatched
- response = queueCommand(cmdObj, paramMap);
- buildAuditTrail(auditTrailSb, command[0], response);
- } else {
- if (!command[0].equalsIgnoreCase("login") && !command[0].equalsIgnoreCase("logout")) {
- String errorString = "Unknown API command: " + ((command == null) ? "null" : command[0]);
- s_logger.warn(errorString);
- auditTrailSb.append(" " + errorString);
- throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, errorString);
- }
- }
- }
- } catch (Exception ex) {
- if (ex instanceof InvalidParameterValueException) {
- throw new ServerApiException(BaseCmd.PARAM_ERROR, ex.getMessage());
- } else if (ex instanceof PermissionDeniedException) {
- throw new ServerApiException(BaseCmd.ACCOUNT_ERROR, ex.getMessage());
- } else if (ex instanceof ServerApiException) {
- throw (ServerApiException) ex;
- } else {
- s_logger.error("unhandled exception executing api command: " + ((command == null) ? "null" : command[0]), ex);
- throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Internal server error, unable to execute request.");
- }
- }
- return response;
- }
-
- private String queueCommand(BaseCmd cmdObj, Map params) {
- UserContext ctx = UserContext.current();
- Long userId = ctx.getCallerUserId();
- Account account = ctx.getCaller();
- if (cmdObj instanceof BaseAsyncCmd) {
- Long objectId = null;
- if (cmdObj instanceof BaseAsyncCreateCmd) {
- BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd) cmdObj;
- _dispatcher.dispatchCreateCmd(createCmd, params);
- objectId = createCmd.getEntityId();
- params.put("id", objectId.toString());
- } else {
- ApiDispatcher.setupParameters(cmdObj, params);
- }
-
- BaseAsyncCmd asyncCmd = (BaseAsyncCmd) cmdObj;
-
- if (userId != null) {
- params.put("ctxUserId", userId.toString());
- }
- if (account != null) {
- params.put("ctxAccountId", String.valueOf(account.getId()));
- }
-
- long startEventId = ctx.getStartEventId();
- asyncCmd.setStartEventId(startEventId);
-
- // save the scheduled event
- Long eventId = EventUtils.saveScheduledEvent((userId == null) ? User.UID_SYSTEM : userId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(), asyncCmd.getEventDescription(),
- startEventId);
- if (startEventId == 0) {
- // There was no create event before, set current event id as start eventId
- startEventId = eventId;
- }
-
- params.put("ctxStartEventId", String.valueOf(startEventId));
-
- ctx.setAccountId(asyncCmd.getEntityOwnerId());
-
- AsyncJobVO job = new AsyncJobVO();
- job.setInstanceId((objectId == null) ? asyncCmd.getInstanceId() : objectId);
- job.setInstanceType(asyncCmd.getInstanceType());
- job.setUserId(userId);
- job.setAccountId(asyncCmd.getEntityOwnerId());
-
- job.setCmd(cmdObj.getClass().getName());
- job.setCmdInfo(ApiGsonHelper.getBuilder().create().toJson(params));
-
- long jobId = _asyncMgr.submitAsyncJob(job);
-
- if (jobId == 0L) {
- String errorMsg = "Unable to schedule async job for command " + job.getCmd();
- s_logger.warn(errorMsg);
- throw new ServerApiException(BaseCmd.INTERNAL_ERROR, errorMsg);
- }
-
- if (objectId != null) {
- return ((BaseAsyncCreateCmd) asyncCmd).getResponse(jobId, objectId);
- }
- return ApiResponseSerializer.toSerializedString(asyncCmd.getResponse(jobId), asyncCmd.getResponseType());
- } else {
- _dispatcher.dispatch(cmdObj, params);
-
- // if the command is of the listXXXCommand, we will need to also return the
- // the job id and status if possible
- if (cmdObj instanceof BaseListCmd) {
- // validate page size
- validatePageSize((BaseListCmd) cmdObj);
- buildAsyncListResponse((BaseListCmd) cmdObj, account);
- }
- return ApiResponseSerializer.toSerializedString((ResponseObject) cmdObj.getResponseObject(), cmdObj.getResponseType());
- }
- }
-
- private void validatePageSize(BaseListCmd command) {
- List responses = ((ListResponse) command.getResponseObject()).getResponses();
- int defaultPageLimit = BaseCmd._configService.getDefaultPageSize().intValue();
- if (responses != null && responses.size() > defaultPageLimit && command.getPage() == null && command.getPageSize() == null) {
- throw new ServerApiException(BaseCmd.PAGE_LIMIT_EXCEED, "Number of returned objects per page exceed default page limit " + defaultPageLimit
- + "; please specify \"page\"/\"pagesize\" parameters");
- }
- }
-
- private void buildAsyncListResponse(BaseListCmd command, Account account) {
- List responses = ((ListResponse) command.getResponseObject()).getResponses();
- if (responses != null && responses.size() > 0) {
- List extends AsyncJob> jobs = null;
-
- // list all jobs for ROOT admin
- if (account.getType() == Account.ACCOUNT_TYPE_ADMIN) {
- jobs = _asyncMgr.findInstancePendingAsyncJobs(command.getInstanceType(), null);
- } else {
- jobs = _asyncMgr.findInstancePendingAsyncJobs(command.getInstanceType(), account.getId());
- }
-
- if (jobs.size() == 0) {
- return;
- }
-
- // Using maps might possibly be more efficient if the set is large enough but for now, we'll just do a
- // comparison of two lists. Either way, there shouldn't be too many async jobs active for the account.
- for (AsyncJob job : jobs) {
- if (job.getInstanceId() == null) {
- continue;
- }
- for (ResponseObject response : responses) {
- if (response.getObjectId() != null && job.getInstanceId().longValue() == response.getObjectId().longValue()) {
- response.setJobId(job.getId());
- response.setJobStatus(job.getStatus());
- }
- }
- }
- }
- }
-
- private void buildAuditTrail(StringBuffer auditTrailSb, String command, String result) {
- if (result == null) {
- return;
- }
- auditTrailSb.append(" " + HttpServletResponse.SC_OK + " ");
- auditTrailSb.append(result);
- /*
- * if (command.equals("queryAsyncJobResult")){ //For this command we need to also log job status and job resultcode for
- * (Pair pair : resultValues){ String key = pair.first(); if (key.equals("jobstatus")){
- * auditTrailSb.append(" "); auditTrailSb.append(key); auditTrailSb.append("="); auditTrailSb.append(pair.second());
- * }else if (key.equals("jobresultcode")){ auditTrailSb.append(" "); auditTrailSb.append(key); auditTrailSb.append("=");
- * auditTrailSb.append(pair.second()); } } }else { for (Pair pair : resultValues){ if
- * (pair.first().equals("jobid")){ // Its an async job so report the jobid auditTrailSb.append(" ");
- * auditTrailSb.append(pair.first()); auditTrailSb.append("="); auditTrailSb.append(pair.second()); } } }
- */
- }
-
- private static boolean isCommandAvailable(String commandName) {
- boolean isCommandAvailable = false;
- isCommandAvailable = s_allCommands.contains(commandName);
- return isCommandAvailable;
- }
-
- public boolean verifyRequest(Map requestParameters, Long userId) throws ServerApiException {
- try {
- String apiKey = null;
- String secretKey = null;
- String signature = null;
- String unsignedRequest = null;
-
- String[] command = (String[]) requestParameters.get("command");
- if (command == null) {
- s_logger.info("missing command, ignoring request...");
- return false;
- }
-
- String commandName = command[0];
-
- // if userId not null, that mean that user is logged in
- if (userId != null) {
- Long accountId = ApiDBUtils.findUserById(userId).getAccountId();
- Account userAccount = _ms.findAccountById(accountId);
- short accountType = userAccount.getType();
-
- if (!isCommandAvailable(accountType, commandName)) {
- s_logger.warn("The given command:" + commandName + " does not exist");
- throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
- }
- return true;
- } else {
- // check against every available command to see if the command exists or not
- if (!isCommandAvailable(commandName) && !commandName.equals("login") && !commandName.equals("logout")) {
- s_logger.warn("The given command:" + commandName + " does not exist");
- throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
- }
- }
-
- // - build a request string with sorted params, make sure it's all lowercase
- // - sign the request, verify the signature is the same
- List parameterNames = new ArrayList();
-
- for (Object paramNameObj : requestParameters.keySet()) {
- parameterNames.add((String) paramNameObj); // put the name in a list that we'll sort later
- }
-
- Collections.sort(parameterNames);
-
- for (String paramName : parameterNames) {
- // parameters come as name/value pairs in the form String/String[]
- String paramValue = ((String[]) requestParameters.get(paramName))[0];
-
- if ("signature".equalsIgnoreCase(paramName)) {
- signature = paramValue;
- } else {
- if ("apikey".equalsIgnoreCase(paramName)) {
- apiKey = paramValue;
- }
-
- if (unsignedRequest == null) {
- unsignedRequest = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
- } else {
- unsignedRequest = unsignedRequest + "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
- }
- }
- }
-
- // if api/secret key are passed to the parameters
- if ((signature == null) || (apiKey == null)) {
- if (s_logger.isDebugEnabled()) {
- s_logger.info("expired session, missing signature, or missing apiKey -- ignoring request...sig: " + signature + ", apiKey: " + apiKey);
- }
- return false; // no signature, bad request
- }
-
- Transaction txn = Transaction.open(Transaction.CLOUD_DB);
- txn.close();
- User user = null;
- // verify there is a user with this api key
- Pair userAcctPair = _ms.findUserByApiKey(apiKey);
- if (userAcctPair == null) {
- s_logger.info("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey);
- return false;
- }
-
- user = userAcctPair.first();
- Account account = userAcctPair.second();
-
- if (user.getState() != Account.State.enabled || !account.getState().equals(Account.State.enabled)) {
- s_logger.info("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() + "; accountState: "
- + account.getState());
- return false;
- }
-
- UserContext.updateContext(user.getId(), account, null);
-
- if (!isCommandAvailable(account.getType(), commandName)) {
- s_logger.warn("The given command:" + commandName + " does not exist");
- throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
- }
-
- // verify secret key exists
- secretKey = user.getSecretKey();
- if (secretKey == null) {
- s_logger.info("User does not have a secret key associated with the account -- ignoring request, username: " + user.getUsername());
- return false;
- }
-
- unsignedRequest = unsignedRequest.toLowerCase();
-
- Mac mac = Mac.getInstance("HmacSHA1");
- SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
- mac.init(keySpec);
- mac.update(unsignedRequest.getBytes());
- byte[] encryptedBytes = mac.doFinal();
- String computedSignature = Base64.encodeBytes(encryptedBytes);
- boolean equalSig = signature.equals(computedSignature);
- if (!equalSig) {
- s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
- }
- return equalSig;
- } catch (Exception ex) {
- if (ex instanceof ServerApiException && ((ServerApiException) ex).getErrorCode() == BaseCmd.UNSUPPORTED_ACTION_ERROR) {
- throw (ServerApiException) ex;
- }
- s_logger.error("unable to verifty request signature", ex);
- }
- return false;
- }
-
- public void loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, Map requestParameters) throws CloudAuthenticationException {
- // We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist
- // we will default to ROOT
- if (domainId == null) {
- if (domainPath == null || domainPath.trim().length() == 0) {
- domainId = DomainVO.ROOT_DOMAIN;
- } else {
- Domain domainObj = _ms.findDomainByPath(domainPath);
- if (domainObj != null) {
- domainId = domainObj.getId();
- } else { // if an unknown path is passed in, fail the login call
- throw new CloudAuthenticationException("Unable to find the domain from the path " + domainPath);
- }
- }
- }
-
- UserAccount userAcct = _ms.authenticateUser(username, password, domainId, requestParameters);
- if (userAcct != null) {
- String timezone = userAcct.getTimezone();
- float offsetInHrs = 0f;
- if (timezone != null) {
- TimeZone t = TimeZone.getTimeZone(timezone);
- s_logger.info("Current user logged in under " + timezone + " timezone");
-
- java.util.Date date = new java.util.Date();
- long longDate = date.getTime();
- float offsetInMs = (t.getOffset(longDate));
- offsetInHrs = offsetInMs / (1000 * 60 * 60);
- s_logger.info("Timezone offset from UTC is: " + offsetInHrs);
- }
-
- Account account = _ms.findAccountById(userAcct.getAccountId());
-
- // set the userId and account object for everyone
- session.setAttribute("userid", userAcct.getId());
- session.setAttribute("username", userAcct.getUsername());
- session.setAttribute("firstname", userAcct.getFirstname());
- session.setAttribute("lastname", userAcct.getLastname());
- session.setAttribute("accountobj", account);
- session.setAttribute("account", account.getAccountName());
- session.setAttribute("domainid", account.getDomainId());
- session.setAttribute("type", Short.valueOf(account.getType()).toString());
-
- if (timezone != null) {
- session.setAttribute("timezone", timezone);
- session.setAttribute("timezoneoffset", Float.valueOf(offsetInHrs).toString());
- }
-
- // (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that
- // to the login response so that session-based authenticators know to send the key back
- SecureRandom sesssionKeyRandom = new SecureRandom();
- byte sessionKeyBytes[] = new byte[20];
- sesssionKeyRandom.nextBytes(sessionKeyBytes);
- String sessionKey = Base64.encodeBytes(sessionKeyBytes);
- session.setAttribute("sessionkey", sessionKey);
-
- return;
- }
- throw new CloudAuthenticationException("Unable to find user " + username + " in domain " + domainId);
- }
-
- public void logoutUser(long userId) {
- _ms.logoutUser(Long.valueOf(userId));
- return;
- }
-
- public boolean verifyUser(Long userId) {
- User user = _ms.findUserById(userId);
- Account account = null;
- if (user != null) {
- account = _ms.findAccountById(user.getAccountId());
- }
-
- if ((user == null) || (user.getRemoved() != null) || !user.getState().equals(Account.State.enabled) || (account == null) || !account.getState().equals(Account.State.enabled)) {
- s_logger.warn("Deleted/Disabled/Locked user with id=" + userId + " attempting to access public API");
- return false;
- }
- return true;
- }
-
- public static boolean isCommandAvailable(short accountType, String commandName) {
- boolean isCommandAvailable = false;
- switch (accountType) {
- case Account.ACCOUNT_TYPE_ADMIN:
- isCommandAvailable = s_adminCommands.contains(commandName);
- break;
- case Account.ACCOUNT_TYPE_DOMAIN_ADMIN:
- isCommandAvailable = s_resellerCommands.contains(commandName);
- break;
- case Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN:
- isCommandAvailable = s_resourceDomainAdminCommands.contains(commandName);
- break;
- case Account.ACCOUNT_TYPE_NORMAL:
- isCommandAvailable = s_userCommands.contains(commandName);
- break;
- }
- return isCommandAvailable;
- }
-
- // FIXME: rather than isError, we might was to pass in the status code to give more flexibility
- private void writeResponse(HttpResponse resp, final String responseText, final int statusCode, String responseType, String reasonPhrase) {
- try {
- resp.setStatusCode(statusCode);
- resp.setReasonPhrase(reasonPhrase);
-
- BasicHttpEntity body = new BasicHttpEntity();
- if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
- // JSON response
- body.setContentType("text/javascript");
- if (responseText == null) {
- body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes("UTF-8")));
- }
- } else {
- body.setContentType("text/xml");
- if (responseText == null) {
- body.setContent(new ByteArrayInputStream("Internal Server Error".getBytes("UTF-8")));
- }
- }
-
- if (responseText != null) {
- body.setContent(new ByteArrayInputStream(responseText.getBytes("UTF-8")));
- }
- resp.setEntity(body);
- } catch (Exception ex) {
- s_logger.error("error!", ex);
- }
- }
-
- // FIXME: the following two threads are copied from
- // http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpServer.java
- // we have to cite a license if we are using this code directly, so we need to add the appropriate citation or modify the
- // code to be very specific to our needs
- static class ListenerThread extends Thread {
- private HttpService _httpService = null;
- private ServerSocket _serverSocket = null;
- private HttpParams _params = null;
-
- public ListenerThread(ApiServer requestHandler, int port) {
- try {
- _serverSocket = new ServerSocket(port);
- } catch (IOException ioex) {
- s_logger.error("error initializing api server", ioex);
- return;
- }
-
- _params = new BasicHttpParams();
- _params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 30000).setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
- .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false).setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
- .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");
-
- // Set up the HTTP protocol processor
- BasicHttpProcessor httpproc = new BasicHttpProcessor();
- httpproc.addInterceptor(new ResponseDate());
- httpproc.addInterceptor(new ResponseServer());
- httpproc.addInterceptor(new ResponseContent());
- httpproc.addInterceptor(new ResponseConnControl());
-
- // Set up request handlers
- HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
- reqistry.register("*", requestHandler);
-
- // Set up the HTTP service
- _httpService = new HttpService(httpproc, new NoConnectionReuseStrategy(), new DefaultHttpResponseFactory());
- _httpService.setParams(_params);
- _httpService.setHandlerResolver(reqistry);
- }
-
- @Override
- public void run() {
- s_logger.info("ApiServer listening on port " + _serverSocket.getLocalPort());
- while (!Thread.interrupted()) {
- try {
- // Set up HTTP connection
- Socket socket = _serverSocket.accept();
- DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
- conn.bind(socket, _params);
-
- // Execute a new worker task to handle the request
- _executor.execute(new WorkerTask(_httpService, conn, _workerCount++));
- } catch (InterruptedIOException ex) {
- break;
- } catch (IOException e) {
- s_logger.error("I/O error initializing connection thread", e);
- break;
- }
- }
- }
- }
-
- static class WorkerTask implements Runnable {
- private final HttpService _httpService;
- private final HttpServerConnection _conn;
-
- public WorkerTask(final HttpService httpService, final HttpServerConnection conn, final int count) {
- _httpService = httpService;
- _conn = conn;
- }
-
- @Override
- public void run() {
- HttpContext context = new BasicHttpContext(null);
- try {
- while (!Thread.interrupted() && _conn.isOpen()) {
- try {
- _httpService.handleRequest(_conn, context);
- _conn.close();
- } finally {
- StackMaid.current().exitCleanup();
- }
- }
- } catch (ConnectionClosedException ex) {
- if (s_logger.isTraceEnabled()) {
- s_logger.trace("ApiServer: Client closed connection");
- }
- } catch (IOException ex) {
- if (s_logger.isTraceEnabled()) {
- s_logger.trace("ApiServer: IOException - " + ex);
- }
- } catch (HttpException ex) {
- s_logger.warn("ApiServer: Unrecoverable HTTP protocol violation" + ex);
- } finally {
- try {
- _conn.shutdown();
- } catch (IOException ignore) {
- }
- }
- }
- }
-
- public String getSerializedApiError(int errorCode, String errorText, Map apiCommandParams, String responseType) {
- String responseName = null;
- String cmdClassName = null;
-
- String responseText = null;
-
- try {
- if (errorCode == BaseCmd.UNSUPPORTED_ACTION_ERROR || apiCommandParams == null || apiCommandParams.isEmpty()) {
- responseName = "errorresponse";
- } else {
- String cmdName = ((String[]) apiCommandParams.get("command"))[0];
- cmdClassName = _apiCommands.getProperty(cmdName);
- if (cmdClassName != null) {
- Class> claz = Class.forName(cmdClassName);
- responseName = ((BaseCmd) claz.newInstance()).getCommandName();
- } else {
- responseName = "errorresponse";
- }
- }
-
- ExceptionResponse apiResponse = new ExceptionResponse();
- apiResponse.setErrorCode(errorCode);
- apiResponse.setErrorText(errorText);
- apiResponse.setResponseName(responseName);
- responseText = ApiResponseSerializer.toSerializedString(apiResponse, responseType);
-
- } catch (Exception e) {
- s_logger.error("Exception responding to http request", e);
- }
- return responseText;
- }
-}
+/**
+ * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
+ *
+ * This software is licensed under the GNU General Public License v3 or later.
+ *
+ * It is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package com.cloud.api;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.http.ConnectionClosedException;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpServerConnection;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.SocketHttpServerConnection;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.protocol.HttpRequestHandlerRegistry;
+import org.apache.http.protocol.HttpService;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.log4j.Logger;
+
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.api.response.ExceptionResponse;
+import com.cloud.api.response.ListResponse;
+import com.cloud.async.AsyncJob;
+import com.cloud.async.AsyncJobManager;
+import com.cloud.async.AsyncJobVO;
+import com.cloud.cluster.StackMaid;
+import com.cloud.configuration.ConfigurationVO;
+import com.cloud.configuration.dao.ConfigurationDao;
+import com.cloud.domain.Domain;
+import com.cloud.domain.DomainVO;
+import com.cloud.event.EventUtils;
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.server.ManagementServer;
+import com.cloud.user.Account;
+import com.cloud.user.AccountService;
+import com.cloud.user.User;
+import com.cloud.user.UserAccount;
+import com.cloud.user.UserContext;
+import com.cloud.utils.Pair;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.component.ComponentLocator;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.encoding.Base64;
+
+public class ApiServer implements HttpRequestHandler {
+ private static final Logger s_logger = Logger.getLogger(ApiServer.class.getName());
+ private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName());
+
+ public static final short ADMIN_COMMAND = 1;
+ public static final short DOMAIN_ADMIN_COMMAND = 4;
+ public static final short RESOURCE_DOMAIN_ADMIN_COMMAND = 2;
+ public static final short USER_COMMAND = 8;
+ private Properties _apiCommands = null;
+ private ApiDispatcher _dispatcher;
+ private ManagementServer _ms = null;
+ private AccountService _accountMgr = null;
+ private AsyncJobManager _asyncMgr = null;
+ private Account _systemAccount = null;
+ private User _systemUser = null;
+
+ private static int _workerCount = 0;
+
+ private static ApiServer s_instance = null;
+ private static List s_userCommands = null;
+ private static List s_resellerCommands = null; // AKA domain-admin
+ private static List s_adminCommands = null;
+ private static List s_resourceDomainAdminCommands = null;
+ private static List s_allCommands = null;
+
+ private static ExecutorService _executor = new ThreadPoolExecutor(10, 150, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("ApiServer"));
+
+ static {
+ s_userCommands = new ArrayList();
+ s_resellerCommands = new ArrayList();
+ s_adminCommands = new ArrayList();
+ s_resourceDomainAdminCommands = new ArrayList();
+ s_allCommands = new ArrayList();
+ }
+
+ private ApiServer() {
+ }
+
+ public static void initApiServer(String[] apiConfig) {
+ if (s_instance == null) {
+ s_instance = new ApiServer();
+ s_instance.init(apiConfig);
+ }
+ }
+
+ public static ApiServer getInstance() {
+ // initApiServer();
+ return s_instance;
+ }
+
+ public Properties get_apiCommands() {
+ return _apiCommands;
+ }
+
+ public void init(String[] apiConfig) {
+ try {
+ BaseCmd.setComponents(new ApiResponseHelper());
+ BaseListCmd.configure();
+ _apiCommands = new Properties();
+ Properties preProcessedCommands = new Properties();
+ if (apiConfig != null) {
+ for (String configFile : apiConfig) {
+ File commandsFile = PropertiesUtil.findConfigFile(configFile);
+ preProcessedCommands.load(new FileInputStream(commandsFile));
+ }
+ for (Object key : preProcessedCommands.keySet()) {
+ String preProcessedCommand = preProcessedCommands.getProperty((String) key);
+ String[] commandParts = preProcessedCommand.split(";");
+ _apiCommands.put(key, commandParts[0]);
+ if (commandParts.length > 1) {
+ try {
+ short cmdPermissions = Short.parseShort(commandParts[1]);
+ if ((cmdPermissions & ADMIN_COMMAND) != 0) {
+ s_adminCommands.add((String) key);
+ }
+ if ((cmdPermissions & RESOURCE_DOMAIN_ADMIN_COMMAND) != 0) {
+ s_resourceDomainAdminCommands.add((String) key);
+ }
+ if ((cmdPermissions & DOMAIN_ADMIN_COMMAND) != 0) {
+ s_resellerCommands.add((String) key);
+ }
+ if ((cmdPermissions & USER_COMMAND) != 0) {
+ s_userCommands.add((String) key);
+ }
+ } catch (NumberFormatException nfe) {
+ s_logger.info("Malformed command.properties permissions value, key = " + key + ", value = " + preProcessedCommand);
+ }
+ }
+ }
+
+ s_allCommands.addAll(s_adminCommands);
+ s_allCommands.addAll(s_resourceDomainAdminCommands);
+ s_allCommands.addAll(s_userCommands);
+ s_allCommands.addAll(s_resellerCommands);
+ }
+ } catch (FileNotFoundException fnfex) {
+ s_logger.error("Unable to find properites file", fnfex);
+ } catch (IOException ioex) {
+ s_logger.error("Exception loading properties file", ioex);
+ }
+
+ _ms = (ManagementServer) ComponentLocator.getComponent(ManagementServer.Name);
+ ComponentLocator locator = ComponentLocator.getLocator(ManagementServer.Name);
+ _accountMgr = locator.getManager(AccountService.class);
+ _asyncMgr = locator.getManager(AsyncJobManager.class);
+ _systemAccount = _accountMgr.getSystemAccount();
+ _systemUser = _accountMgr.getSystemUser();
+ _dispatcher = ApiDispatcher.getInstance();
+
+ int apiPort = 8096; // default port
+ ConfigurationDao configDao = locator.getDao(ConfigurationDao.class);
+ SearchCriteria sc = configDao.createSearchCriteria();
+ sc.addAnd("name", SearchCriteria.Op.EQ, "integration.api.port");
+ List values = configDao.search(sc, null);
+ if ((values != null) && (values.size() > 0)) {
+ ConfigurationVO apiPortConfig = values.get(0);
+ apiPort = Integer.parseInt(apiPortConfig.getValue());
+ }
+
+ ListenerThread listenerThread = new ListenerThread(this, apiPort);
+ listenerThread.start();
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
+ // get some information for the access log...
+ StringBuffer sb = new StringBuffer();
+ HttpServerConnection connObj = (HttpServerConnection) context.getAttribute("http.connection");
+ if (connObj instanceof SocketHttpServerConnection) {
+ InetAddress remoteAddr = ((SocketHttpServerConnection) connObj).getRemoteAddress();
+ sb.append(remoteAddr.toString() + " -- ");
+ }
+ sb.append(request.getRequestLine());
+
+ try {
+ String uri = request.getRequestLine().getUri();
+ int requestParamsStartIndex = uri.indexOf('?');
+ if (requestParamsStartIndex >= 0) {
+ uri = uri.substring(requestParamsStartIndex + 1);
+ }
+
+ String[] paramArray = uri.split("&");
+ if (paramArray.length < 1) {
+ s_logger.info("no parameters received for request: " + uri + ", aborting...");
+ return;
+ }
+
+ Map parameterMap = new HashMap();
+
+ String responseType = BaseCmd.RESPONSE_TYPE_XML;
+ for (String paramEntry : paramArray) {
+ String[] paramValue = paramEntry.split("=");
+ if (paramValue.length != 2) {
+ s_logger.info("malformed parameter: " + paramEntry + ", skipping");
+ continue;
+ }
+ if ("response".equalsIgnoreCase(paramValue[0])) {
+ responseType = paramValue[1];
+ } else {
+ // according to the servlet spec, the parameter map should be in the form (name=String, value=String[]), so
+ // parameter values will be stored in an array
+ parameterMap.put(/* name */paramValue[0], /* value */new String[] { paramValue[1] });
+ }
+ }
+ try {
+ // always trust commands from API port, user context will always be UID_SYSTEM/ACCOUNT_ID_SYSTEM
+ UserContext.registerContext(_systemUser.getId(), _systemAccount, null, true);
+ sb.insert(0, "(userId=" + User.UID_SYSTEM + " accountId=" + Account.ACCOUNT_ID_SYSTEM + " sessionId=" + null + ") ");
+ String responseText = handleRequest(parameterMap, true, responseType, sb);
+ sb.append(" 200 " + ((responseText == null) ? 0 : responseText.length()));
+
+ writeResponse(response, responseText, HttpStatus.SC_OK, responseType, null);
+ } catch (ServerApiException se) {
+ String responseText = getSerializedApiError(se.getErrorCode(), se.getDescription(), parameterMap, responseType);
+ writeResponse(response, responseText, se.getErrorCode(), responseType, se.getDescription());
+ sb.append(" " + se.getErrorCode() + " " + se.getDescription());
+ } catch (RuntimeException e) {
+ // log runtime exception like NullPointerException to help identify the source easier
+ s_logger.error("Unhandled exception, ", e);
+ throw e;
+ }
+ } finally {
+ s_accessLogger.info(sb.toString());
+ UserContext.unregisterContext();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public String handleRequest(Map params, boolean decode, String responseType, StringBuffer auditTrailSb) throws ServerApiException {
+ String response = null;
+ String[] command = null;
+ try {
+ command = (String[]) params.get("command");
+ if (command == null) {
+ s_logger.error("invalid request, no command sent");
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("dumping request parameters");
+ for (Object key : params.keySet()) {
+ String keyStr = (String) key;
+ String[] value = (String[]) params.get(key);
+ s_logger.trace(" key: " + keyStr + ", value: " + ((value == null) ? "'null'" : value[0]));
+ }
+ }
+ throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "Invalid request, no command sent");
+ } else {
+ Map paramMap = new HashMap();
+ Set keys = params.keySet();
+ Iterator keysIter = keys.iterator();
+ while (keysIter.hasNext()) {
+ String key = (String) keysIter.next();
+ if ("command".equalsIgnoreCase(key)) {
+ continue;
+ }
+ String[] value = (String[]) params.get(key);
+
+ String decodedValue = null;
+ if (decode) {
+ try {
+ decodedValue = URLDecoder.decode(value[0], "UTF-8");
+ } catch (UnsupportedEncodingException usex) {
+ s_logger.warn(key + " could not be decoded, value = " + value[0]);
+ throw new ServerApiException(BaseCmd.PARAM_ERROR, key + " could not be decoded, received value " + value[0]);
+ } catch (IllegalArgumentException iae) {
+ s_logger.warn(key + " could not be decoded, value = " + value[0]);
+ throw new ServerApiException(BaseCmd.PARAM_ERROR, key + " could not be decoded, received value " + value[0] + " which contains illegal characters eg.%");
+ }
+ } else {
+ decodedValue = value[0];
+ }
+ paramMap.put(key, decodedValue);
+ }
+ String cmdClassName = _apiCommands.getProperty(command[0]);
+ if (cmdClassName != null) {
+ Class> cmdClass = Class.forName(cmdClassName);
+ BaseCmd cmdObj = (BaseCmd) cmdClass.newInstance();
+
+ cmdObj.setResponseType(responseType);
+ // This is where the command is either serialized, or directly dispatched
+ response = queueCommand(cmdObj, paramMap);
+ buildAuditTrail(auditTrailSb, command[0], response);
+ } else {
+ if (!command[0].equalsIgnoreCase("login") && !command[0].equalsIgnoreCase("logout")) {
+ String errorString = "Unknown API command: " + ((command == null) ? "null" : command[0]);
+ s_logger.warn(errorString);
+ auditTrailSb.append(" " + errorString);
+ throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, errorString);
+ }
+ }
+ }
+ } catch (Exception ex) {
+ if (ex instanceof InvalidParameterValueException) {
+ throw new ServerApiException(BaseCmd.PARAM_ERROR, ex.getMessage());
+ } else if (ex instanceof PermissionDeniedException) {
+ throw new ServerApiException(BaseCmd.ACCOUNT_ERROR, ex.getMessage());
+ } else if (ex instanceof ServerApiException) {
+ throw (ServerApiException) ex;
+ } else {
+ s_logger.error("unhandled exception executing api command: " + ((command == null) ? "null" : command[0]), ex);
+ throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Internal server error, unable to execute request.");
+ }
+ }
+ return response;
+ }
+
+ private String queueCommand(BaseCmd cmdObj, Map params) {
+ UserContext ctx = UserContext.current();
+ Long userId = ctx.getCallerUserId();
+ Account account = ctx.getCaller();
+ if (cmdObj instanceof BaseAsyncCmd) {
+ Long objectId = null;
+ if (cmdObj instanceof BaseAsyncCreateCmd) {
+ BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd) cmdObj;
+ _dispatcher.dispatchCreateCmd(createCmd, params);
+ objectId = createCmd.getEntityId();
+ params.put("id", objectId.toString());
+ } else {
+ ApiDispatcher.setupParameters(cmdObj, params);
+ }
+
+ BaseAsyncCmd asyncCmd = (BaseAsyncCmd) cmdObj;
+
+ if (userId != null) {
+ params.put("ctxUserId", userId.toString());
+ }
+ if (account != null) {
+ params.put("ctxAccountId", String.valueOf(account.getId()));
+ }
+
+ long startEventId = ctx.getStartEventId();
+ asyncCmd.setStartEventId(startEventId);
+
+ // save the scheduled event
+ Long eventId = EventUtils.saveScheduledEvent((userId == null) ? User.UID_SYSTEM : userId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(), asyncCmd.getEventDescription(),
+ startEventId);
+ if (startEventId == 0) {
+ // There was no create event before, set current event id as start eventId
+ startEventId = eventId;
+ }
+
+ params.put("ctxStartEventId", String.valueOf(startEventId));
+
+ ctx.setAccountId(asyncCmd.getEntityOwnerId());
+
+ AsyncJobVO job = new AsyncJobVO();
+ job.setInstanceId((objectId == null) ? asyncCmd.getInstanceId() : objectId);
+ job.setInstanceType(asyncCmd.getInstanceType());
+ job.setUserId(userId);
+ job.setAccountId(asyncCmd.getEntityOwnerId());
+
+ job.setCmd(cmdObj.getClass().getName());
+ job.setCmdInfo(ApiGsonHelper.getBuilder().create().toJson(params));
+
+ long jobId = _asyncMgr.submitAsyncJob(job);
+
+ if (jobId == 0L) {
+ String errorMsg = "Unable to schedule async job for command " + job.getCmd();
+ s_logger.warn(errorMsg);
+ throw new ServerApiException(BaseCmd.INTERNAL_ERROR, errorMsg);
+ }
+
+ if (objectId != null) {
+ return ((BaseAsyncCreateCmd) asyncCmd).getResponse(jobId, objectId);
+ }
+ return ApiResponseSerializer.toSerializedString(asyncCmd.getResponse(jobId), asyncCmd.getResponseType());
+ } else {
+ _dispatcher.dispatch(cmdObj, params);
+
+ // if the command is of the listXXXCommand, we will need to also return the
+ // the job id and status if possible
+ if (cmdObj instanceof BaseListCmd) {
+ // validate page size
+ validatePageSize((BaseListCmd) cmdObj);
+ buildAsyncListResponse((BaseListCmd) cmdObj, account);
+ }
+ return ApiResponseSerializer.toSerializedString((ResponseObject) cmdObj.getResponseObject(), cmdObj.getResponseType());
+ }
+ }
+
+ private void validatePageSize(BaseListCmd command) {
+ List responses = ((ListResponse) command.getResponseObject()).getResponses();
+ int defaultPageLimit = BaseCmd._configService.getDefaultPageSize().intValue();
+ if (responses != null && responses.size() > defaultPageLimit && command.getPage() == null && command.getPageSize() == null) {
+ throw new ServerApiException(BaseCmd.PAGE_LIMIT_EXCEED, "Number of returned objects per page exceed default page limit " + defaultPageLimit
+ + "; please specify \"page\"/\"pagesize\" parameters");
+ }
+ }
+
+ private void buildAsyncListResponse(BaseListCmd command, Account account) {
+ List responses = ((ListResponse) command.getResponseObject()).getResponses();
+ if (responses != null && responses.size() > 0) {
+ List extends AsyncJob> jobs = null;
+
+ // list all jobs for ROOT admin
+ if (account.getType() == Account.ACCOUNT_TYPE_ADMIN) {
+ jobs = _asyncMgr.findInstancePendingAsyncJobs(command.getInstanceType(), null);
+ } else {
+ jobs = _asyncMgr.findInstancePendingAsyncJobs(command.getInstanceType(), account.getId());
+ }
+
+ if (jobs.size() == 0) {
+ return;
+ }
+
+ // Using maps might possibly be more efficient if the set is large enough but for now, we'll just do a
+ // comparison of two lists. Either way, there shouldn't be too many async jobs active for the account.
+ for (AsyncJob job : jobs) {
+ if (job.getInstanceId() == null) {
+ continue;
+ }
+ for (ResponseObject response : responses) {
+ if (response.getObjectId() != null && job.getInstanceId().longValue() == response.getObjectId().longValue()) {
+ response.setJobId(job.getId());
+ response.setJobStatus(job.getStatus());
+ }
+ }
+ }
+ }
+ }
+
+ private void buildAuditTrail(StringBuffer auditTrailSb, String command, String result) {
+ if (result == null) {
+ return;
+ }
+ auditTrailSb.append(" " + HttpServletResponse.SC_OK + " ");
+ auditTrailSb.append(result);
+ /*
+ * if (command.equals("queryAsyncJobResult")){ //For this command we need to also log job status and job resultcode for
+ * (Pair pair : resultValues){ String key = pair.first(); if (key.equals("jobstatus")){
+ * auditTrailSb.append(" "); auditTrailSb.append(key); auditTrailSb.append("="); auditTrailSb.append(pair.second());
+ * }else if (key.equals("jobresultcode")){ auditTrailSb.append(" "); auditTrailSb.append(key); auditTrailSb.append("=");
+ * auditTrailSb.append(pair.second()); } } }else { for (Pair pair : resultValues){ if
+ * (pair.first().equals("jobid")){ // Its an async job so report the jobid auditTrailSb.append(" ");
+ * auditTrailSb.append(pair.first()); auditTrailSb.append("="); auditTrailSb.append(pair.second()); } } }
+ */
+ }
+
+ private static boolean isCommandAvailable(String commandName) {
+ boolean isCommandAvailable = false;
+ isCommandAvailable = s_allCommands.contains(commandName);
+ return isCommandAvailable;
+ }
+
+ public boolean verifyRequest(Map requestParameters, Long userId) throws ServerApiException {
+ try {
+ String apiKey = null;
+ String secretKey = null;
+ String signature = null;
+ String unsignedRequest = null;
+
+ String[] command = (String[]) requestParameters.get("command");
+ if (command == null) {
+ s_logger.info("missing command, ignoring request...");
+ return false;
+ }
+
+ String commandName = command[0];
+
+ // if userId not null, that mean that user is logged in
+ if (userId != null) {
+ Long accountId = ApiDBUtils.findUserById(userId).getAccountId();
+ Account userAccount = _ms.findAccountById(accountId);
+ short accountType = userAccount.getType();
+
+ if (!isCommandAvailable(accountType, commandName)) {
+ s_logger.warn("The given command:" + commandName + " does not exist");
+ throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
+ }
+ return true;
+ } else {
+ // check against every available command to see if the command exists or not
+ if (!isCommandAvailable(commandName) && !commandName.equals("login") && !commandName.equals("logout")) {
+ s_logger.warn("The given command:" + commandName + " does not exist");
+ throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
+ }
+ }
+
+ // - build a request string with sorted params, make sure it's all lowercase
+ // - sign the request, verify the signature is the same
+ List parameterNames = new ArrayList();
+
+ for (Object paramNameObj : requestParameters.keySet()) {
+ parameterNames.add((String) paramNameObj); // put the name in a list that we'll sort later
+ }
+
+ Collections.sort(parameterNames);
+
+ for (String paramName : parameterNames) {
+ // parameters come as name/value pairs in the form String/String[]
+ String paramValue = ((String[]) requestParameters.get(paramName))[0];
+
+ if ("signature".equalsIgnoreCase(paramName)) {
+ signature = paramValue;
+ } else {
+ if ("apikey".equalsIgnoreCase(paramName)) {
+ apiKey = paramValue;
+ }
+
+ if (unsignedRequest == null) {
+ unsignedRequest = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
+ } else {
+ unsignedRequest = unsignedRequest + "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
+ }
+ }
+ }
+
+ // if api/secret key are passed to the parameters
+ if ((signature == null) || (apiKey == null)) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.info("expired session, missing signature, or missing apiKey -- ignoring request...sig: " + signature + ", apiKey: " + apiKey);
+ }
+ return false; // no signature, bad request
+ }
+
+ Transaction txn = Transaction.open(Transaction.CLOUD_DB);
+ txn.close();
+ User user = null;
+ // verify there is a user with this api key
+ Pair userAcctPair = _ms.findUserByApiKey(apiKey);
+ if (userAcctPair == null) {
+ s_logger.info("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey);
+ return false;
+ }
+
+ user = userAcctPair.first();
+ Account account = userAcctPair.second();
+
+ if (user.getState() != Account.State.enabled || !account.getState().equals(Account.State.enabled)) {
+ s_logger.info("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() + "; accountState: "
+ + account.getState());
+ return false;
+ }
+
+ UserContext.updateContext(user.getId(), account, null);
+
+ if (!isCommandAvailable(account.getType(), commandName)) {
+ s_logger.warn("The given command:" + commandName + " does not exist");
+ throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist");
+ }
+
+ // verify secret key exists
+ secretKey = user.getSecretKey();
+ if (secretKey == null) {
+ s_logger.info("User does not have a secret key associated with the account -- ignoring request, username: " + user.getUsername());
+ return false;
+ }
+
+ unsignedRequest = unsignedRequest.toLowerCase();
+
+ Mac mac = Mac.getInstance("HmacSHA1");
+ SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
+ mac.init(keySpec);
+ mac.update(unsignedRequest.getBytes());
+ byte[] encryptedBytes = mac.doFinal();
+ String computedSignature = Base64.encodeBytes(encryptedBytes);
+ boolean equalSig = signature.equals(computedSignature);
+ if (!equalSig) {
+ s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
+ }
+ return equalSig;
+ } catch (Exception ex) {
+ if (ex instanceof ServerApiException && ((ServerApiException) ex).getErrorCode() == BaseCmd.UNSUPPORTED_ACTION_ERROR) {
+ throw (ServerApiException) ex;
+ }
+ s_logger.error("unable to verifty request signature", ex);
+ }
+ return false;
+ }
+
+ public void loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, Map requestParameters) throws CloudAuthenticationException {
+ // We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist
+ // we will default to ROOT
+ if (domainId == null) {
+ if (domainPath == null || domainPath.trim().length() == 0) {
+ domainId = DomainVO.ROOT_DOMAIN;
+ } else {
+ Domain domainObj = _ms.findDomainByPath(domainPath);
+ if (domainObj != null) {
+ domainId = domainObj.getId();
+ } else { // if an unknown path is passed in, fail the login call
+ throw new CloudAuthenticationException("Unable to find the domain from the path " + domainPath);
+ }
+ }
+ }
+
+ UserAccount userAcct = _ms.authenticateUser(username, password, domainId, requestParameters);
+ if (userAcct != null) {
+ String timezone = userAcct.getTimezone();
+ float offsetInHrs = 0f;
+ if (timezone != null) {
+ TimeZone t = TimeZone.getTimeZone(timezone);
+ s_logger.info("Current user logged in under " + timezone + " timezone");
+
+ java.util.Date date = new java.util.Date();
+ long longDate = date.getTime();
+ float offsetInMs = (t.getOffset(longDate));
+ offsetInHrs = offsetInMs / (1000 * 60 * 60);
+ s_logger.info("Timezone offset from UTC is: " + offsetInHrs);
+ }
+
+ Account account = _ms.findAccountById(userAcct.getAccountId());
+
+ // set the userId and account object for everyone
+ session.setAttribute("userid", userAcct.getId());
+ session.setAttribute("username", userAcct.getUsername());
+ session.setAttribute("firstname", userAcct.getFirstname());
+ session.setAttribute("lastname", userAcct.getLastname());
+ session.setAttribute("accountobj", account);
+ session.setAttribute("account", account.getAccountName());
+ session.setAttribute("domainid", account.getDomainId());
+ session.setAttribute("type", Short.valueOf(account.getType()).toString());
+ session.setAttribute("registrationtoken", userAcct.getRegistrationToken());
+ session.setAttribute("registered", new Boolean(userAcct.isRegistered()).toString());
+
+ if (timezone != null) {
+ session.setAttribute("timezone", timezone);
+ session.setAttribute("timezoneoffset", Float.valueOf(offsetInHrs).toString());
+ }
+
+ // (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that
+ // to the login response so that session-based authenticators know to send the key back
+ SecureRandom sesssionKeyRandom = new SecureRandom();
+ byte sessionKeyBytes[] = new byte[20];
+ sesssionKeyRandom.nextBytes(sessionKeyBytes);
+ String sessionKey = Base64.encodeBytes(sessionKeyBytes);
+ session.setAttribute("sessionkey", sessionKey);
+
+ return;
+ }
+ throw new CloudAuthenticationException("Unable to find user " + username + " in domain " + domainId);
+ }
+
+ public void logoutUser(long userId) {
+ _ms.logoutUser(Long.valueOf(userId));
+ return;
+ }
+
+ public boolean verifyUser(Long userId) {
+ User user = _ms.findUserById(userId);
+ Account account = null;
+ if (user != null) {
+ account = _ms.findAccountById(user.getAccountId());
+ }
+
+ if ((user == null) || (user.getRemoved() != null) || !user.getState().equals(Account.State.enabled) || (account == null) || !account.getState().equals(Account.State.enabled)) {
+ s_logger.warn("Deleted/Disabled/Locked user with id=" + userId + " attempting to access public API");
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isCommandAvailable(short accountType, String commandName) {
+ boolean isCommandAvailable = false;
+ switch (accountType) {
+ case Account.ACCOUNT_TYPE_ADMIN:
+ isCommandAvailable = s_adminCommands.contains(commandName);
+ break;
+ case Account.ACCOUNT_TYPE_DOMAIN_ADMIN:
+ isCommandAvailable = s_resellerCommands.contains(commandName);
+ break;
+ case Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN:
+ isCommandAvailable = s_resourceDomainAdminCommands.contains(commandName);
+ break;
+ case Account.ACCOUNT_TYPE_NORMAL:
+ isCommandAvailable = s_userCommands.contains(commandName);
+ break;
+ }
+ return isCommandAvailable;
+ }
+
+ // FIXME: rather than isError, we might was to pass in the status code to give more flexibility
+ private void writeResponse(HttpResponse resp, final String responseText, final int statusCode, String responseType, String reasonPhrase) {
+ try {
+ resp.setStatusCode(statusCode);
+ resp.setReasonPhrase(reasonPhrase);
+
+ BasicHttpEntity body = new BasicHttpEntity();
+ if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
+ // JSON response
+ body.setContentType("text/javascript");
+ if (responseText == null) {
+ body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes("UTF-8")));
+ }
+ } else {
+ body.setContentType("text/xml");
+ if (responseText == null) {
+ body.setContent(new ByteArrayInputStream("Internal Server Error".getBytes("UTF-8")));
+ }
+ }
+
+ if (responseText != null) {
+ body.setContent(new ByteArrayInputStream(responseText.getBytes("UTF-8")));
+ }
+ resp.setEntity(body);
+ } catch (Exception ex) {
+ s_logger.error("error!", ex);
+ }
+ }
+
+ // FIXME: the following two threads are copied from
+ // http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpServer.java
+ // we have to cite a license if we are using this code directly, so we need to add the appropriate citation or modify the
+ // code to be very specific to our needs
+ static class ListenerThread extends Thread {
+ private HttpService _httpService = null;
+ private ServerSocket _serverSocket = null;
+ private HttpParams _params = null;
+
+ public ListenerThread(ApiServer requestHandler, int port) {
+ try {
+ _serverSocket = new ServerSocket(port);
+ } catch (IOException ioex) {
+ s_logger.error("error initializing api server", ioex);
+ return;
+ }
+
+ _params = new BasicHttpParams();
+ _params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 30000).setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
+ .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false).setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
+ .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");
+
+ // Set up the HTTP protocol processor
+ BasicHttpProcessor httpproc = new BasicHttpProcessor();
+ httpproc.addInterceptor(new ResponseDate());
+ httpproc.addInterceptor(new ResponseServer());
+ httpproc.addInterceptor(new ResponseContent());
+ httpproc.addInterceptor(new ResponseConnControl());
+
+ // Set up request handlers
+ HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
+ reqistry.register("*", requestHandler);
+
+ // Set up the HTTP service
+ _httpService = new HttpService(httpproc, new NoConnectionReuseStrategy(), new DefaultHttpResponseFactory());
+ _httpService.setParams(_params);
+ _httpService.setHandlerResolver(reqistry);
+ }
+
+ @Override
+ public void run() {
+ s_logger.info("ApiServer listening on port " + _serverSocket.getLocalPort());
+ while (!Thread.interrupted()) {
+ try {
+ // Set up HTTP connection
+ Socket socket = _serverSocket.accept();
+ DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
+ conn.bind(socket, _params);
+
+ // Execute a new worker task to handle the request
+ _executor.execute(new WorkerTask(_httpService, conn, _workerCount++));
+ } catch (InterruptedIOException ex) {
+ break;
+ } catch (IOException e) {
+ s_logger.error("I/O error initializing connection thread", e);
+ break;
+ }
+ }
+ }
+ }
+
+ static class WorkerTask implements Runnable {
+ private final HttpService _httpService;
+ private final HttpServerConnection _conn;
+
+ public WorkerTask(final HttpService httpService, final HttpServerConnection conn, final int count) {
+ _httpService = httpService;
+ _conn = conn;
+ }
+
+ @Override
+ public void run() {
+ HttpContext context = new BasicHttpContext(null);
+ try {
+ while (!Thread.interrupted() && _conn.isOpen()) {
+ try {
+ _httpService.handleRequest(_conn, context);
+ _conn.close();
+ } finally {
+ StackMaid.current().exitCleanup();
+ }
+ }
+ } catch (ConnectionClosedException ex) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("ApiServer: Client closed connection");
+ }
+ } catch (IOException ex) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("ApiServer: IOException - " + ex);
+ }
+ } catch (HttpException ex) {
+ s_logger.warn("ApiServer: Unrecoverable HTTP protocol violation" + ex);
+ } finally {
+ try {
+ _conn.shutdown();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ public String getSerializedApiError(int errorCode, String errorText, Map apiCommandParams, String responseType) {
+ String responseName = null;
+ String cmdClassName = null;
+
+ String responseText = null;
+
+ try {
+ if (errorCode == BaseCmd.UNSUPPORTED_ACTION_ERROR || apiCommandParams == null || apiCommandParams.isEmpty()) {
+ responseName = "errorresponse";
+ } else {
+ String cmdName = ((String[]) apiCommandParams.get("command"))[0];
+ cmdClassName = _apiCommands.getProperty(cmdName);
+ if (cmdClassName != null) {
+ Class> claz = Class.forName(cmdClassName);
+ responseName = ((BaseCmd) claz.newInstance()).getCommandName();
+ } else {
+ responseName = "errorresponse";
+ }
+ }
+
+ ExceptionResponse apiResponse = new ExceptionResponse();
+ apiResponse.setErrorCode(errorCode);
+ apiResponse.setErrorText(errorText);
+ apiResponse.setResponseName(responseName);
+ responseText = ApiResponseSerializer.toSerializedString(apiResponse, responseType);
+
+ } catch (Exception e) {
+ s_logger.error("Exception responding to http request", e);
+ }
+ return responseText;
+ }
+}
diff --git a/server/src/com/cloud/servlet/RegisterCompleteServlet.java b/server/src/com/cloud/servlet/RegisterCompleteServlet.java
new file mode 100644
index 00000000000..807951448c9
--- /dev/null
+++ b/server/src/com/cloud/servlet/RegisterCompleteServlet.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
+ *
+ * This software is licensed under the GNU General Public License v3 or later.
+ *
+ * It is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package com.cloud.servlet;
+
+import java.util.List;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.configuration.Configuration;
+import com.cloud.configuration.dao.ConfigurationDao;
+import com.cloud.server.ManagementServer;
+import com.cloud.user.Account;
+import com.cloud.user.AccountService;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.SerialVersionUID;
+import com.cloud.utils.component.ComponentLocator;
+
+public class RegisterCompleteServlet extends HttpServlet implements ServletContextListener {
+ public static final Logger s_logger = Logger.getLogger(RegisterCompleteServlet.class.getName());
+
+ static final long serialVersionUID = SerialVersionUID.CloudStartupServlet;
+
+ protected static AccountService _accountSvc = null;
+ protected static ConfigurationDao _configDao = null;
+ protected static UserDao _userDao = null;
+
+ @Override
+ public void init() throws ServletException {
+ ComponentLocator locator = ComponentLocator.getLocator(ManagementServer.Name);
+ _accountSvc = locator.getManager(AccountService.class);
+ _configDao = locator.getDao(ConfigurationDao.class);
+ _userDao = locator.getDao(UserDao.class);
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ try {
+ init();
+ } catch (ServletException e) {
+ s_logger.error("Exception starting management server ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
+ doGet(req, resp);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
+ String registrationToken = req.getParameter("token");
+ if (registrationToken == null || registrationToken.trim().length() == 0) {
+ // Return an error code
+ }
+
+ User resourceAdminUser = _accountSvc.getActiveUserByRegistrationToken(registrationToken);
+ if (resourceAdminUser == null) {
+ // Return an error code
+ }
+
+ if(!resourceAdminUser.isRegistered()){
+ _accountSvc.markUserRegistered(resourceAdminUser.getId());
+ }
+
+ Account resourceAdminAccount = _accountSvc.getActiveAccount(resourceAdminUser.getAccountId());
+ Account rsUserAccount = _accountSvc.getActiveAccount(resourceAdminAccount.getAccountName()+"-user", resourceAdminAccount.getDomainId());
+
+ List users = _userDao.listByAccount(rsUserAccount.getId());
+ User rsUser = users.get(0);
+
+ Configuration config = _configDao.findByName("endpointe.url");
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ \"registration_info\" : { \"endpoint_url\" : \""+config.getValue()+"\", ");
+ sb.append("\"domain_id\" : \""+resourceAdminAccount.getDomainId()+"\", ");
+ sb.append("\"admin_account\" : \""+resourceAdminUser.getUsername()+"\", ");
+ sb.append("\"admin_account_api_key\" : \""+resourceAdminUser.getApiKey()+"\", ");
+ sb.append("\"admin_account_secret_key\" : \""+resourceAdminUser.getSecretKey()+"\", ");
+ sb.append("\"user_account\" : \""+rsUser.getUsername()+"\", ");
+ sb.append("\"user_account_api_key\" : \""+rsUser.getApiKey()+"\", ");
+ sb.append("\"user_account_secret_key\" : \""+rsUser.getSecretKey()+"\" ");
+ sb.append("} }");
+
+ try {
+ resp.setContentType("text/javascript; charset=UTF-8");
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.getWriter().print(sb.toString());
+ } catch (Exception ex) {
+ s_logger.error("unknown exception writing register complete response", ex);
+ }
+ }
+}
diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java
index a0154d08648..eefb5896019 100755
--- a/server/src/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/com/cloud/user/AccountManagerImpl.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -1144,6 +1145,13 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
user.setAccountId(accountId.longValue());
user.setEmail(email);
user.setTimezone(timezone);
+
+ if(userType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN){
+ //set registration token
+ byte[] bytes = (domainId + accountName + username + System.currentTimeMillis()).getBytes();
+ String registrationToken = UUID.nameUUIDFromBytes(bytes).toString();
+ user.setRegistrationToken(registrationToken);
+ }
if (s_logger.isDebugEnabled()) {
s_logger.debug("Creating user: " + username + ", account: " + accountName + " (id:" + accountId + "), domain: " + domainId + " timezone:" + timezone);
}
@@ -1767,4 +1775,16 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
return new Pair(accountName, domainId);
}
+
+ @Override
+ public User getActiveUserByRegistrationToken(String registrationToken) {
+ return _userDao.findUserByRegistrationToken(registrationToken);
+ }
+
+ @Override
+ public void markUserRegistered(long userId) {
+ UserVO userForUpdate = _userDao.createForUpdate();
+ userForUpdate.setRegistered(true);
+ _userDao.update(Long.valueOf(userId), userForUpdate);
+ }
}
diff --git a/server/src/com/cloud/user/dao/UserDao.java b/server/src/com/cloud/user/dao/UserDao.java
index 436a830b353..4ee3a12a85b 100644
--- a/server/src/com/cloud/user/dao/UserDao.java
+++ b/server/src/com/cloud/user/dao/UserDao.java
@@ -55,5 +55,13 @@ public interface UserDao extends GenericDao{
* @param secretKey
* @return
*/
- UserVO findUserBySecretKey(String secretKey);
+ UserVO findUserBySecretKey(String secretKey);
+
+ /**
+ * Finds a user based on the registration token provided.
+ * @param registrationToken
+ * @return
+ */
+ UserVO findUserByRegistrationToken(String registrationToken);
+
}
diff --git a/server/src/com/cloud/user/dao/UserDaoImpl.java b/server/src/com/cloud/user/dao/UserDaoImpl.java
index 04b16128c0d..9fba2b19d9a 100644
--- a/server/src/com/cloud/user/dao/UserDaoImpl.java
+++ b/server/src/com/cloud/user/dao/UserDaoImpl.java
@@ -41,7 +41,8 @@ public class UserDaoImpl extends GenericDaoBase implements UserDao
protected SearchBuilder UsernameLikeSearch;
protected SearchBuilder UserIdSearch;
protected SearchBuilder AccountIdSearch;
- protected SearchBuilder SecretKeySearch;
+ protected SearchBuilder SecretKeySearch;
+ protected SearchBuilder RegistrationTokenSearch;
protected UserDaoImpl () {
UsernameSearch = createSearchBuilder();
@@ -67,7 +68,11 @@ public class UserDaoImpl extends GenericDaoBase implements UserDao
SecretKeySearch = createSearchBuilder();
SecretKeySearch.and("secretKey", SecretKeySearch.entity().getSecretKey(), SearchCriteria.Op.EQ);
- SecretKeySearch.done();
+ SecretKeySearch.done();
+
+ RegistrationTokenSearch = createSearchBuilder();
+ RegistrationTokenSearch.and("registrationToken", RegistrationTokenSearch.entity().getRegistrationToken(), SearchCriteria.Op.EQ);
+ RegistrationTokenSearch.done();
}
@Override
@@ -134,5 +139,12 @@ public class UserDaoImpl extends GenericDaoBase implements UserDao
{
throw new CloudRuntimeException("unable to update user -- a user with that name exists");
}
- }
+ }
+
+ @Override
+ public UserVO findUserByRegistrationToken(String registrationToken) {
+ SearchCriteria sc = RegistrationTokenSearch.create();
+ sc.setParameters("registrationToken", registrationToken);
+ return findOneBy(sc);
+ }
}
diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql
index 42a3ea9190a..f7d52db51b2 100755
--- a/setup/db/create-schema.sql
+++ b/setup/db/create-schema.sql
@@ -698,6 +698,8 @@ CREATE TABLE `cloud`.`user` (
`created` datetime NOT NULL COMMENT 'date created',
`removed` datetime COMMENT 'date removed',
`timezone` varchar(30) default NULL,
+ `registration_token` varchar(255) default NULL,
+ `is_registered` tinyint NOT NULL DEFAULT 0 COMMENT '1: yes, 0: no',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
diff --git a/ui/cloudkit/scripts/cloudkit.js b/ui/cloudkit/scripts/cloudkit.js
new file mode 100644
index 00000000000..7d1967a477d
--- /dev/null
+++ b/ui/cloudkit/scripts/cloudkit.js
@@ -0,0 +1,52 @@
+ /**
+ * Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
+ *
+ * This software is licensed under the GNU General Public License v3 or later.
+ *
+ * It is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+var g_loginResponse = null;
+$.urlParam = function(name){ var results = new RegExp('[\\?&]' + name + '=([^]*)').exec(window.location.href); if (!results) { return 0; } return results[1] || 0;}
+
+function logout() {
+ window.location='/client/cloudkit/login.jsp';
+ g_loginResponse = null;
+ return true;
+}
+
+$(document).ready(function() {
+
+ var url = $.urlParam("loginUrl");
+ if (url != undefined && url != null && url.length > 0) {
+ url = unescape("/client/api?"+url);
+ $.ajax({
+ url: url,
+ dataType: "json",
+ async: false,
+ success: function(json) {
+ g_loginResponse = json.loginresponse;
+ $("#registration_complete_link").attr("href","https://my.rightscale.com/cloud_registrations/cloudkit/new?callback_url="+encodeURIComponent("http://localhost:8080/client/cloudkit/complete?token="+g_loginResponse.registrationtoken));
+ },
+ error: function() {
+ logout();
+ },
+ beforeSend: function(XMLHttpRequest) {
+ return true;
+ }
+ });
+ } else {
+ logout();
+ }
+});
+
+