You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by js...@apache.org on 2017/10/04 21:13:37 UTC

[geode] branch develop updated: GEODE-3685: MBean wrappers are properly invoked over http

This is an automated email from the ASF dual-hosted git repository.

jstewart pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new db4a493  GEODE-3685: MBean wrappers are properly invoked over http
db4a493 is described below

commit db4a493efc09600bf0a9778d5274c09b23b16644
Author: Jared Stewart <js...@pivotal.io>
AuthorDate: Thu Sep 28 14:44:51 2017 -0700

    GEODE-3685: MBean wrappers are properly invoked over http
    
    This closes #838.
---
 .../cli/commands/StopServerAcceptanceTest.java     |  66 ++++
 .../StopServerWithSecurityAcceptanceTest.java      | 139 ++++++++
 .../geode/examples/SimpleSecurityManager.java      |  59 ++++
 .../geode/management/internal/ManagementAgent.java |  36 +-
 .../controllers/AbstractCommandsController.java    | 378 +++------------------
 .../AbstractMultiPartCommandsController.java       |  77 -----
 .../web/controllers/ShellCommandsController.java   | 154 ++++-----
 .../web/http/support/SimpleHttpRequester.java      |  53 +--
 .../internal/web/shell/HttpOperationInvoker.java   | 122 ++-----
 .../web/shell/RestOperationErrorHandler.java       |  58 ++++
 .../geode/test/junit/rules/gfsh/GfshExecution.java |   8 +
 .../geode/test/junit/rules/gfsh/GfshRule.java      |   3 +-
 .../shell/HttpOperationInvokerSecurityTest.java    |  11 +-
 13 files changed, 486 insertions(+), 678 deletions(-)

