diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java
index 795707991b8..9806e58c868 100755
--- a/server/src/com/cloud/api/ApiServer.java
+++ b/server/src/com/cloud/api/ApiServer.java
@@ -14,896 +14,885 @@
* 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.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();
- }
- }
+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 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());
+ 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);
- 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;
- }
-}
+ 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;
+ }
+}