diff --git a/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/FilterScriptManagerServlet.java b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/FilterScriptManagerServlet.java index a894d2867f..5f275ebd3d 100644 --- a/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/FilterScriptManagerServlet.java +++ b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/FilterScriptManagerServlet.java @@ -27,16 +27,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; -import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; @@ -83,11 +79,6 @@ public class FilterScriptManagerServlet extends HttpServlet { private static final long serialVersionUID = -1L; private static final Logger logger = LoggerFactory.getLogger(FilterScriptManagerServlet.class); - /* actions that we permit as an immutable Set */ - private static final Set VALID_GET_ACTIONS = Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[]{"LIST", "DOWNLOAD"}))); - private static final Set VALID_PUT_ACTIONS = Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[]{"UPLOAD", "ACTIVATE", "DEACTIVATE", "RUN", "CANARY"}))); - - /* DAO for performing CRUD operations with scripts */ private static ZuulFilterDAO scriptDAO; @@ -458,31 +449,7 @@ private Map createEndpointScriptJSON(FilterInfo script) { private static boolean isValidAction(HttpServletRequest request, HttpServletResponse response) { String action = request.getParameter("action"); if (action != null) { - action = action.trim().toUpperCase(); - /* test for GET actions */ - if (VALID_GET_ACTIONS.contains(action)) { - if (!request.getMethod().equals("GET")) { - // valid action, wrong method - setUsageError(405, "ERROR: Invalid HTTP method for action type.", response); - return false; - } - // valid action and method - return true; - } - - if (VALID_PUT_ACTIONS.contains(action)) { - if (!(request.getMethod().equals("PUT") || request.getMethod().equals("POST"))) { - // valid action, wrong method - setUsageError(405, "ERROR: Invalid HTTP method for action type.", response); - return false; - } - // valid action and method - return true; - } - - // wrong action - setUsageError(400, "ERROR: Unknown action type.", response); - return false; + return new ValidActionEvaluation(request, response).isValid(); } else { setUsageError(400, "ERROR: Invalid arguments.", response); return false; @@ -496,17 +463,7 @@ private static boolean isValidAction(HttpServletRequest request, HttpServletResp * @param response */ private static void setUsageError(int statusCode, String message, HttpServletResponse response) { - response.setStatus(statusCode); - try { - Writer w = response.getWriter(); - if (message != null) { - w.write(message + "\n\n"); - } - w.write(getUsageDoc()); - } catch (Exception e) { - logger.error("Failed to output usage error.", e); - // won't throw exception because this is not critical, logging the error is enough - } + new UsageError(statusCode, message).setOn(response); } /** @@ -525,42 +482,7 @@ private static void setUsageError(int statusCode, HttpServletResponse response) * @return */ private static String getUsageDoc() { - StringBuilder s = new StringBuilder(); - s.append("Usage: /scriptManager?action=&").append("\n"); - s.append(" Actions:").append("\n"); - s.append(" LIST: List all endpoints with scripts or all scripts for a given endpoint.").append("\n"); - s.append(" Arguments:").append("\n"); - s.append(" endpoint: [Optional (Default: All endpoints)] The endpoint of script revisions to list.").append("\n"); - s.append(" Examples:").append("\n"); - s.append(" GET /scriptManager?action=LIST").append("\n"); - s.append(" GET /scriptManager?action=LIST&endpoint=/ps3/home").append("\n"); - s.append("\n"); - - s.append(" DOWNLOAD: Download a given script.").append("\n"); - s.append(" Arguments:").append("\n"); - s.append(" endpoint: [Required] The endpoint of script to download.").append("\n"); - s.append(" revision: [Optional (Default: last revision)] The revision to download.").append("\n"); - s.append(" Examples:").append("\n"); - s.append(" GET /scriptManager?action=DOWNLOAD&endpoint=/ps3/home").append("\n"); - s.append(" GET /scriptManager?action=DOWNLOAD&endpoint=/ps3/home&revision=23").append("\n"); - s.append("\n"); - - s.append(" UPLOAD: Upload a script for a given endpoint.").append("\n"); - s.append(" Arguments:").append("\n"); - s.append(" endpoint: [Required] The endpoint to associated the script with. If it doesn't exist it will be created.").append("\n"); - s.append(" userAuthenticationRequired: [Optional (Default: true)] Whether the script requires an authenticated user to execute.").append("\n"); - s.append(" Example:").append("\n"); - s.append(" POST /scriptManager?action=UPLOAD&endpoint=/ps3/home").append("\n"); - s.append(" POST /scriptManager?action=UPLOAD&endpoint=/ps3/home&userAuthenticationRequired=false").append("\n"); - s.append("\n"); - - s.append(" ACTIVATE: Mark a particular script revision as active for production.").append("\n"); - s.append(" Arguments:").append("\n"); - s.append(" endpoint: [Required] The endpoint for which a script revision should be activated.").append("\n"); - s.append(" revision: [Required] The script revision to activate.").append("\n"); - s.append(" Example:").append("\n"); - s.append(" PUT /scriptManager?action=ACTIVATE&endpoint=/ps3/home&revision=22").append("\n"); - return s.toString(); + return new UsageDoc().get(); } public static class UnitTest { @@ -977,4 +899,5 @@ private FilterInfo mockEndpointScript() { return script; } } + } diff --git a/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageDoc.java b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageDoc.java new file mode 100644 index 0000000000..eb6e996d43 --- /dev/null +++ b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageDoc.java @@ -0,0 +1,44 @@ +package com.netflix.zuul.scriptManager; + +final class UsageDoc { + + String get() { + StringBuilder s = new StringBuilder(); + s.append("Usage: /scriptManager?action=&").append("\n"); + s.append(" Actions:").append("\n"); + s.append(" LIST: List all endpoints with scripts or all scripts for a given endpoint.").append("\n"); + s.append(" Arguments:").append("\n"); + s.append(" endpoint: [Optional (Default: All endpoints)] The endpoint of script revisions to list.").append("\n"); + s.append(" Examples:").append("\n"); + s.append(" GET /scriptManager?action=LIST").append("\n"); + s.append(" GET /scriptManager?action=LIST&endpoint=/ps3/home").append("\n"); + s.append("\n"); + + s.append(" DOWNLOAD: Download a given script.").append("\n"); + s.append(" Arguments:").append("\n"); + s.append(" endpoint: [Required] The endpoint of script to download.").append("\n"); + s.append(" revision: [Optional (Default: last revision)] The revision to download.") + .append("\n"); + s.append(" Examples:").append("\n"); + s.append(" GET /scriptManager?action=DOWNLOAD&endpoint=/ps3/home").append("\n"); + s.append(" GET /scriptManager?action=DOWNLOAD&endpoint=/ps3/home&revision=23").append("\n"); + s.append("\n"); + + s.append(" UPLOAD: Upload a script for a given endpoint.").append("\n"); + s.append(" Arguments:").append("\n"); + s.append(" endpoint: [Required] The endpoint to associated the script with. If it doesn't exist it will be created.").append("\n"); + s.append(" userAuthenticationRequired: [Optional (Default: true)] Whether the script requires an authenticated user to execute.").append("\n"); + s.append(" Example:").append("\n"); + s.append(" POST /scriptManager?action=UPLOAD&endpoint=/ps3/home").append("\n"); + s.append(" POST /scriptManager?action=UPLOAD&endpoint=/ps3/home&userAuthenticationRequired=false").append("\n"); + s.append("\n"); + + s.append(" ACTIVATE: Mark a particular script revision as active for production.").append("\n"); + s.append(" Arguments:").append("\n"); + s.append(" endpoint: [Required] The endpoint for which a script revision should be activated.").append("\n"); + s.append(" revision: [Required] The script revision to activate.").append("\n"); + s.append(" Example:").append("\n"); + s.append(" PUT /scriptManager?action=ACTIVATE&endpoint=/ps3/home&revision=22").append("\n"); + return s.toString(); + } +} diff --git a/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageError.java b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageError.java new file mode 100644 index 0000000000..5cd5c05c6d --- /dev/null +++ b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/UsageError.java @@ -0,0 +1,35 @@ +package com.netflix.zuul.scriptManager; + +import java.io.Writer; + +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class UsageError { + + private static final Logger LOGGER = LoggerFactory.getLogger(UsageError.class); + + private final int statusCode; + private final String message; + + UsageError(int statusCode, String message) { + this.statusCode = statusCode; + this.message = message; + } + + void setOn(HttpServletResponse response) { + response.setStatus(statusCode); + try { + Writer w = response.getWriter(); + if (message != null) { + w.write(message + "\n\n"); + } + w.write(new UsageDoc().get()); + } catch (Exception e) { + LOGGER.error("Failed to output usage error.", e); + // won't throw exception because this is not critical, logging the error is enough + } + } +} diff --git a/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/ValidActionEvaluation.java b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/ValidActionEvaluation.java new file mode 100644 index 0000000000..5bee8c1dbc --- /dev/null +++ b/zuul-netflix/src/main/java/com/netflix/zuul/scriptManager/ValidActionEvaluation.java @@ -0,0 +1,61 @@ +package com.netflix.zuul.scriptManager; + +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Collections.unmodifiableSet; + +class ValidActionEvaluation { + + private static final Set VALID_GET_ACTIONS = unmodifiableSet(newHashSet("LIST", "DOWNLOAD")); + private static final Set VALID_PUT_ACTIONS = + unmodifiableSet(newHashSet("UPLOAD", "ACTIVATE", "DEACTIVATE", "RUN", "CANARY")); + + private final boolean result; + + ValidActionEvaluation(HttpServletRequest request, HttpServletResponse response) { + this.result = resultOfEvaluation(request, response); + } + + private boolean resultOfEvaluation(HttpServletRequest request, HttpServletResponse response) { + String action = request.getParameter("action").trim().toUpperCase(); + if (VALID_GET_ACTIONS.contains(action)) { + return validateGetAction(request, response); + } + + if (VALID_PUT_ACTIONS.contains(action)) { + return validatePutAction(request, response); + } + + // wrong action + new UsageError(400, "ERROR: Unknown action type.").setOn(response); + return false; + } + + private boolean validatePutAction(HttpServletRequest request, HttpServletResponse response) { + if ((request.getMethod().equals("PUT") || request.getMethod().equals("POST"))) { + return true; + } + addValidActionWrongMethodError(response); + return false; + } + + private void addValidActionWrongMethodError(HttpServletResponse response) { + new UsageError(405, "ERROR: Invalid HTTP method for action type.").setOn(response); + } + + private boolean validateGetAction(HttpServletRequest request, HttpServletResponse response) { + if (request.getMethod().equals("GET")) { + return true; + } + addValidActionWrongMethodError(response); + return false; + } + + public boolean isValid() { + return result; + } +}