diff --git a/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerAcceptanceTest.java b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerAcceptanceTest.java
new file mode 100644
index 0000000..247052a
--- /dev/null
+++ b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerAcceptanceTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.cli.commands;
+
+import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.management.internal.cli.util.CommandStringBuilder;
+import org.apache.geode.test.junit.categories.AcceptanceTest;
+import org.apache.geode.test.junit.rules.gfsh.GfshExecution;
+import org.apache.geode.test.junit.rules.gfsh.GfshRule;
+import org.apache.geode.test.junit.rules.gfsh.GfshScript;
+
+@Category(AcceptanceTest.class)
+public class StopServerAcceptanceTest {
+
+  @Rule
+  public GfshRule gfshRule = new GfshRule();
+
+
+  @Before
+  public void startCluster() {
+    gfshRule.execute("start locator --name=locator", "start server --name=server");
+  }
+
+  @Test
+  public void canStopServerByNameWhenConnectedOverJmx() throws Exception {
+
+    gfshRule.execute("connect", "stop server --name=server");
+  }
+
+  @Test
+  public void canStopServerByNameWhenConnectedOverHttp() throws Exception {
+
+    gfshRule.execute("connect --use-http", "stop server --name=server");
+  }
+
+  @Test
+  public void cannotStopServerByNameWhenNotConnected() throws Exception {
+    startCluster();
+
+    gfshRule.execute(GfshScript.of("stop server --name=server").expectFailure());
+  }
+}
diff --git a/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerWithSecurityAcceptanceTest.java b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerWithSecurityAcceptanceTest.java
new file mode 100644
index 0000000..4588ee6
--- /dev/null
+++ b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StopServerWithSecurityAcceptanceTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.cli.commands;
+
+import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.management.internal.cli.util.CommandStringBuilder;
+import org.apache.geode.test.junit.categories.AcceptanceTest;
+import org.apache.geode.test.junit.rules.gfsh.GfshExecution;
+import org.apache.geode.test.junit.rules.gfsh.GfshRule;
+import org.apache.geode.test.junit.rules.gfsh.GfshScript;
+
+@Category(AcceptanceTest.class)
+public class StopServerWithSecurityAcceptanceTest {
+
+  @Rule
+  public GfshRule gfshRule = new GfshRule();
+
+  private static final Properties securityProps = new Properties();
+
+  static {
+    securityProps.setProperty(SECURITY_MANAGER, SimpleSecurityManager.class.getName());
+    securityProps.setProperty("security-username", "cluster");
+    securityProps.setProperty("security-password", "cluster");
+  }
+
+  private File securityPropertiesFile;
+
+  @Before
+  public void before() throws Exception {
+    securityPropertiesFile = gfshRule.getTemporaryFolder().newFile("security.properties");
+    securityProps.store(new FileOutputStream(securityPropertiesFile), null);
+  }
+
+  @Test
+  public void cannotStopServerAsDataReaderOverHttp() throws Exception {
+    startCluster();
+
+    GfshExecution stopServer = dataReaderCannotStopServer(true);
+    assertThat(stopServer.getStdErrText()).contains("dataReader not authorized for CLUSTER:READ");
+  }
+
+  @Test
+  public void canStopServerAsClusterAdminOverHttp() throws Exception {
+    startCluster();
+
+    clusterAdminCanStopServer(true);
+  }
+
+  @Test
+  public void cannotStopServerAsDataReaderOverJmx() throws Exception {
+    startCluster();
+
+    GfshExecution stopServer = dataReaderCannotStopServer(false);
+    assertThat(stopServer.getStdErrText()).contains("dataReader not authorized for CLUSTER:READ");
+  }
+
+  @Test
+  public void canStopServerAsClusterAdminOverJmx() throws Exception {
+    startCluster();
+
+    clusterAdminCanStopServer(false);
+  }
+
+  @Test
+  public void cannotStopServerAsClusterReaderOverJmx() throws Exception {
+    startCluster();
+
+    GfshExecution stopServer = clusterReaderCannotStopServer(false);
+    assertThat(stopServer.getStdErrText())
+        .contains("clusterRead not authorized for CLUSTER:MANAGE");
+  }
+
+  @Test
+  public void cannotStopServerAsClusterReaderOverHttp() throws Exception {
+    startCluster();
+
+    GfshExecution stopServer = clusterReaderCannotStopServer(true);
+    assertThat(stopServer.getStdErrText())
+        .contains("clusterRead not authorized for CLUSTER:MANAGE");
+  }
+
+  private GfshExecution startCluster() {
+    String startLocator = new CommandStringBuilder("start locator").addOption("name", "locator")
+        .addOption("security-properties-file", securityPropertiesFile.getAbsolutePath()).toString();
+
+    String startServer = new CommandStringBuilder("start server").addOption("name", "server")
+        .addOption("security-properties-file", securityPropertiesFile.getAbsolutePath()).toString();
+
+    return GfshScript.of(startLocator, startServer).withName("cluster-setup").execute(gfshRule);
+  }
+
+  private GfshExecution dataReaderCannotStopServer(boolean useHttp) {
+    return GfshScript.of(connectCommand("dataReader", useHttp), "stop server --name=server")
+        .expectFailure().execute(gfshRule);
+  }
+
+  private GfshExecution clusterAdminCanStopServer(boolean useHttp) {
+    return GfshScript.of(connectCommand("cluster", useHttp), "stop server --name=server")
+        .execute(gfshRule);
+  }
+
+  private GfshExecution clusterReaderCannotStopServer(boolean useHttp) {
+    return GfshScript.of(connectCommand("clusterRead", useHttp), "stop server --name=server")
+        .expectFailure().execute(gfshRule);
+  }
+
+  private String connectCommand(String permission, boolean useHttp) {
+    CommandStringBuilder cmd = new CommandStringBuilder("connect").addOption("user", permission)
+        .addOption("password", permission);
+    if (useHttp) {
+      cmd.addOption("use-http");
+    }
+    return cmd.getCommandString();
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java b/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java
new file mode 100644
index 0000000..7743ddb
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.examples;
+
+import java.util.Properties;
+
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.ResourcePermission;
+import org.apache.geode.security.SecurityManager;
+
+/**
+ * Intended for implementation testing, this class authenticates a user when the username matches
+ * the password, which also represents the permissions the user is granted.
+ */
+public class SimpleSecurityManager implements SecurityManager {
+
+  @Override
+  public void init(final Properties securityProps) {
+    // nothing
+  }
+
+  @Override
+  public Object authenticate(final Properties credentials) throws AuthenticationFailedException {
+    String username = credentials.getProperty("security-username");
+    String password = credentials.getProperty("security-password");
+    if (username != null && username.equals(password)) {
+      return username;
+    }
+    throw new AuthenticationFailedException("invalid username/password");
+  }
+
+  @Override
+  public boolean authorize(final Object principal, final ResourcePermission permission) {
+    String[] principals = principal.toString().toLowerCase().split(",");
+    for (String role : principals) {
+      String permissionString = permission.toString().replace(":", "").toLowerCase();
+      if (permissionString.startsWith(role))
+        return true;
+    }
+    return false;
+  }
+
+  @Override
+  public void close() {
+    // nothing
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
index 39ad703..243458d 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
@@ -19,7 +19,6 @@ import java.io.Serializable;
 import java.lang.management.ManagementFactory;
 import java.net.InetAddress;
 import java.net.ServerSocket;
-import java.net.Socket;
 import java.net.UnknownHostException;
 import java.rmi.AlreadyBoundException;
 import java.rmi.registry.LocateRegistry;
@@ -90,12 +89,12 @@ public class ManagementAgent {
    */
   private boolean running = false;
   private Registry registry;
+
   private JMXConnectorServer jmxConnectorServer;
   private JMXShiroAuthenticator shiroAuthenticator;
   private final DistributionConfig config;
   private final SecurityService securityService;
   private boolean isHttpServiceRunning = false;
-
   /**
    * This system property is set to true when the embedded HTTP server is started so that the
    * embedded pulse webapp can use a local MBeanServer instead of a remote JMX connection.
@@ -115,11 +114,11 @@ public class ManagementAgent {
     return this.running;
   }
 
-  public synchronized boolean isHttpServiceRunning() {
+  synchronized boolean isHttpServiceRunning() {
     return isHttpServiceRunning;
   }
 
-  public synchronized void setHttpServiceRunning(boolean isHttpServiceRunning) {
+  private synchronized void setHttpServiceRunning(boolean isHttpServiceRunning) {
     this.isHttpServiceRunning = isHttpServiceRunning;
   }
 
@@ -180,7 +179,7 @@ public class ManagementAgent {
 
   private Server httpServer;
   private final String GEMFIRE_VERSION = GemFireVersion.getGemFireVersion();
-  private AgentUtil agentUtil = new AgentUtil(GEMFIRE_VERSION);
+  private final AgentUtil agentUtil = new AgentUtil(GEMFIRE_VERSION);
 
   private void startHttpService(boolean isServer) {
     final SystemManagementService managementService = (SystemManagementService) ManagementService
@@ -461,8 +460,7 @@ public class ManagementAgent {
             try {
               registry.bind("jmxrmi", stub);
             } catch (AlreadyBoundException x) {
-              final IOException io = new IOException(x.getMessage(), x);
-              throw io;
+              throw new IOException(x.getMessage(), x);
             }
             super.start();
           }
@@ -513,30 +511,18 @@ public class ManagementAgent {
           logger.info("Registered AccessControlMBean on " + accessControlMBeanON);
         } catch (InstanceAlreadyExistsException | MBeanRegistrationException
             | NotCompliantMBeanException e) {
-          throw new GemFireConfigException("Error while configuring accesscontrol for jmx resource",
-              e);
+          throw new GemFireConfigException(
+              "Error while configuring access control for jmx resource", e);
         }
       }
     } catch (MalformedObjectNameException e) {
-      throw new GemFireConfigException("Error while configuring accesscontrol for jmx resource", e);
+      throw new GemFireConfigException("Error while configuring access control for jmx resource",
+          e);
     }
   }
 
-  private static class GemFireRMIClientSocketFactory
-      implements RMIClientSocketFactory, Serializable {
-
-    private static final long serialVersionUID = -7604285019188827617L;
-
-    private transient SocketCreator sc;
-
-    public GemFireRMIClientSocketFactory(SocketCreator sc) {
-      this.sc = sc;
-    }
-
-    @Override
-    public Socket createSocket(String host, int port) throws IOException {
-      return this.sc.connectForClient(host, port, 0);
-    }
+  public JMXConnectorServer getJmxConnectorServer() {
+    return jmxConnectorServer;
   }
 
   private static class GemFireRMIServerSocketFactory
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
index aa530a5..fc11fc9 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
@@ -17,19 +17,14 @@ package org.apache.geode.management.internal.web.controllers;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.management.ManagementFactory;
-import java.net.URI;
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 
+import javax.management.InstanceNotFoundException;
 import javax.management.JMX;
 import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
-import javax.management.Query;
-import javax.management.QueryExp;
 
 import org.apache.logging.log4j.Logger;
 import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
@@ -39,25 +34,25 @@ import org.springframework.web.bind.WebDataBinder;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.InitBinder;
 import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.context.request.WebRequest;
-import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
 
 import org.apache.geode.internal.cache.GemFireCacheImpl;
 import org.apache.geode.internal.cache.InternalCache;
-import org.apache.geode.internal.lang.StringUtils;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.log4j.LogMarker;
+import org.apache.geode.internal.security.SecurityService;
 import org.apache.geode.internal.util.ArrayUtils;
 import org.apache.geode.management.DistributedSystemMXBean;
 import org.apache.geode.management.ManagementService;
 import org.apache.geode.management.MemberMXBean;
 import org.apache.geode.management.internal.MBeanJMXAdapter;
-import org.apache.geode.management.internal.ManagementConstants;
+import org.apache.geode.management.internal.ManagementAgent;
 import org.apache.geode.management.internal.SystemManagementService;
 import org.apache.geode.management.internal.cli.shell.Gfsh;
+import org.apache.geode.management.internal.security.MBeanServerWrapper;
 import org.apache.geode.management.internal.web.controllers.support.LoginHandlerInterceptor;
 import org.apache.geode.management.internal.web.util.UriUtils;
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.GemFireSecurityException;
 import org.apache.geode.security.NotAuthorizedException;
 
 /**
@@ -67,14 +62,15 @@ import org.apache.geode.security.NotAuthorizedException;
  * 
  * @see org.apache.geode.management.MemberMXBean
  * @see org.apache.geode.management.internal.cli.shell.Gfsh
+ * @see org.springframework.http.ResponseEntity
  * @see org.springframework.stereotype.Controller
  * @see org.springframework.web.bind.annotation.ExceptionHandler
  * @see org.springframework.web.bind.annotation.InitBinder
+ * @see org.springframework.web.bind.annotation.ResponseBody
  * @since GemFire 8.0
  */
 @SuppressWarnings("unused")
 public abstract class AbstractCommandsController {
-
   private static final Logger logger = LogService.getLogger();
 
   protected static final String DEFAULT_ENCODING = UriUtils.DEFAULT_ENCODING;
@@ -84,242 +80,53 @@ public abstract class AbstractCommandsController {
 
   private Class accessControlKlass;
 
-  private InternalCache getCache() {
-    return GemFireCacheImpl.getInstance();
-  }
-
-  // Convert a predefined exception to an HTTP Status code
-  @ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "Not authenticated") // 401
-  @ExceptionHandler(org.apache.geode.security.AuthenticationFailedException.class)
-  public void authenticate() {
-    // nothing
-  }
-
-  // Convert a predefined exception to an HTTP Status code
-  @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Access Denied") // 403
-  @ExceptionHandler(java.lang.SecurityException.class)
-  public void authorize() {
-    // nothing
-  }
-
-  /**
-   * Asserts the argument is valid, as determined by the caller passing the result of an evaluated
-   * expression to this assertion.
-   * 
-   * @param validArg a boolean value indicating the evaluation of the expression validating the
-   *        argument.
-   * @param message a String value used as the message when constructing an
-   *        IllegalArgumentException.
-   * @param args Object arguments used to populate placeholder's in the message.
-   * @throws IllegalArgumentException if the argument is not valid.
-   * @see java.lang.String#format(String, Object...)
-   */
-  protected static void assertArgument(final boolean validArg, final String message,
-      final Object... args) {
-    if (!validArg) {
-      throw new IllegalArgumentException(String.format(message, args));
-    }
-  }
-
-  /**
-   * Asserts the Object reference is not null!
-   * 
-   * @param obj the reference to the Object.
-   * @param message the String value used as the message when constructing and throwing a
-   *        NullPointerException.
-   * @param args Object arguments used to populate placeholder's in the message.
-   * @throws NullPointerException if the Object reference is null.
-   * @see java.lang.String#format(String, Object...)
-   */
-  protected static void assertNotNull(final Object obj, final String message,
-      final Object... args) {
-    if (obj == null) {
-      throw new NullPointerException(String.format(message, args));
-    }
-  }
-
-  /**
-   * Asserts whether state, based on the evaluation of a conditional expression, passed to this
-   * assertion is valid.
-   * 
-   * @param validState a boolean value indicating the evaluation of the expression from which the
-   *        conditional state is based. For example, a caller might use an expression of the form
-   *        (initableObj.isInitialized()).
-   * @param message a String values used as the message when constructing an IllegalStateException.
-   * @param args Object arguments used to populate placeholder's in the message.
-   * @throws IllegalStateException if the conditional state is not valid.
-   * @see java.lang.String#format(String, Object...)
-   */
-  protected static void assertState(final boolean validState, final String message,
-      final Object... args) {
-    if (!validState) {
-      throw new IllegalStateException(String.format(message, args));
-    }
-  }
-
-  /**
-   * Decodes the encoded String value using the default encoding UTF-8. It is assumed the String
-   * value was encoded with the URLEncoder using the UTF-8 encoding. This method handles
-   * UnsupportedEncodingException by just returning the encodedValue.
-   * 
-   * @param encodedValue the encoded String value to decode.
-   * @return the decoded value of the String or encodedValue if the UTF-8 encoding is unsupported.
-   * @see org.apache.geode.management.internal.web.util.UriUtils#decode(String)
-   */
-  protected static String decode(final String encodedValue) {
-    return UriUtils.decode(encodedValue);
+  @ExceptionHandler(Exception.class)
+  public ResponseEntity<String> internalError(final Exception e) {
+    final String stackTrace = getPrintableStackTrace(e);
+    logger.fatal(stackTrace);
+    return new ResponseEntity<>(stackTrace, HttpStatus.INTERNAL_SERVER_ERROR);
   }
 
-  /**
-   * Decodes the encoded String value using the specified encoding (such as UTF-8). It is assumed
-   * the String value was encoded with the URLEncoder using the specified encoding. This method
-   * handles UnsupportedEncodingException by just returning the encodedValue.
-   * 
-   * @param encodedValue a String value encoded in the encoding.
-   * @param encoding a String value specifying the encoding.
-   * @return the decoded value of the String or encodedValue if the specified encoding is
-   *         unsupported.
-   * @see org.apache.geode.management.internal.web.util.UriUtils#decode(String, String)
-   */
-  protected static String decode(final String encodedValue, final String encoding) {
-    return UriUtils.decode(encodedValue, encoding);
+  @ExceptionHandler(AuthenticationFailedException.class)
+  public ResponseEntity<String> unauthorized(AuthenticationFailedException e) {
+    return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED);
   }
 
-  /**
-   * Gets the specified value if not null or empty, otherwise returns the default value.
-   * 
-   * @param value the String value being evaluated for having value (not null and not empty).
-   * @param defaultValue the default String value returned if 'value' has no value.
-   * @return 'value' if not null or empty, otherwise returns the default value.
-   * @see #hasValue(String)
-   */
-  protected static String defaultIfNoValue(final String value, final String defaultValue) {
-    return (hasValue(value) ? value : defaultValue);
+  @ExceptionHandler({NotAuthorizedException.class, java.lang.SecurityException.class})
+  public ResponseEntity<String> forbidden(Exception e) {
+    return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
   }
 
-  /**
-   * Encodes the String value using the default encoding UTF-8.
-   * 
-   * @param value the String value to encode.
-   * @return an encoded value of the String using the default encoding UTF-8 or value if the UTF-8
-   *         encoding is unsupported.
-   * @see org.apache.geode.management.internal.web.util.UriUtils#encode(String)
-   */
-  protected static String encode(final String value) {
-    return UriUtils.encode(value);
+  @ExceptionHandler(MalformedObjectNameException.class)
+  public ResponseEntity<String> badRequest(final MalformedObjectNameException e) {
+    logger.info(e);
+    return new ResponseEntity<>(getPrintableStackTrace(e), HttpStatus.BAD_REQUEST);
   }
 
-  /**
-   * Encodes the String value using the specified encoding (such as UTF-8).
-   * 
-   * @param value the String value to encode.
-   * @param encoding a String value indicating the encoding.
-   * @return an encoded value of the String using the specified encoding or value if the specified
-   *         encoding is unsupported.
-   * @see org.apache.geode.management.internal.web.util.UriUtils#encode(String, String)
-   */
-  protected static String encode(final String value, final String encoding) {
-    return UriUtils.encode(value, encoding);
+  @ExceptionHandler(InstanceNotFoundException.class)
+  public ResponseEntity<String> notFound(final InstanceNotFoundException e) {
+    logger.info(e);
+    return new ResponseEntity<>(getPrintableStackTrace(e), HttpStatus.NOT_FOUND);
   }
 
-  /**
-   * Determines whether the specified Object has value, which is determined by a non-null Object
-   * reference.
-   * 
-   * @param value the Object value being evaluated for value.
-   * @return a boolean value indicating whether the specified Object has value.
-   * @see java.lang.Object
-   */
-  protected static boolean hasValue(final Object value) {
-    return (value instanceof String[] ? hasValue((String[]) value)
-        : (value instanceof String ? hasValue((String) value) : value != null));
-  }
-
-  /**
-   * Determines whether the specified String has value, determined by whether the String is
-   * non-null, not empty and not blank.
-   * 
-   * @param value the String being evaluated for value.
-   * @return a boolean indicating whether the specified String has value or not.
-   * @see java.lang.String
-   */
-  protected static boolean hasValue(final String value) {
-    return StringUtils.isNotBlank(value);
-  }
-
-  /**
-   * Determines whether the specified String array has any value, which is determined by a non-null
-   * String array reference along with containing at least 1 non-null, not empty and not blank
-   * element.
-   * 
-   * @param array an String array being evaluated for value.
-   * @return a boolean indicating whether the specified String array has any value.
-   * @see #hasValue(String)
-   * @see java.lang.String
-   */
-  protected static boolean hasValue(final String[] array) {
-    if (array != null && array.length > 0) {
-      for (final String element : array) {
-        if (hasValue(element)) {
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
 
   /**
    * Writes the stack trace of the Throwable to a String.
-   * 
+   *
    * @param t a Throwable object who's stack trace will be written to a String.
    * @return a String containing the stack trace of the Throwable.
    * @see java.io.StringWriter
    * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
    */
-  protected static String printStackTrace(final Throwable t) {
+  private static String getPrintableStackTrace(final Throwable t) {
     final StringWriter stackTraceWriter = new StringWriter();
     t.printStackTrace(new PrintWriter(stackTraceWriter));
     return stackTraceWriter.toString();
   }
 
   /**
-   * Converts the URI relative path to an absolute path based on the Servlet context information.
-   * 
-   * @param path the URI relative path to append to the Servlet context path.
-   * @param scheme the scheme to use for the URI
-   * @return a URI constructed with all component path information.
-   * @see java.net.URI
-   * @see org.springframework.web.servlet.support.ServletUriComponentsBuilder
-   */
-  public static URI toUri(final String path, final String scheme) {
-    return ServletUriComponentsBuilder.fromCurrentContextPath().path(REST_API_VERSION).path(path)
-        .scheme(scheme).build().toUri();
-  }
-
-  /**
-   * Handles any Exception thrown by a REST API web service endpoint, HTTP request handler method
-   * during the invocation and processing of a command.
-   * 
-   * @param cause the Exception causing the error.
-   * @return a ResponseEntity with an appropriate HTTP status code (500 - Internal Server Error) and
-   *         HTTP response body containing the stack trace of the Exception.
-   * @see java.lang.Exception
-   * @see org.springframework.http.ResponseEntity
-   * @see org.springframework.web.bind.annotation.ExceptionHandler
-   * @see org.springframework.web.bind.annotation.ResponseBody
-   */
-  @ExceptionHandler(Exception.class)
-  @ResponseBody
-  public ResponseEntity<String> handleException(final Exception cause) {
-    final String stackTrace = printStackTrace(cause);
-    logger.fatal(stackTrace);
-    return new ResponseEntity<String>(stackTrace, HttpStatus.INTERNAL_SERVER_ERROR);
-  }
-
-  /**
    * Initializes data bindings for various HTTP request handler method parameter Java class types.
-   * 
+   *
    * @param dataBinder the DataBinder implementation used for Web transactions.
    * @see org.springframework.web.bind.WebDataBinder
    * @see org.springframework.web.bind.annotation.InitBinder
@@ -330,110 +137,27 @@ public abstract class AbstractCommandsController {
         new StringArrayPropertyEditor(StringArrayPropertyEditor.DEFAULT_SEPARATOR, false));
   }
 
-  /**
-   * Logs the client's HTTP (web) request including details of the HTTP headers and request
-   * parameters along with the web request context and description.
-   * 
-   * @param request the object encapsulating the details of the client's HTTP (web) request.
-   * @see org.springframework.web.context.request.WebRequest
-   */
-  protected void logRequest(final WebRequest request) {
-    if (request != null) {
-      final Map<String, String> headers = new HashMap<java.lang.String, java.lang.String>();
-
-      for (Iterator<String> it = request.getHeaderNames(); it.hasNext();) {
-        final String headerName = it.next();
-        headers.put(headerName,
-            ArrayUtils.toString((Object[]) request.getHeaderValues(headerName)));
-      }
-
-      final Map<String, String> parameters =
-          new HashMap<String, String>(request.getParameterMap().size());
-
-      for (Iterator<String> it = request.getParameterNames(); it.hasNext();) {
-        final String parameterName = it.next();
-        parameters.put(parameterName,
-            ArrayUtils.toString((Object[]) request.getParameterValues(parameterName)));
-      }
-
-      logger.info("HTTP-request: description ({}), context ({}), headers ({}), parameters ({})",
-          request.getDescription(false), request.getContextPath(), headers, parameters);
-    }
-  }
 
   /**
    * Gets a reference to the platform MBeanServer running in this JVM process. The MBeanServer
-   * instance constitutes a connection to the MBeanServer.
+   * instance constitutes a connection to the MBeanServer. This method returns a security-wrapped
+   * MBean if integrated security is active.
    * 
    * @return a reference to the platform MBeanServer for this JVM process.
    * @see java.lang.management.ManagementFactory#getPlatformMBeanServer()
    * @see javax.management.MBeanServer
    */
   protected MBeanServer getMBeanServer() {
-    return ManagementFactory.getPlatformMBeanServer();
-  }
-
-  /**
-   * Gets the MemberMXBean from the JVM Platform MBeanServer for the specified member, identified by
-   * name or ID in the GemFire cluster.
-   * 
-   * @param memberNameId a String indicating the name or ID of the GemFire member.
-   * @return a proxy to the GemFire member's MemberMXBean.
-   * @throws IllegalStateException if no MemberMXBean could be found for GemFire member with ID or
-   *         name.
-   * @throws RuntimeException wrapping the MalformedObjectNameException if the ObjectName pattern is
-   *         malformed.
-   * @see #getMBeanServer()
-   * @see #isMemberMXBeanFound(java.util.Collection)
-   * @see javax.management.ObjectName
-   * @see javax.management.QueryExp
-   * @see javax.management.MBeanServer#queryNames(javax.management.ObjectName,
-   *      javax.management.QueryExp)
-   * @see javax.management.JMX#newMXBeanProxy(javax.management.MBeanServerConnection,
-   *      javax.management.ObjectName, Class)
-   * @see org.apache.geode.management.MemberMXBean
-   */
-  protected MemberMXBean getMemberMXBean(final String memberNameId) {
-    try {
-      final MBeanServer connection = getMBeanServer();
-
-      final String objectNamePattern =
-          ManagementConstants.OBJECTNAME__PREFIX.concat("type=Member,*");
-
-      // NOTE throws a MalformedObjectNameException, but this should not happen since we constructed
-      // the ObjectName above
-      final ObjectName objectName = ObjectName.getInstance(objectNamePattern);
-
-      final QueryExp query = Query.or(Query.eq(Query.attr("Name"), Query.value(memberNameId)),
-          Query.eq(Query.attr("Id"), Query.value(memberNameId)));
-
-      final Set<ObjectName> objectNames = connection.queryNames(objectName, query);
-
-      assertState(isMemberMXBeanFound(objectNames),
-          "No MemberMXBean with ObjectName (%1$s) based on Query (%2$s) was found in the Platform MBeanServer for member (%3$s)!",
-          objectName, query, memberNameId);
-
-      return JMX.newMXBeanProxy(connection, objectNames.iterator().next(), MemberMXBean.class);
-    } catch (MalformedObjectNameException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  /**
-   * Determines whether the desired MemberMXBean, identified by name or ID, was found in the
-   * platform MBeanServer of this JVM process.
-   * 
-   * @param objectNames a Collection of ObjectNames possibly referring to the desired MemberMXBean.
-   * @return a boolean value indicating whether the desired MemberMXBean was found.
-   * @see javax.management.ObjectName
-   */
-  private boolean isMemberMXBeanFound(final Collection<ObjectName> objectNames) {
-    return !(objectNames == null || objectNames.isEmpty());
+    InternalCache cache = GemFireCacheImpl.getInstance();
+    SystemManagementService service =
+        (SystemManagementService) ManagementService.getExistingManagementService(cache);
+    ManagementAgent managementAgent = service.getManagementAgent();
+    return managementAgent.getJmxConnectorServer().getMBeanServer();
   }
 
   /**
    * Lookup operation for the MemberMXBean representing the Manager in the GemFire cluster. This
-   * method gets an instance fo the Platform MBeanServer for this JVM process and uses it to lookup
+   * method gets an instance of the Platform MBeanServer for this JVM process and uses it to lookup
    * the MemberMXBean for the GemFire Manager based on the ObjectName declared in the
    * DistributedSystemMXBean.getManagerObjectName() operation.
    * 
@@ -444,15 +168,11 @@ public abstract class AbstractCommandsController {
    * @see org.apache.geode.management.DistributedSystemMXBean
    * @see org.apache.geode.management.MemberMXBean
    */
-  protected synchronized MemberMXBean getManagingMemberMXBean() {
+  private synchronized MemberMXBean getManagingMemberMXBean() {
     if (managingMemberMXBeanProxy == null) {
-      SystemManagementService service =
-          (SystemManagementService) ManagementService.getExistingManagementService(getCache());
       MBeanServer mbs = getMBeanServer();
-
-      final DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(mbs,
+      DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(mbs,
           MBeanJMXAdapter.getDistributedSystemName(), DistributedSystemMXBean.class);
-
       managingMemberMXBeanProxy = createMemberMXBeanForManagerUsingProxy(mbs,
           distributedSystemMXBean.getMemberObjectName());
     }
@@ -461,11 +181,9 @@ public abstract class AbstractCommandsController {
   }
 
   protected synchronized ObjectName getMemberObjectName() {
-    final MBeanServer platformMBeanServer = getMBeanServer();
-
-    final DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(platformMBeanServer,
+    MBeanServer platformMBeanServer = getMBeanServer();
+    DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(platformMBeanServer,
         MBeanJMXAdapter.getDistributedSystemName(), DistributedSystemMXBean.class);
-
     return distributedSystemMXBean.getMemberObjectName();
   }
 
@@ -494,7 +212,7 @@ public abstract class AbstractCommandsController {
    * @see LoginHandlerInterceptor#getEnvironment()
    */
   protected Map<String, String> getEnvironment() {
-    final Map<String, String> environment = new HashMap<String, String>();
+    final Map<String, String> environment = new HashMap<>();
 
     environment.putAll(LoginHandlerInterceptor.getEnvironment());
     environment.put(Gfsh.ENV_APP_NAME, Gfsh.GFSH_APP_NAME);
@@ -525,13 +243,7 @@ public abstract class AbstractCommandsController {
     logger.debug(LogMarker.CONFIG,
         "Processing Command ({}) with Environment ({}) having File Data ({})...", command,
         environment, (fileData != null && fileData.length > 0));
-    return getManagingMemberMXBean().processCommand(command, environment,
-        ArrayUtils.toByteArray(fileData));
+    MemberMXBean manager = getManagingMemberMXBean();
+    return manager.processCommand(command, environment, ArrayUtils.toByteArray(fileData));
   }
-
-  @ExceptionHandler(NotAuthorizedException.class)
-  public ResponseEntity<String> handleAppException(NotAuthorizedException ex) {
-    return new ResponseEntity<String>(ex.getMessage(), HttpStatus.FORBIDDEN);
-  }
-
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractMultiPartCommandsController.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractMultiPartCommandsController.java
deleted file mode 100644
index 14b3d05..0000000
--- a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractMultiPartCommandsController.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.geode.management.internal.web.controllers;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import org.apache.geode.internal.lang.SystemUtils;
-import org.apache.geode.internal.util.IOUtils;
-
-import org.springframework.web.multipart.MultipartFile;
-
-/**
- * The AbstractMultiPartCommandsController class is a abstract base class encapsulating all common
- * functionality for handling multi-part (file upload) HTTP requests.
- * <p/>
- * 
- * @see org.apache.geode.management.internal.web.controllers.AbstractCommandsController
- * @since GemFire 8.0
- */
-@SuppressWarnings("unused")
-public class AbstractMultiPartCommandsController extends AbstractCommandsController {
-
-  protected static final String RESOURCES_REQUEST_PARAMETER = "resources";
-
-  /**
-   * Saves an array of File objects to this system's file system.
-   * <p/>
-   * 
-   * @param files an array of MultipartFile objects to persist to the file system.
-   * @throws IOException if I/O error occurs while saving the Files to the file system.
-   * @see org.springframework.web.multipart.MultipartFile
-   */
-  protected static void save(final MultipartFile... files) throws IOException {
-    if (files != null) {
-      for (final MultipartFile file : files) {
-        save(file);
-      }
-    }
-  }
-
-  /**
-   * Saves a multi-part File to this system's file system.
-   * <p/>
-   * 
-   * @param file the MultipartFile object to persist to the file system.
-   * @throws IOException if I/O error occurs while saving the File to the file system.
-   * @see org.springframework.web.multipart.MultipartFile
-   */
-  protected static void save(final MultipartFile file) throws IOException {
-    final File saveFile = new File(SystemUtils.CURRENT_DIRECTORY, file.getOriginalFilename());
-
-    FileOutputStream fileWriter = null;
-
-    try {
-      fileWriter = new FileOutputStream(saveFile, false);
-      fileWriter.write(file.getBytes());
-      fileWriter.flush();
-    } finally {
-      IOUtils.close(fileWriter);
-    }
-  }
-
-}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
index 02c3e93..9c65090 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
@@ -14,7 +14,8 @@
  */
 package org.apache.geode.management.internal.web.controllers;
 
-import static org.apache.geode.management.internal.web.controllers.AbstractMultiPartCommandsController.RESOURCES_REQUEST_PARAMETER;
+import static org.apache.commons.io.IOUtils.toInputStream;
+import static org.apache.geode.management.internal.web.util.UriUtils.decode;
 
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -23,8 +24,11 @@ import java.util.Set;
 
 import javax.management.AttributeNotFoundException;
 import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
+import javax.management.ReflectionException;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.ArrayUtils;
@@ -65,113 +69,53 @@ import org.apache.geode.management.internal.web.util.ConvertUtils;
 @RequestMapping(AbstractCommandsController.REST_API_VERSION)
 @SuppressWarnings("unused")
 public class ShellCommandsController extends AbstractCommandsController {
-
-  protected static final String MBEAN_ATTRIBUTE_LINK_RELATION = "mbean-attribute";
-  protected static final String MBEAN_OPERATION_LINK_RELATION = "mbean-operation";
-  protected static final String MBEAN_QUERY_LINK_RELATION = "mbean-query";
-  protected static final String PING_LINK_RELATION = "ping";
-
   @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, value = "/management/commands")
   public ResponseEntity<InputStreamResource> command(@RequestParam(value = "cmd") String command,
-      @RequestParam(value = RESOURCES_REQUEST_PARAMETER,
-          required = false) MultipartFile[] fileResource)
+      @RequestParam(value = "resources", required = false) MultipartFile[] fileResource)
       throws IOException {
     String result =
         processCommand(decode(command), getEnvironment(), ConvertUtils.convert(fileResource));
     return getResponse(result);
   }
 
-  ResponseEntity<InputStreamResource> getResponse(String result) {
-    // the result is json string from CommandResult
-    CommandResult commandResult = ResultBuilder.fromJson(result);
-
-    if (commandResult.getStatus().equals(Result.Status.OK) && commandResult.hasFileToDownload()) {
-      return getFileDownloadResponse(commandResult);
-    } else {
-      return getJsonResponse(result);
-    }
-  }
-
-  private ResponseEntity<InputStreamResource> getJsonResponse(String result) {
-    // if the command is successful, the output is the filepath, else we need to send the
-    // original result back so that the receiver will know to turn it into a Result object
-
-    HttpHeaders respHeaders = new HttpHeaders();
-    try {
-      InputStreamResource isr =
-          new InputStreamResource(org.apache.commons.io.IOUtils.toInputStream(result, "UTF-8"));
-      respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
-      return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
-    } catch (Exception e) {
-      throw new RuntimeException("IO Error writing file to output stream", e);
-    }
-  }
-
-  private ResponseEntity<InputStreamResource> getFileDownloadResponse(CommandResult commandResult) {
-    HttpHeaders respHeaders = new HttpHeaders();
-    InputStreamResource isr;// if the command is successful, the output is the filepath,
-
-    Path filePath = commandResult.getFileToDownload();
-    try {
-      isr = new InputStreamResource(new FileInputStream(filePath.toFile()));
-      respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
-      return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
-    } catch (Exception e) {
-      throw new RuntimeException("IO Error writing file to output stream", e);
-    } finally {
-      FileUtils.deleteQuietly(filePath.toFile());
-    }
-  }
-
   @RequestMapping(method = RequestMethod.GET, value = "/mbean/attribute")
   public ResponseEntity<?> getAttribute(@RequestParam("resourceName") final String resourceName,
-      @RequestParam("attributeName") final String attributeName) {
-    try {
-      final Object attributeValue = getMBeanServer()
-          .getAttribute(ObjectName.getInstance(decode(resourceName)), decode(attributeName));
-
-      return new ResponseEntity<>(IOUtils.serializeObject(attributeValue), HttpStatus.OK);
-    } catch (AttributeNotFoundException | MalformedObjectNameException e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.BAD_REQUEST);
-    } catch (InstanceNotFoundException e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.NOT_FOUND);
-    } catch (Exception e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
-    }
+      @RequestParam("attributeName") final String attributeName)
+      throws AttributeNotFoundException, MBeanException, ReflectionException,
+      InstanceNotFoundException, IOException, MalformedObjectNameException {
+    // Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
+    MBeanServer mBeanServer = getMBeanServer();
+    ObjectName objectName = ObjectName.getInstance(decode(resourceName));
+    final Object attributeValue = mBeanServer.getAttribute(objectName, decode(attributeName));
+    byte[] serializedResult = IOUtils.serializeObject(attributeValue);
+    return new ResponseEntity<>(serializedResult, HttpStatus.OK);
   }
 
   @RequestMapping(method = RequestMethod.POST, value = "/mbean/operation")
   public ResponseEntity<?> invoke(@RequestParam("resourceName") final String resourceName,
       @RequestParam("operationName") final String operationName,
       @RequestParam(value = "signature", required = false) String[] signature,
-      @RequestParam(value = "parameters", required = false) Object[] parameters) {
+      @RequestParam(value = "parameters", required = false) Object[] parameters)
+      throws MalformedObjectNameException, MBeanException, InstanceNotFoundException,
+      ReflectionException, IOException {
+    // Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
     signature = (signature != null ? signature : ArrayUtils.EMPTY_STRING_ARRAY);
     parameters = (parameters != null ? parameters : ObjectUtils.EMPTY_OBJECT_ARRAY);
-
-    try {
-      final Object result = getMBeanServer().invoke(ObjectName.getInstance(decode(resourceName)),
-          decode(operationName), parameters, signature);
-
-      return new ResponseEntity<>(IOUtils.serializeObject(result), HttpStatus.OK);
-    } catch (InstanceNotFoundException e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.NOT_FOUND);
-    } catch (MalformedObjectNameException e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.BAD_REQUEST);
-    } catch (Exception e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
-    }
+    MBeanServer mBeanServer = getMBeanServer();
+    ObjectName objectName = ObjectName.getInstance(decode(resourceName));
+    final Object result =
+        mBeanServer.invoke(objectName, decode(operationName), parameters, signature);
+    byte[] serializedResult = IOUtils.serializeObject(result);
+    return new ResponseEntity<>(serializedResult, HttpStatus.OK);
   }
 
   @RequestMapping(method = RequestMethod.POST, value = "/mbean/query")
-  public ResponseEntity<?> queryNames(@RequestBody final QueryParameterSource query) {
-    try {
-      final Set<ObjectName> objectNames =
-          getMBeanServer().queryNames(query.getObjectName(), query.getQueryExpression());
-
-      return new ResponseEntity<>(IOUtils.serializeObject(objectNames), HttpStatus.OK);
-    } catch (IOException e) {
-      return new ResponseEntity<>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
-    }
+  public ResponseEntity<?> queryNames(@RequestBody final QueryParameterSource query)
+      throws IOException {
+    // Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
+    final Set<ObjectName> objectNames =
+        getMBeanServer().queryNames(query.getObjectName(), query.getQueryExpression());
+    return new ResponseEntity<>(IOUtils.serializeObject(objectNames), HttpStatus.OK);
   }
 
   @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}, value = "/ping")
@@ -187,8 +131,42 @@ public class ShellCommandsController extends AbstractCommandsController {
 
   @RequestMapping(method = RequestMethod.GET, value = "/version/full")
   @ResponseBody
-  public String versionSimple() {
+  public String fullVersion() {
     return GemFireVersion.asString();
   }
 
+
+  private ResponseEntity<InputStreamResource> getResponse(String result) {
+    CommandResult commandResult = ResultBuilder.fromJson(result);
+    if (commandResult.getStatus().equals(Result.Status.OK) && commandResult.hasFileToDownload()) {
+      return getFileDownloadResponse(commandResult);
+    } else {
+      return getJsonResponse(result);
+    }
+  }
+
+  private ResponseEntity<InputStreamResource> getJsonResponse(String result) {
+    HttpHeaders respHeaders = new HttpHeaders();
+    try {
+      InputStreamResource isr = new InputStreamResource(toInputStream(result, "UTF-8"));
+      respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+      return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
+    } catch (Exception e) {
+      throw new RuntimeException("IO Error writing file to output stream", e);
+    }
+  }
+
+  private ResponseEntity<InputStreamResource> getFileDownloadResponse(CommandResult commandResult) {
+    HttpHeaders respHeaders = new HttpHeaders();
+    Path filePath = commandResult.getFileToDownload();
+    try {
+      InputStreamResource isr = new InputStreamResource(new FileInputStream(filePath.toFile()));
+      respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
+      return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
+    } catch (Exception e) {
+      throw new RuntimeException("IO Error writing file to output stream", e);
+    } finally {
+      FileUtils.deleteQuietly(filePath.toFile());
+    }
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/http/support/SimpleHttpRequester.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/http/support/SimpleHttpRequester.java
index 23c4482..8c4f91f 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/web/http/support/SimpleHttpRequester.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/http/support/SimpleHttpRequester.java
@@ -15,7 +15,6 @@
 
 package org.apache.geode.management.internal.web.http.support;
 
-import java.io.IOException;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
@@ -23,16 +22,12 @@ import java.util.Set;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
-import org.springframework.http.client.ClientHttpResponse;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
-import org.springframework.web.client.ResponseErrorHandler;
 import org.springframework.web.client.RestTemplate;
 
 import org.apache.geode.management.internal.cli.shell.Gfsh;
-import org.apache.geode.security.AuthenticationFailedException;
-import org.apache.geode.security.NotAuthorizedException;
+import org.apache.geode.management.internal.web.shell.RestOperationErrorHandler;
 
 
 /**
@@ -78,51 +73,7 @@ public class SimpleHttpRequester {
     this.securityProperties = securityProperties;
     this.restTemplate = new RestTemplate(clientHttpRequestFactory);
 
-    this.restTemplate.setErrorHandler(new ResponseErrorHandler() {
-      @Override
-      public boolean hasError(final ClientHttpResponse response) throws IOException {
-        final HttpStatus status = response.getStatusCode();
-
-        switch (status) {
-          case BAD_REQUEST: // 400 *
-          case UNAUTHORIZED: // 401
-          case FORBIDDEN: // 403
-          case NOT_FOUND: // 404 *
-          case METHOD_NOT_ALLOWED: // 405 *
-          case NOT_ACCEPTABLE: // 406 *
-          case REQUEST_TIMEOUT: // 408
-          case CONFLICT: // 409
-          case REQUEST_ENTITY_TOO_LARGE: // 413
-          case REQUEST_URI_TOO_LONG: // 414
-          case UNSUPPORTED_MEDIA_TYPE: // 415 *
-          case TOO_MANY_REQUESTS: // 429
-          case INTERNAL_SERVER_ERROR: // 500 *
-          case NOT_IMPLEMENTED: // 501
-          case BAD_GATEWAY: // 502 ?
-          case SERVICE_UNAVAILABLE: // 503
-            return true;
-          default:
-            return false;
-        }
-      }
-
-      @Override
-      public void handleError(final ClientHttpResponse response) throws IOException {
-        final String message = String.format("The HTTP request failed with: %1$d - %2$s",
-            response.getRawStatusCode(), response.getStatusText());
-
-        if (response.getRawStatusCode() == 401) {
-          throw new AuthenticationFailedException(message);
-        } else if (response.getRawStatusCode() == 403) {
-          throw new NotAuthorizedException(message);
-        } else {
-          throw new RuntimeException(message);
-        }
-
-      }
-
-
-    });
+    this.restTemplate.setErrorHandler(new RestOperationErrorHandler());
 
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java
index a6f6c6f..577280d 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java
@@ -14,9 +14,7 @@
  */
 package org.apache.geode.management.internal.web.shell;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.file.Files;
@@ -33,12 +31,12 @@ import javax.management.ObjectName;
 import javax.management.QueryExp;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.logging.log4j.Logger;
 import org.springframework.core.io.Resource;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.client.ClientHttpResponse;
@@ -48,13 +46,11 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.ResourceAccessException;
-import org.springframework.web.client.ResponseErrorHandler;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.util.UriComponentsBuilder;
 
 import org.apache.geode.annotations.TestingOnly;
 import org.apache.geode.internal.GemFireVersion;
-import org.apache.geode.internal.lang.StringUtils;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.util.IOUtils;
 import org.apache.geode.management.DistributedSystemMXBean;
@@ -68,8 +64,6 @@ import org.apache.geode.management.internal.web.http.converter.SerializableObjec
 import org.apache.geode.management.internal.web.http.support.SimpleHttpRequester;
 import org.apache.geode.management.internal.web.shell.support.HttpMBeanProxyFactory;
 import org.apache.geode.management.internal.web.util.ConvertUtils;
-import org.apache.geode.security.AuthenticationFailedException;
-import org.apache.geode.security.NotAuthorizedException;
 
 /**
  * The HttpOperationInvoker class is an abstract base class encapsulating common functionality for
@@ -85,23 +79,23 @@ import org.apache.geode.security.NotAuthorizedException;
 @SuppressWarnings("unused")
 public class HttpOperationInvoker implements OperationInvoker {
 
-  protected static final long DEFAULT_INITIAL_DELAY = TimeUnit.SECONDS.toMillis(1);
-  protected static final long DEFAULT_PERIOD = TimeUnit.MILLISECONDS.toMillis(2000);
+  private static final long DEFAULT_INITIAL_DELAY = TimeUnit.SECONDS.toMillis(1);
+  private static final long DEFAULT_PERIOD = TimeUnit.MILLISECONDS.toMillis(2000);
 
-  protected static final String REST_API_BASE_URL = "http://localhost:8080";
-  protected static final String REST_API_VERSION = "/v1";
-  protected static final String REST_API_WEB_APP_CONTEXT = "/geode-mgmt";
-  protected static final String REST_API_URL =
+  private static final String REST_API_BASE_URL = "http://localhost:8080";
+  private static final String REST_API_VERSION = "/v1";
+  private static final String REST_API_WEB_APP_CONTEXT = "/geode-mgmt";
+  private static final String REST_API_URL =
       REST_API_BASE_URL + REST_API_WEB_APP_CONTEXT + REST_API_VERSION;
-  protected static final String USER_AGENT_HTTP_REQUEST_HEADER_VALUE =
+  private static final String USER_AGENT_HTTP_REQUEST_HEADER_VALUE =
       "GemFire-Shell/v" + GemFireVersion.getGemFireVersion();
 
-  protected static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
+  private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
 
   // the ID of the GemFire distributed system (cluster)
   private Integer clusterId = CLUSTER_ID_WHEN_NOT_CONNECTED;
 
-  protected static final String RESOURCES_REQUEST_PARAMETER = "resources";
+  private static final String RESOURCES_REQUEST_PARAMETER = "resources";
 
   // Executor for scheduling periodic Runnable task to assess the state of the Manager's HTTP
   // service or Web Service
@@ -144,7 +138,7 @@ public class HttpOperationInvoker implements OperationInvoker {
    * testing purposes.
    */
   @TestingOnly
-  HttpOperationInvoker(final String baseUrl) {
+  private HttpOperationInvoker(final String baseUrl) {
     this.baseUrl = baseUrl;
     this.executorService = null;
     this.gfsh = null;
@@ -154,7 +148,7 @@ public class HttpOperationInvoker implements OperationInvoker {
   /**
    * Constructs an instance of the HttpOperationInvoker class with a reference to the GemFire shell
    * (Gfsh) instance using this HTTP-based OperationInvoker to send commands to the GemFire Manager
-   * via HTTP for procsessing along with the base URL to the GemFire Manager's embedded HTTP service
+   * via HTTP for processing along with the base URL to the GemFire Manager's embedded HTTP service
    * hosting the HTTP (REST) interface.
    *
    * @param gfsh a reference to the instance of the GemFire shell (Gfsh) using this HTTP-based
@@ -178,9 +172,8 @@ public class HttpOperationInvoker implements OperationInvoker {
     // constructs an instance of the Spring RestTemplate for M&M REST API (interface) operations
     this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
 
-    // add our custom HttpMessageConverter for serializing DTO Objects into the HTTP request message
-    // body
-    // and de-serializing HTTP response message body content back into DTO Objects
+    // add our custom HttpMessageConverter for serializing DTO Objects into the HTTP request
+    // message body and de-serializing HTTP response message body content back into DTO Objects
     List<HttpMessageConverter<?>> converters = this.restTemplate.getMessageConverters();
     // remove the MappingJacksonHttpConverter
     for (int i = converters.size() - 1; i >= 0; i--) {
@@ -192,77 +185,13 @@ public class HttpOperationInvoker implements OperationInvoker {
     converters.add(new SerializableObjectHttpMessageConverter());
 
     // set the ResponseErrorHandler handling any errors originating from our HTTP request
-    this.restTemplate.setErrorHandler(new ResponseErrorHandler() {
-      @Override
-      public boolean hasError(final ClientHttpResponse response) throws IOException {
-        final HttpStatus status = response.getStatusCode();
-
-        switch (status) {
-          case BAD_REQUEST: // 400 *
-          case UNAUTHORIZED: // 401
-          case FORBIDDEN: // 403
-          case NOT_FOUND: // 404 *
-          case METHOD_NOT_ALLOWED: // 405 *
-          case NOT_ACCEPTABLE: // 406 *
-          case REQUEST_TIMEOUT: // 408
-          case CONFLICT: // 409
-          case REQUEST_ENTITY_TOO_LARGE: // 413
-          case REQUEST_URI_TOO_LONG: // 414
-          case UNSUPPORTED_MEDIA_TYPE: // 415 *
-          case TOO_MANY_REQUESTS: // 429
-          case INTERNAL_SERVER_ERROR: // 500 *
-          case NOT_IMPLEMENTED: // 501
-          case BAD_GATEWAY: // 502 ?
-          case SERVICE_UNAVAILABLE: // 503
-            return true;
-          default:
-            return false;
-        }
-      }
-
-      @Override
-      public void handleError(final ClientHttpResponse response) throws IOException {
-        String body = readBody(response);
-        final String message = String.format("The HTTP request failed with: %1$d - %2$s.",
-            response.getRawStatusCode(), body);
-
-        if (gfsh.getDebug()) {
-          gfsh.logSevere(body, null);
-        }
-
-        if (response.getRawStatusCode() == 401) {
-          throw new AuthenticationFailedException(message);
-        } else if (response.getRawStatusCode() == 403) {
-          throw new NotAuthorizedException(message);
-        } else {
-          throw new RuntimeException(message);
-        }
-      }
-
-      private String readBody(final ClientHttpResponse response) throws IOException {
-        BufferedReader responseBodyReader = null;
+    this.restTemplate.setErrorHandler(new RestOperationErrorHandler(gfsh));
 
-        try {
-          responseBodyReader = new BufferedReader(new InputStreamReader(response.getBody()));
-
-          final StringBuilder buffer = new StringBuilder();
-          String line;
-
-          while ((line = responseBodyReader.readLine()) != null) {
-            buffer.append(line).append(StringUtils.LINE_SEPARATOR);
-          }
-
-          return buffer.toString().trim();
-        } finally {
-          IOUtils.close(responseBodyReader);
-        }
-      }
-    });
     setupBackgroundPingRequest();
     initClusterId();
   }
 
-  protected void setupBackgroundPingRequest() {
+  private void setupBackgroundPingRequest() {
     SimpleHttpRequester requester = new SimpleHttpRequester(gfsh, securityProperties);
     getExecutorService().scheduleAtFixedRate(() -> {
       try {
@@ -312,7 +241,7 @@ public class HttpOperationInvoker implements OperationInvoker {
    * @return an instance of the ScheduledExecutorService for scheduling periodic or delayed tasks.
    * @see java.util.concurrent.ScheduledExecutorService
    */
-  protected ScheduledExecutorService getExecutorService() {
+  private ScheduledExecutorService getExecutorService() {
     return this.executorService;
   }
 
@@ -335,7 +264,7 @@ public class HttpOperationInvoker implements OperationInvoker {
    * @return an instance of the Spring RestTemplate used to make REST API web service calls.
    * @see org.springframework.web.client.RestTemplate
    */
-  protected RestTemplate getRestTemplate() {
+  private RestTemplate getRestTemplate() {
     return this.restTemplate;
   }
 
@@ -365,7 +294,7 @@ public class HttpOperationInvoker implements OperationInvoker {
    * @see #isDebugEnabled()
    * @see #printInfo(String, Object...)
    */
-  protected void printDebug(final String message, final Object... args) {
+  private void printDebug(final String message, final Object... args) {
     if (isDebugEnabled()) {
       printInfo(message, args);
     }
@@ -446,8 +375,7 @@ public class HttpOperationInvoker implements OperationInvoker {
     }
   }
 
-  void addHeaderValues(org.springframework.http.client.ClientHttpRequest request)
-      throws IOException {
+  private void addHeaderValues(org.springframework.http.client.ClientHttpRequest request) {
     // update the headers
     request.getHeaders().add(HttpHeaders.USER_AGENT, USER_AGENT_HTTP_REQUEST_HEADER_VALUE);
     request.getHeaders().setAccept(acceptableMediaTypes);
@@ -533,7 +461,7 @@ public class HttpOperationInvoker implements OperationInvoker {
     return clusterId;
   }
 
-  protected void initClusterId() {
+  private void initClusterId() {
     if (isReady()) {
       try {
         clusterId = (Integer) getAttribute(ManagementConstants.OBJECTNAME__DISTRIBUTEDSYSTEM_MXBEAN,
@@ -593,7 +521,7 @@ public class HttpOperationInvoker implements OperationInvoker {
       final String[] signatures) {
     final URI link = createURI("/mbean/operation");
 
-    MultiValueMap<String, Object> content = new LinkedMultiValueMap<String, Object>();
+    MultiValueMap<String, Object> content = new LinkedMultiValueMap<>();
 
     content.add("resourceName", resourceName);
     content.add("operationName", operationName);
@@ -609,8 +537,8 @@ public class HttpOperationInvoker implements OperationInvoker {
     }
 
     try {
-      return IOUtils
-          .deserializeObject(post(link, MediaType.MULTIPART_FORM_DATA, content, byte[].class));
+      byte[] postResult = post(link, MediaType.MULTIPART_FORM_DATA, content, byte[].class);
+      return IOUtils.deserializeObject(postResult);
     } catch (IOException e) {
       throw new MBeanAccessException(String.format(
           "De-serializing the result from invoking operation (%1$s) on MBean (%2$s) failed!",
@@ -678,7 +606,7 @@ public class HttpOperationInvoker implements OperationInvoker {
   public Object processCommand(final CommandRequest command) {
     URI link = command.getHttpRequestUrl(baseUrl);
     if (command.hasFileData()) {
-      MultiValueMap<String, Object> content = new LinkedMultiValueMap<String, Object>();
+      MultiValueMap<String, Object> content = new LinkedMultiValueMap<>();
 
       Resource[] resources = ConvertUtils.convert(command.getFileData());
       for (Resource resource : resources) {
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/RestOperationErrorHandler.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/RestOperationErrorHandler.java
new file mode 100644
index 0000000..676030b
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/shell/RestOperationErrorHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.web.shell;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.client.DefaultResponseErrorHandler;
+
+import org.apache.geode.management.internal.cli.shell.Gfsh;
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.NotAuthorizedException;
+
+public class RestOperationErrorHandler extends DefaultResponseErrorHandler {
+  private final Gfsh gfsh;
+
+  RestOperationErrorHandler(Gfsh gfsh) {
+    this.gfsh = gfsh;
+  }
+
+  public RestOperationErrorHandler() {
+    this(null);
+  }
+
+  @Override
+  public void handleError(final ClientHttpResponse response) throws IOException {
+    String body = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8);
+    final String message = String.format("The HTTP request failed with: %1$d - %2$s",
+        response.getRawStatusCode(), body);
+
+    if (gfsh != null && gfsh.getDebug()) {
+      gfsh.logSevere(body, null);
+    }
+
+    if (response.getRawStatusCode() == 401) {
+      throw new AuthenticationFailedException(message);
+    } else if (response.getRawStatusCode() == 403) {
+      throw new NotAuthorizedException(message);
+    } else {
+      throw new RuntimeException(message);
+    }
+
+  }
+}
diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshExecution.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshExecution.java
index af23cc0..23d83e6 100644
--- a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshExecution.java
+++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshExecution.java
@@ -14,9 +14,13 @@
  */
 package org.apache.geode.test.junit.rules.gfsh;
 
+import static java.util.stream.Collectors.joining;
+
 import java.io.File;
 import java.util.List;
 
+import org.apache.commons.lang.SystemUtils;
+
 public class GfshExecution {
   private final Process process;
   private final File workingDir;
@@ -36,6 +40,10 @@ public class GfshExecution {
     return processLogger.getStdErrLines();
   }
 
+  public String getStdErrText() {
+    return getStdErrLines().stream().collect(joining(SystemUtils.LINE_SEPARATOR));
+  }
+
   /**
    * Note that this is the working directory of gfsh itself. If your script started a server or
    * locator, this will be the parent directory of that member's working directory.
diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshRule.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshRule.java
index 595f9fd..68baaf5 100644
--- a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshRule.java
+++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/gfsh/GfshRule.java
@@ -89,7 +89,8 @@ public class GfshRule extends ExternalResource {
   public GfshExecution execute(GfshScript gfshScript) {
     GfshExecution gfshExecution;
     try {
-      File workingDir = temporaryFolder.newFolder(gfshScript.getName());
+      File workingDir = new File(temporaryFolder.getRoot(), gfshScript.getName());
+      workingDir.mkdirs();
       Process process = toProcessBuilder(gfshScript, gfsh, workingDir).start();
       gfshExecution = new GfshExecution(process, workingDir);
       gfshExecutions.add(gfshExecution);
diff --git a/geode-web/src/test/java/org/apache/geode/management/internal/web/shell/HttpOperationInvokerSecurityTest.java b/geode-web/src/test/java/org/apache/geode/management/internal/web/shell/HttpOperationInvokerSecurityTest.java
index a610676..5e74033 100644
--- a/geode-web/src/test/java/org/apache/geode/management/internal/web/shell/HttpOperationInvokerSecurityTest.java
+++ b/geode-web/src/test/java/org/apache/geode/management/internal/web/shell/HttpOperationInvokerSecurityTest.java
@@ -55,7 +55,7 @@ public class HttpOperationInvokerSecurityTest {
   private static CommandRequest request;
 
   @Test
-  public void performBeanOperationsNoAuthorizationCheck() throws Exception {
+  public void performBeanOperationsHasAuthorizationCheck() throws Exception {
     gfsh.secureConnectAndVerify(locator.getHttpPort(), GfshShellConnectionRule.PortType.http,
         "test", "test");
     invoker = (HttpOperationInvoker) gfsh.getGfsh().getOperationInvoker();
@@ -70,11 +70,11 @@ public class HttpOperationInvokerSecurityTest {
     DistributedSystemMXBean bean = invoker.getDistributedSystemMXBean();
     assertThat(bean).isInstanceOf(DistributedSystemMXBean.class);
 
-    String[] gatewayReceivers =
-        (String[]) invoker.invoke(ManagementConstants.OBJECTNAME__DISTRIBUTEDSYSTEM_MXBEAN,
-            "listGatewayReceivers", new Object[0], new String[0]);
+    assertThatThrownBy(
+        () -> invoker.invoke(ManagementConstants.OBJECTNAME__DISTRIBUTEDSYSTEM_MXBEAN,
+            "listGatewayReceivers", new Object[0], new String[0]))
+                .isInstanceOf(NotAuthorizedException.class);
 
-    assertThat(gatewayReceivers).isEmpty();
     ObjectName objectName = ObjectName.getInstance("GemFire:type=Member,*");
     QueryExp query = Query.eq(Query.attr("Name"), Query.value("mock"));
 
@@ -94,7 +94,6 @@ public class HttpOperationInvokerSecurityTest {
     when(request.getHttpRequestUrl(anyString())).thenCallRealMethod();
     when(request.getUserInput()).thenReturn("list members");
 
-
     assertThatThrownBy(() -> invoker.processCommand(request))
         .isInstanceOf(NotAuthorizedException.class);
     gfsh.disconnect();

-- 
To stop receiving notification emails like this one, please contact
['"commits@geode.apache.org" <co...@geode.apache.org>'].