Skip to content

Commit

Permalink
GEODE-6991: Create alter query-service gfsh command (apache#4324)
Browse files Browse the repository at this point in the history
* GEODE-6991: Create alter query-service gfsh command

Co-authored-by: Benjamin Ross <[email protected]>
Co-authored-by: Donal Evans <[email protected]>
  • Loading branch information
2 people authored and gesterzhou committed Nov 21, 2019
1 parent 2e04da3 commit 12f68c8
Show file tree
Hide file tree
Showing 15 changed files with 1,380 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ javadoc/org/apache/geode/cache/query/RegionNotFoundException.html
javadoc/org/apache/geode/cache/query/SelectResults.html
javadoc/org/apache/geode/cache/query/Struct.html
javadoc/org/apache/geode/cache/query/TypeMismatchException.html
javadoc/org/apache/geode/cache/query/management/configuration/QueryConfigService.MethodAuthorizer.Parameter.html
javadoc/org/apache/geode/cache/query/management/configuration/QueryConfigService.MethodAuthorizer.html
javadoc/org/apache/geode/cache/query/management/configuration/QueryConfigService.html
javadoc/org/apache/geode/cache/query/management/configuration/package-frame.html
javadoc/org/apache/geode/cache/query/management/configuration/package-summary.html
javadoc/org/apache/geode/cache/query/management/configuration/package-tree.html
javadoc/org/apache/geode/cache/query/package-frame.html
javadoc/org/apache/geode/cache/query/package-summary.html
javadoc/org/apache/geode/cache/query/package-tree.html
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
* 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.management.internal.cli.commands.AlterQueryServiceCommand.AUTHORIZER_PARAMETERS;
import static org.apache.geode.management.internal.cli.commands.AlterQueryServiceCommand.COMMAND_NAME;
import static org.apache.geode.management.internal.cli.commands.AlterQueryServiceCommand.METHOD_AUTHORIZER_NAME;
import static org.apache.geode.management.internal.cli.commands.AlterQueryServiceCommand.NO_MEMBERS_FOUND_MESSAGE;
import static org.apache.geode.management.internal.cli.commands.AlterQueryServiceCommand.SPLITTING_REGEX;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.IntStream;

import junitparams.JUnitParamsRunner;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;

import org.apache.geode.cache.query.internal.QueryConfigurationService;
import org.apache.geode.cache.query.security.JavaBeanAccessorMethodAuthorizer;
import org.apache.geode.cache.query.security.MethodInvocationAuthorizer;
import org.apache.geode.cache.query.security.RegExMethodAuthorizer;
import org.apache.geode.cache.query.security.RestrictedMethodAuthorizer;
import org.apache.geode.cache.query.security.UnrestrictedMethodAuthorizer;
import org.apache.geode.examples.SimpleSecurityManager;
import org.apache.geode.management.internal.cli.util.TestMethodAuthorizer;
import org.apache.geode.test.compiler.ClassBuilder;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.rules.GfshCommandRule;
import org.apache.geode.test.junit.rules.VMProvider;

@RunWith(JUnitParamsRunner.class)
public class AlterQueryServiceCommandWithSecurityDUnitTest {
private static final Class<RestrictedMethodAuthorizer> DEFAULT_AUTHORIZER_CLASS =
RestrictedMethodAuthorizer.class;
private static final String JAVA_BEAN_AUTHORIZER_PARAMS = "java.lang;java.util";
// A regex containing a comma to confirm that the '--authorizer-parameter' option is parsed
// correctly
private static final String REGEX_AUTHORIZER_PARAMETERS =
"^java.util.List..{4,8}$;^java.util.Set..{4,8}$";
private static final String USER_AUTHORIZER_PARAMETERS = "param1;param2;param3";
private static final String TEST_AUTHORIZER_TXT = "TestMethodAuthorizer.txt";
private final int serversToStart = 2;

@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();

@Rule
public ClusterStartupRule cluster = new ClusterStartupRule();

@Rule
public GfshCommandRule gfsh = new GfshCommandRule();

private static MemberVM locator;
private static List<MemberVM> servers = new ArrayList<>();

@Before
public void setUp() throws Exception {
locator = cluster.startLocatorVM(0, l -> l.withSecurityManager(SimpleSecurityManager.class));
int locatorPort = locator.getPort();
IntStream.range(0, serversToStart).forEach(
i -> servers.add(i, cluster.startServerVM(i + 1, s -> s.withConnectionToLocator(locatorPort)
.withCredential("clusterManage", "clusterManage"))));
gfsh.connectAndVerify(locator);
}

// This test verifies that the cluster starts with the default method authorizer configured, then
// changes the currently configured authorizer to each of the out-of-the-box authorizers.
@Test
public void alterQueryServiceCommandUpdatesMethodAuthorizerClass() {
verifyCurrentAuthorizerClass(DEFAULT_AUTHORIZER_CLASS);

executeAndAssertGfshCommandIsSuccessful(UnrestrictedMethodAuthorizer.class, "");

executeAndAssertGfshCommandIsSuccessful(JavaBeanAccessorMethodAuthorizer.class,
JAVA_BEAN_AUTHORIZER_PARAMS);

executeAndAssertGfshCommandIsSuccessful(RegExMethodAuthorizer.class,
REGEX_AUTHORIZER_PARAMETERS);

executeAndAssertGfshCommandIsSuccessful(RestrictedMethodAuthorizer.class, "");
}

@Test
public void alterQueryServiceCommandUpdatesMethodAuthorizerParameters() {
gfsh.executeAndAssertThat(COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "="
+ JavaBeanAccessorMethodAuthorizer.class.getName() + " --" + AUTHORIZER_PARAMETERS + "="
+ JAVA_BEAN_AUTHORIZER_PARAMS).statusIsSuccess().hasTableSection()
.hasRowSize(serversToStart);

servers.forEach(server -> server.invoke(() -> {
JavaBeanAccessorMethodAuthorizer methodAuthorizer = (JavaBeanAccessorMethodAuthorizer) Objects
.requireNonNull(ClusterStartupRule.getCache()).getService(QueryConfigurationService.class)
.getMethodAuthorizer();

Set<String> expectedParameters =
new HashSet<>(Arrays.asList(JAVA_BEAN_AUTHORIZER_PARAMS.split(SPLITTING_REGEX)));
assertThat(methodAuthorizer.getAllowedPackages()).isEqualTo(expectedParameters);
}));

gfsh.executeAndAssertThat(
COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "=" + RegExMethodAuthorizer.class.getName()
+ " --" + AUTHORIZER_PARAMETERS + "=" + REGEX_AUTHORIZER_PARAMETERS)
.statusIsSuccess().hasTableSection().hasRowSize(serversToStart);

servers.forEach(server -> server.invoke(() -> {
RegExMethodAuthorizer methodAuthorizer = (RegExMethodAuthorizer) Objects
.requireNonNull(ClusterStartupRule.getCache()).getService(QueryConfigurationService.class)
.getMethodAuthorizer();

Set<String> expectedParameters =
new HashSet<>(Arrays.asList(REGEX_AUTHORIZER_PARAMETERS.split(SPLITTING_REGEX)));
assertThat(methodAuthorizer.getAllowedPatterns()).isEqualTo(expectedParameters);
}));
}

@Test
public void alterQueryServiceCommandWithUserSpecifiedMethodAuthorizerUpdatesMethodAuthorizer()
throws IOException {
verifyCurrentAuthorizerClass(DEFAULT_AUTHORIZER_CLASS);

Class<TestMethodAuthorizer> authorizerClass = TestMethodAuthorizer.class;
String authorizerName = authorizerClass.getName();

String classContent =
new String(Files.readAllBytes(Paths.get(getFilePath(TEST_AUTHORIZER_TXT))));
String jarFileName = "testJar.jar";
File jarFile = tempFolder.newFile(jarFileName);
new ClassBuilder().writeJarFromContent(authorizerName, classContent, jarFile);

gfsh.executeAndAssertThat("deploy --jars=" + jarFile.getAbsolutePath()).statusIsSuccess();

executeAndAssertGfshCommandIsSuccessful(TestMethodAuthorizer.class, USER_AUTHORIZER_PARAMETERS);

servers.forEach(server -> server.invoke(() -> {
TestMethodAuthorizer methodAuthorizer = (TestMethodAuthorizer) Objects
.requireNonNull(ClusterStartupRule.getCache()).getService(QueryConfigurationService.class)
.getMethodAuthorizer();

Set<String> expectedParameters =
new HashSet<>(Arrays.asList(USER_AUTHORIZER_PARAMETERS.split(SPLITTING_REGEX)));
assertThat(methodAuthorizer.getParameters()).isEqualTo(expectedParameters);
}));
}

@Test
public void alterQueryServiceCommandUpdatesClusterConfigWhenNoMembersAreFound() {
servers.forEach(VMProvider::stop);

Class<JavaBeanAccessorMethodAuthorizer> authorizerClass =
JavaBeanAccessorMethodAuthorizer.class;
String authorizerName = authorizerClass.getName();

gfsh.executeAndAssertThat(COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "=" + authorizerName
+ " --" + AUTHORIZER_PARAMETERS + "=" + JAVA_BEAN_AUTHORIZER_PARAMS)
.statusIsSuccess().hasInfoSection().hasLines().contains(NO_MEMBERS_FOUND_MESSAGE);

startServerAndVerifyJavaBeanAccessorMethodAuthorizer();
}

@Test
public void alterQueryServiceCommandDoesNotUpdateClusterConfigWhenFunctionExecutionFailsOnAllMembers() {
String authorizerName = "badAuthorizerName";

gfsh.executeAndAssertThat(COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "=" + authorizerName
+ " --" + AUTHORIZER_PARAMETERS + "=" + JAVA_BEAN_AUTHORIZER_PARAMS)
.statusIsError();

locator.invoke(() -> assertThat(Objects.requireNonNull(ClusterStartupRule.getLocator())
.getConfigurationPersistenceService().getCacheConfig(null)).isNull());
}

private void executeAndAssertGfshCommandIsSuccessful(Class authorizerClass,
String authorizerParameters) {
gfsh.executeAndAssertThat(
COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "=" + authorizerClass.getName()
+ " --" + AUTHORIZER_PARAMETERS + "=" + authorizerParameters)
.statusIsSuccess()
.hasTableSection().hasRowSize(serversToStart);

verifyCurrentAuthorizerClass(authorizerClass);
}

private void startServerAndVerifyJavaBeanAccessorMethodAuthorizer() {
int locatorPort = locator.getPort();
MemberVM newServer = cluster.startServerVM(servers.size() + 1, s -> s
.withConnectionToLocator(locatorPort).withCredential("clusterManage", "clusterManage"));
newServer.invoke(() -> {
JavaBeanAccessorMethodAuthorizer methodAuthorizer =
(JavaBeanAccessorMethodAuthorizer) Objects.requireNonNull(ClusterStartupRule.getCache())
.getService(QueryConfigurationService.class).getMethodAuthorizer();
assertThat(methodAuthorizer.getClass()).isEqualTo(JavaBeanAccessorMethodAuthorizer.class);

Set<String> expectedParameters =
new HashSet<>(Arrays.asList(JAVA_BEAN_AUTHORIZER_PARAMS.split(SPLITTING_REGEX)));
assertThat(methodAuthorizer.getAllowedPackages()).isEqualTo(expectedParameters);
});
}

private void verifyCurrentAuthorizerClass(Class authorizerClass) {
servers.forEach(server -> server.invoke(() -> {
MethodInvocationAuthorizer methodAuthorizer = Objects
.requireNonNull(ClusterStartupRule.getCache()).getService(QueryConfigurationService.class)
.getMethodAuthorizer();
assertThat(methodAuthorizer.getClass()).isEqualTo(authorizerClass);
}));
}

private String getFilePath(String fileName) throws IOException {
URL url = getClass().getResource(fileName);
File textFile = this.tempFolder.newFile(fileName);
FileUtils.copyURLToFile(url, textFile);

return textFile.getAbsolutePath();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.management.internal.cli.commands.AlterQueryServiceCommand.COMMAND_NAME;
import static org.apache.geode.management.internal.cli.commands.AlterQueryServiceCommand.METHOD_AUTHORIZER_NAME;
import static org.apache.geode.management.internal.cli.functions.AlterQueryServiceFunction.SECURITY_NOT_ENABLED_MESSAGE;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import org.apache.geode.cache.query.internal.QueryConfigurationService;
import org.apache.geode.cache.query.internal.QueryConfigurationServiceImpl;
import org.apache.geode.cache.query.security.MethodInvocationAuthorizer;
import org.apache.geode.cache.query.security.UnrestrictedMethodAuthorizer;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.rules.GfshCommandRule;

public class AlterQueryServiceCommandWithoutSecurityDUnitTest {
private final int serversToStart = 2;

@Rule
public ClusterStartupRule cluster = new ClusterStartupRule();

@Rule
public GfshCommandRule gfsh = new GfshCommandRule();

private static MemberVM locator;
private static List<MemberVM> servers = new ArrayList<>();

@Before
public void setUp() throws Exception {
locator = cluster.startLocatorVM(0);
IntStream.range(0, serversToStart).forEach(
i -> servers.add(i, cluster.startServerVM(i + 1, locator.getPort())));
gfsh.connectAndVerify(locator);
}

@Test
public void alterQueryServiceCommandDoesNotUpdateMethodAuthorizerWhenSecurityIsNotEnabled() {
verifyNoOpAuthorizer();

String authorizerName = UnrestrictedMethodAuthorizer.class.getName();
gfsh.executeAndAssertThat(COMMAND_NAME + " --" + METHOD_AUTHORIZER_NAME + "=" + authorizerName)
.statusIsError().containsOutput(SECURITY_NOT_ENABLED_MESSAGE);

verifyNoOpAuthorizer();
}

private void verifyNoOpAuthorizer() {
servers.forEach(s -> s.invoke(() -> {
MethodInvocationAuthorizer methodAuthorizer = Objects
.requireNonNull(ClusterStartupRule.getCache()).getService(QueryConfigurationService.class)
.getMethodAuthorizer();
assertThat(methodAuthorizer).isEqualTo(QueryConfigurationServiceImpl.getNoOpAuthorizer());
}));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.util;

import java.lang.reflect.Method;
import java.util.Set;

import org.apache.geode.cache.Cache;
import org.apache.geode.cache.query.security.MethodInvocationAuthorizer;

// Any changes to this class should be reflected in TestMethodAuthorizer.txt
public class TestMethodAuthorizer implements MethodInvocationAuthorizer {

private Set<String> parameters;

public Set<String> getParameters() {
return this.parameters;
}

@Override
public boolean authorize(Method method, Object target) {
return parameters.contains(method.getName());
}

@Override
public void initialize(Cache cache, Set<String> parameters) {
// To allow an exception to be thrown from a mock
cache.isClosed();
this.parameters = parameters;
}
}
Loading

0 comments on commit 12f68c8

Please sign in to comment.