@SuppressWarnings("unchecked") private void processRequest(HttpServletRequest req, HttpServletResponse resp) { StringBuffer auditTrailSb = new StringBuffer(); auditTrailSb.append(" " + req.getRemoteAddr()); auditTrailSb.append(" -- " + req.getMethod() + " "); // get the response format since we'll need it in a couple of places String responseType = BaseCmd.RESPONSE_TYPE_XML; Map<String, Object[]> params = new HashMap<String, Object[]>(); params.putAll(req.getParameterMap()); // // For HTTP GET requests, it seems that HttpServletRequest.getParameterMap() actually tries // to unwrap URL encoded content from ISO-9959-1. // // After failed in using setCharacterEncoding() to control it, end up with following hacking : // for all GET requests, // we will override it with our-own way of UTF-8 based URL decoding. // utf8Fixup(req, params); try { HttpSession session = req.getSession(false); Object[] responseTypeParam = params.get("response"); if (responseTypeParam != null) { responseType = (String) responseTypeParam[0]; } Object[] commandObj = params.get("command"); if (commandObj != null) { String command = (String) commandObj[0]; if ("logout".equalsIgnoreCase(command)) { // if this is just a logout, invalidate the session and return if (session != null) { Long userId = (Long) session.getAttribute("userid"); Account account = (Account) session.getAttribute("accountobj"); Long accountId = null; if (account != null) { accountId = account.getId(); } auditTrailSb.insert( 0, "(userId=" + userId + " accountId=" + accountId + " sessionId=" + session.getId() + ")"); if (userId != null) { _apiServer.logoutUser(userId); } try { session.invalidate(); } catch (IllegalStateException ise) { } } auditTrailSb.append("command=logout"); auditTrailSb.append(" " + HttpServletResponse.SC_OK); writeResponse( resp, getLogoutSuccessResponse(responseType), HttpServletResponse.SC_OK, responseType); return; } else if ("login".equalsIgnoreCase(command)) { auditTrailSb.append("command=login"); // if this is a login, authenticate the user and return if (session != null) { try { session.invalidate(); } catch (IllegalStateException ise) { } } session = req.getSession(true); String[] username = (String[]) params.get("username"); String[] password = (String[]) params.get("password"); String[] domainIdArr = (String[]) params.get("domainid"); if (domainIdArr == null) { domainIdArr = (String[]) params.get("domainId"); } String[] domainName = (String[]) params.get("domain"); Long domainId = null; if ((domainIdArr != null) && (domainIdArr.length > 0)) { try { domainId = new Long(Long.parseLong(domainIdArr[0])); auditTrailSb.append(" domainid=" + domainId); // building the params for POST call } catch (NumberFormatException e) { s_logger.warn("Invalid domain id entered by user"); auditTrailSb.append( " " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one"); String serializedResponse = _apiServer.getSerializedApiError( HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params, responseType, null); writeResponse( resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType); } } String domain = null; if (domainName != null) { domain = domainName[0]; auditTrailSb.append(" domain=" + domain); if (domain != null) { // ensure domain starts with '/' and ends with '/' if (!domain.endsWith("/")) { domain += '/'; } if (!domain.startsWith("/")) { domain = "/" + domain; } } } if (username != null) { String pwd = ((password == null) ? null : password[0]); try { _apiServer.loginUser(session, username[0], pwd, domainId, domain, params); auditTrailSb.insert( 0, "(userId=" + session.getAttribute("userid") + " accountId=" + ((Account) session.getAttribute("accountobj")).getId() + " sessionId=" + session.getId() + ")"); String loginResponse = getLoginSuccessResponse(session, responseType); writeResponse(resp, loginResponse, HttpServletResponse.SC_OK, responseType); return; } catch (CloudAuthenticationException ex) { // TODO: fall through to API key, or just fail here w/ auth error? (HTTP 401) try { session.invalidate(); } catch (IllegalStateException ise) { } auditTrailSb.append( " " + BaseCmd.ACCOUNT_ERROR + " " + ex.getMessage() != null ? ex.getMessage() : "failed to authenticate user, check if username/password are correct"); String serializedResponse = _apiServer.getSerializedApiError( BaseCmd.ACCOUNT_ERROR, ex.getMessage() != null ? ex.getMessage() : "failed to authenticate user, check if username/password are correct", params, responseType, null); writeResponse(resp, serializedResponse, BaseCmd.ACCOUNT_ERROR, responseType); return; } } } } auditTrailSb.append(req.getQueryString()); boolean isNew = ((session == null) ? true : session.isNew()); // Initialize an empty context and we will update it after we have verified the request below, // we no longer rely on web-session here, verifyRequest will populate user/account information // if a API key exists UserContext.registerContext( _accountMgr.getSystemUser().getId(), _accountMgr.getSystemAccount(), null, false); Long userId = null; if (!isNew) { userId = (Long) session.getAttribute("userid"); String account = (String) session.getAttribute("account"); Object accountObj = session.getAttribute("accountobj"); String sessionKey = (String) session.getAttribute("sessionkey"); String[] sessionKeyParam = (String[]) params.get("sessionkey"); if ((sessionKeyParam == null) || (sessionKey == null) || !sessionKey.equals(sessionKeyParam[0])) { try { session.invalidate(); } catch (IllegalStateException ise) { } auditTrailSb.append( " " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials"); String serializedResponse = _apiServer.getSerializedApiError( HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType, null); writeResponse( resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType); return; } // Do a sanity check here to make sure the user hasn't already been deleted if ((userId != null) && (account != null) && (accountObj != null) && _apiServer.verifyUser(userId)) { String[] command = (String[]) params.get("command"); if (command == null) { s_logger.info("missing command, ignoring request..."); auditTrailSb.append( " " + HttpServletResponse.SC_BAD_REQUEST + " " + "no command specified"); String serializedResponse = _apiServer.getSerializedApiError( HttpServletResponse.SC_BAD_REQUEST, "no command specified", params, responseType, null); writeResponse( resp, serializedResponse, HttpServletResponse.SC_BAD_REQUEST, responseType); return; } UserContext.updateContext(userId, (Account) accountObj, session.getId()); } else { // Invalidate the session to ensure we won't allow a request across management server // restarts if the userId // was serialized to the // stored session try { session.invalidate(); } catch (IllegalStateException ise) { } auditTrailSb.append( " " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials"); String serializedResponse = _apiServer.getSerializedApiError( HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType, null); writeResponse( resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType); return; } } if (_apiServer.verifyRequest(params, userId)) { /* * if (accountObj != null) { Account userAccount = (Account)accountObj; if (userAccount.getType() == * Account.ACCOUNT_TYPE_NORMAL) { params.put(BaseCmd.Properties.USER_ID.getName(), new String[] { userId }); * params.put(BaseCmd.Properties.ACCOUNT.getName(), new String[] { account }); * params.put(BaseCmd.Properties.DOMAIN_ID.getName(), new String[] { domainId }); * params.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { accountObj }); } else { * params.put(BaseCmd.Properties.USER_ID.getName(), new String[] { userId }); * params.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { accountObj }); } } * * // update user context info here so that we can take information if the request is authenticated // via api * key mechanism updateUserContext(params, session != null ? session.getId() : null); */ auditTrailSb.insert( 0, "(userId=" + UserContext.current().getCallerUserId() + " accountId=" + UserContext.current().getCaller().getId() + " sessionId=" + (session != null ? session.getId() : null) + ")"); try { String response = _apiServer.handleRequest(params, false, responseType, auditTrailSb); writeResponse( resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType); } catch (ServerApiException se) { String serializedResponseText = _apiServer.getSerializedApiError( se.getErrorCode(), se.getDescription(), params, responseType, null); resp.setHeader("X-Description", se.getDescription()); writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType); auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription()); } } else { if (session != null) { try { session.invalidate(); } catch (IllegalStateException ise) { } } auditTrailSb.append( " " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials and/or request signature"); String serializedResponse = _apiServer.getSerializedApiError( HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials and/or request signature", params, responseType, null); writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType); } } catch (Exception ex) { if (ex instanceof ServerApiException && ((ServerApiException) ex).getErrorCode() == BaseCmd.UNSUPPORTED_ACTION_ERROR) { ServerApiException se = (ServerApiException) ex; String serializedResponseText = _apiServer.getSerializedApiError( se.getErrorCode(), se.getDescription(), params, responseType, null); resp.setHeader("X-Description", se.getDescription()); writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType); auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription()); } else { s_logger.error("unknown exception writing api response", ex); auditTrailSb.append(" unknown exception writing api response"); } } finally { s_accessLogger.info(auditTrailSb.toString()); // cleanup user context to prevent from being peeked in other request context UserContext.unregisterContext(); } }
public boolean verifyRequest(Map<String, Object[]> 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) { User user = ApiDBUtils.findUserById(userId); try { checkCommandAvailable(user, commandName); } catch (PermissionDeniedException ex) { s_logger.debug( "The given command:" + commandName + " does not exist or it is not available for user with id:" + userId); throw new ServerApiException( ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "The given command does not exist or it is not available for user"); } return true; } else { // check against every available command to see if the command exists or not if (!_apiNameCmdClassMap.containsKey(commandName) && !commandName.equals("login") && !commandName.equals("logout")) { s_logger.debug( "The given command:" + commandName + " does not exist or it is not available for user with id:" + userId); throw new ServerApiException( ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "The given command does not exist or it is not available for user"); } } // - build a request string with sorted params, make sure it's all lowercase // - sign the request, verify the signature is the same List<String> parameterNames = new ArrayList<String>(); for (Object paramNameObj : requestParameters.keySet()) { parameterNames.add((String) paramNameObj); // put the name in a list that we'll sort later } Collections.sort(parameterNames); String signatureVersion = null; String expires = null; 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; } else if ("signatureversion".equalsIgnoreCase(paramName)) { signatureVersion = paramValue; } else if ("expires".equalsIgnoreCase(paramName)) { expires = 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)) { s_logger.debug( "Expired session, missing signature, or missing apiKey -- ignoring request. Signature: " + signature + ", apiKey: " + apiKey); return false; // no signature, bad request } Date expiresTS = null; // FIXME: Hard coded signature, why not have an enum if ("3".equals(signatureVersion)) { // New signature authentication. Check for expire parameter and its validity if (expires == null) { s_logger.debug( "Missing Expires parameter -- ignoring request. Signature: " + signature + ", apiKey: " + apiKey); return false; } synchronized (_dateFormat) { try { expiresTS = _dateFormat.parse(expires); } catch (ParseException pe) { s_logger.debug("Incorrect date format for Expires parameter", pe); return false; } } Date now = new Date(System.currentTimeMillis()); if (expiresTS.before(now)) { s_logger.debug( "Request expired -- ignoring ...sig: " + signature + ", apiKey: " + apiKey); return false; } } Transaction txn = Transaction.open(Transaction.CLOUD_DB); txn.close(); User user = null; // verify there is a user with this api key Pair<User, Account> userAcctPair = _accountMgr.findUserByApiKey(apiKey); if (userAcctPair == null) { s_logger.debug( "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); try { checkCommandAvailable(user, commandName); } catch (PermissionDeniedException ex) { s_logger.debug( "The given command:" + commandName + " does not exist or it is not available for user"); throw new ServerApiException( ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist or it is not available for user with id:" + userId); } // 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: "******"HmacSHA1"); SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1"); mac.init(keySpec); mac.update(unsignedRequest.getBytes()); byte[] encryptedBytes = mac.doFinal(); String computedSignature = Base64.encodeBase64String(encryptedBytes); boolean equalSig = signature.equals(computedSignature); if (!equalSig) { s_logger.info( "User signature: " + signature + " is not equaled to computed signature: " + computedSignature); } return equalSig; } catch (ServerApiException ex) { throw ex; } catch (Exception ex) { s_logger.error("unable to verify request signature"); } return false; }