/* * 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.security; import org.apache.geode.DataSerializable; import org.apache.geode.Instantiator; import org.apache.geode.cache.operations.OperationContext.OperationCode; import org.apache.geode.distributed.ConfigurationProperties; import org.apache.geode.internal.AvailablePortHelper; import org.apache.geode.internal.security.FilterPostAuthorization; import org.apache.geode.internal.security.FilterPreAuthorization; import org.apache.geode.internal.security.ObjectWithAuthz; import org.apache.geode.security.generator.CredentialGenerator; import org.apache.geode.security.generator.DummyAuthzCredentialGenerator; import org.apache.geode.security.generator.DummyCredentialGenerator; import org.apache.geode.security.templates.UserPasswordAuthInit; import org.apache.geode.test.junit.categories.DistributedTest; import org.apache.geode.test.junit.categories.FlakyTest; import org.apache.geode.test.junit.categories.SecurityTest; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Random; import static org.apache.geode.security.SecurityTestUtils.closeCache; import static org.apache.geode.security.SecurityTestUtils.getLocatorPort; /** * Tests for authorization callback that modify objects and callbacks from client to server. * * The scheme of these tests is thus: A user name terminating in integer 'i' can get values * terminating with 'i', '2*i', '3*i' and so on. So 'gemfire1' can get 'value1', 'value2', ...; * 'gemfire2' can get 'value2', 'value4', ... and so on. On the server side this is done by adding * the index 'i' to the object in the pre-processing phase, and checked by comparing against the * user name index during the post-processing phase. * * This enables testing of object and callback modification both in pre-processing and * post-processing phases. * * @since GemFire 5.5 */ @Category({DistributedTest.class, SecurityTest.class}) public class ClientAuthzObjectModDUnitTest extends ClientAuthorizationTestCase { private static final String preAccessor = FilterPreAuthorization.class.getName() + ".create"; private static final String postAccessor = FilterPostAuthorization.class.getName() + ".create"; @Override protected final void postSetUpClientAuthorizationTestBase() throws Exception { // required by FilterPreAuthorization and FilterPostAuthorization. Normally, // this would be automatically registered in the static initializer, but with dunit // a previous test may have already loaded these classes. We clear the instantiators // between each test. server1.invoke("registerInstantiator", () -> Instantiator.register(new MyInstantiator(), false)); server2.invoke("registerInstantiator", () -> Instantiator.register(new MyInstantiator(), false)); } @Category(FlakyTest.class) // GEODE-2071: failed with ForcedDisconnectException @Test public void testAllOpsObjectModWithFailover() throws Exception { OperationWithAction[] allOps = allOps(); TestPostCredentialGenerator tgen = new TestPostCredentialGenerator(); CredentialGenerator credentialGenerator = new DummyCredentialGenerator(); credentialGenerator.init(); Properties extraProps = credentialGenerator.getSystemProperties(); Properties javaProps = credentialGenerator.getJavaProperties(); String authInit = credentialGenerator.getAuthInit(); String authenticator = credentialGenerator.getAuthenticator(); System.out.println("testPutsGetsObjectModWithFailover: Using authinit: " + authInit); System.out.println("testPutsGetsObjectModWithFailover: Using authenticator: " + authenticator); System.out .println("testPutsGetsObjectModWithFailover: Using pre-operation accessor: " + preAccessor); System.out.println( "testPutsGetsObjectModWithFailover: Using post-operation accessor: " + postAccessor); // Start servers with all required properties Properties serverProps = buildProperties(authenticator, extraProps, preAccessor, postAccessor); // Get ports for the servers int[] portsList = AvailablePortHelper.getRandomAvailableTCPPorts(2); int port1 = portsList[0]; int port2 = portsList[1]; // Perform all the ops on the clients List<OperationWithAction> opBlock = new ArrayList(); Random rnd = new Random(); for (int opNum = 0; opNum < allOps.length; ++opNum) { // Start client with valid credentials as specified in OperationWithAction OperationWithAction currentOp = allOps[opNum]; if (currentOp.equals(OperationWithAction.OPBLOCK_END) || currentOp.equals(OperationWithAction.OPBLOCK_NO_FAILOVER)) { // End of current operation block; execute all the operations on the servers with failover if (opBlock.size() > 0) { // Start the first server and execute the operation block server1.invoke("createCacheServer", () -> createCacheServer(getLocatorPort(), port1, serverProps, javaProps)); server2.invoke("closeCache", () -> closeCache()); executeOpBlock(opBlock, port1, port2, authInit, extraProps, null, tgen, rnd); if (!currentOp.equals(OperationWithAction.OPBLOCK_NO_FAILOVER)) { // Failover to the second server and run the block again server2.invoke("createCacheServer", () -> createCacheServer(getLocatorPort(), port2, serverProps, javaProps)); server1.invoke("closeCache", () -> closeCache()); executeOpBlock(opBlock, port1, port2, authInit, extraProps, null, tgen, rnd); } opBlock.clear(); } } else { currentOp.setOpNum(opNum); opBlock.add(currentOp); } } } private OperationWithAction[] allOps() { return new OperationWithAction[] { // Perform CREATE and verify with GET new OperationWithAction(OperationCode.PUT, 1, OpFlags.NONE, 8), // For second client connect with valid credentials for key2, key4, key6, key8 and check // that other KEYS are not accessible new OperationWithAction(OperationCode.GET, 2, OpFlags.CHECK_NOKEY, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.GET, 2, OpFlags.CHECK_NOKEY | OpFlags.USE_OLDCONN | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 2, 4, 6}), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.GET, 3, OpFlags.CHECK_NOKEY, new int[] {2, 5}), new OperationWithAction(OperationCode.GET, 3, OpFlags.CHECK_NOKEY | OpFlags.USE_OLDCONN | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 1, 3, 4, 6, 7}), // OPBLOCK_END indicates end of an operation block that needs to be executed on each server // when doing failover OperationWithAction.OPBLOCK_END, // Perform UPDATE and verify with GET new OperationWithAction(OperationCode.PUT, 1, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, 8), // For second client check that key2, key4, key6, key8 are accessible but others are not new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_NOKEY | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 2, 4, 6}), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {2, 5}), new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_NOKEY | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 1, 3, 4, 6, 7}), OperationWithAction.OPBLOCK_END, // Perform UPDATE and verify with GET_ALL new OperationWithAction(OperationCode.PUT, 1, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, 8), // For second client check that key2, key4, key6, key8 are accessible but others are not; // getAll test in doOp uses a combination of local entries and remote fetches new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.USE_ALL_KEYS, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.USE_ALL_KEYS | OpFlags.CHECK_NOKEY | OpFlags.CHECK_FAIL, new int[] {0, 2, 4, 6}), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.USE_ALL_KEYS, new int[] {2, 5}), new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.USE_ALL_KEYS | OpFlags.CHECK_NOKEY | OpFlags.CHECK_FAIL, new int[] {0, 1, 3, 4, 6, 7}), // locally destroy the KEYS to also test create after failover new OperationWithAction(OperationCode.DESTROY, 1, OpFlags.USE_OLDCONN | OpFlags.LOCAL_OP, 8), OperationWithAction.OPBLOCK_END, // Perform PUTALL and verify with GET new OperationWithAction(OperationCode.PUTALL, 1, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, 8), // For second client check that key2, key4, key6, key8 are accessible but others are not new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.GET, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_NOKEY | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 2, 4, 6}), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {2, 5}), new OperationWithAction(OperationCode.GET, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_NOKEY | OpFlags.CHECK_NOTAUTHZ, new int[] {0, 1, 3, 4, 6, 7}), OperationWithAction.OPBLOCK_END, // Test UPDATE and verify with a QUERY new OperationWithAction(OperationCode.PUT, 1, OpFlags.USE_OLDCONN, 8), // For second client check that key2, key4, key6, key8 are accessible but others are not new OperationWithAction(OperationCode.QUERY, 2, OpFlags.USE_OLDCONN, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.QUERY, 2, OpFlags.USE_OLDCONN | OpFlags.CHECK_FAIL, new int[] {0, 2, 4, 6}), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.QUERY, 3, OpFlags.USE_OLDCONN, new int[] {2, 5}), new OperationWithAction(OperationCode.QUERY, 3, OpFlags.USE_OLDCONN | OpFlags.CHECK_FAIL, new int[] {0, 1, 3, 4, 6, 7}), OperationWithAction.OPBLOCK_END, // Test UPDATE and verify with a EXECUTE_CQ initial results new OperationWithAction(OperationCode.PUT, 1, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, 8), // For second client check that key2, key4, key6, key8 are accessible but others are not new OperationWithAction(OperationCode.EXECUTE_CQ, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {1, 3, 5, 7}), new OperationWithAction(OperationCode.CLOSE_CQ, 2, OpFlags.USE_OLDCONN, 1), new OperationWithAction(OperationCode.EXECUTE_CQ, 2, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_FAIL, new int[] {0, 2, 4, 6}), new OperationWithAction(OperationCode.CLOSE_CQ, 2, OpFlags.USE_OLDCONN, 1), // For third client check that key3, key6 are accessible but others are not new OperationWithAction(OperationCode.EXECUTE_CQ, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL, new int[] {2, 5}), new OperationWithAction(OperationCode.CLOSE_CQ, 3, OpFlags.USE_OLDCONN, 1), new OperationWithAction(OperationCode.EXECUTE_CQ, 3, OpFlags.USE_OLDCONN | OpFlags.USE_NEWVAL | OpFlags.CHECK_FAIL, new int[] {0, 1, 3, 4, 6, 7}), new OperationWithAction(OperationCode.CLOSE_CQ, 3, OpFlags.USE_OLDCONN, 1), OperationWithAction.OPBLOCK_END}; } private Properties buildProperties(final String authenticator, final Properties extraProps, final String preAccessor, final String postAccessor) { Properties authProps = new Properties(); if (authenticator != null) { authProps.setProperty(ConfigurationProperties.SECURITY_CLIENT_AUTHENTICATOR, authenticator); } if (preAccessor != null) { authProps.setProperty(ConfigurationProperties.SECURITY_CLIENT_ACCESSOR, preAccessor); } if (postAccessor != null) { authProps.setProperty(ConfigurationProperties.SECURITY_CLIENT_ACCESSOR_PP, postAccessor); } if (extraProps != null) { authProps.putAll(extraProps); } return authProps; } private static class MyInstantiator extends Instantiator { public MyInstantiator() { this(ObjectWithAuthz.class, ObjectWithAuthz.CLASSID); } public MyInstantiator(final Class clazz, final int classId) { super(clazz, classId); } @Override public DataSerializable newInstance() { return new ObjectWithAuthz(); } } private static class TestPostCredentialGenerator implements TestCredentialGenerator { public TestPostCredentialGenerator() {} @Override public Properties getAllowedCredentials(final OperationCode[] opCodes, final String[] regionNames, final int[] keyIndices, final int num) { int userIndex = 1; byte role = DummyAuthzCredentialGenerator.getRequiredRole(opCodes); if (role == DummyAuthzCredentialGenerator.READER_ROLE) { userIndex = keyIndices[0] + 1; } Properties props = new Properties(); props.setProperty(UserPasswordAuthInit.USER_NAME, "user" + userIndex); props.setProperty(UserPasswordAuthInit.PASSWORD, "user" + userIndex); return props; } @Override public Properties getDisallowedCredentials(final OperationCode[] opCodes, final String[] regionNames, final int[] keyIndices, final int num) { int userIndex = 0; for (int index = 0; index < keyIndices.length; ++index) { if (keyIndices[index] != index) { userIndex = index + 1; break; } } Properties props = new Properties(); props.setProperty(UserPasswordAuthInit.USER_NAME, "gemfire" + userIndex); props.setProperty(UserPasswordAuthInit.PASSWORD, "gemfire" + userIndex); return props; } @Override public CredentialGenerator getCredentialGenerator() { return null; } } }