You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ji...@apache.org on 2021/08/23 16:11:32 UTC

[geode] branch expireAuthentication updated (e4f8e8e -> 5bbd1c9)

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

jinmeiliao pushed a change to branch expireAuthentication
in repository https://gitbox.apache.org/repos/asf/geode.git.


 discard e4f8e8e  GEODE-9521: Add test to cover multi-servers scenario for re-authentic… (#6782)
    omit 6c1bf75  GEODE-9460: Add testing for mutli-user scenarios (#6755)
    omit 4be645b  GEODE-9456, GEODE-9452: Authentication Expiration (#6721)
     add 76695e4  GEODE-8980: bump deps (#6734)
     add 5e9b368  GEODE-4181: Reduce ChildVM command line length (#6763)
     add fc6ef7c  GEODE-9525: Add Radish benchmarks to CI (#6775)
     add 1969f39  GEODE-9514: optimize how Coder reads and writes "int" and "long" values (#6765)
     add 9fcbb74  GEODE-9525: Add Radish benchmarks to CI - with fix (#6780)
     add 2752849  GEODE-9523: Fix invalid path in ContainerInstall (#6774)
     add 0d1954b  GEODE-9526: Fix path computation in DiskStore test (#6777)
     add 9ff39d12 GEODE-9529: fix release scripts (benchmark baseline, docker) (#6779)
     add dc18a2a  GEODE-9525: Add Radish benchmarks to CI - fix 2 (#6784)
     new c79b756  GEODE-9456, GEODE-9452: Authentication Expiration (#6721)
     new 8507bed  GEODE-9460: Add testing for mutli-user scenarios (#6755)
     new 5bbd1c9  GEODE-9521: Add test to cover multi-servers scenario for re-authentic… (#6782)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (e4f8e8e)
            \
             N -- N -- N   refs/heads/expireAuthentication (5bbd1c9)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../src/test/resources/expected-pom.xml            |  44 ++--
 .../gradle/plugins/DependencyConstraints.groovy    |  20 +-
 ci/pipelines/geode-build/jinja.template.yml        |   8 +-
 ci/pipelines/shared/jinja.variables.yml            |  13 +-
 dev-tools/dependencies/bump.sh                     |   2 +-
 dev-tools/release/promote_rc.sh                    |  22 +-
 docker/Dockerfile                                  |  15 +-
 .../apache/geode/redis/internal/netty/Coder.java   | 271 +++++++++++++++++++--
 .../geode/redis/internal/netty/CoderTest.java      |  69 ++++++
 .../geode/session/tests/ContainerInstall.java      |   4 +-
 .../apache/geode/session/tests/TomcatInstall.java  |   2 +-
 .../session/tests/GenericAppServerInstall.java     |   2 +-
 .../integrationTest/resources/assembly_content.txt |  34 +--
 .../resources/dependency_classpath.txt             |  28 +--
 geode-assembly/src/main/dist/LICENSE               |   2 +-
 geode-connectors/build.gradle                      |   2 +-
 .../cli/CreateDataSourceCommandDUnitTest.java      |   2 +-
 .../cli/DeregisterDriverCommandDUnitTest.java      |   2 +-
 .../internal/cli/ListDriversCommandDUnitTest.java  |   4 +-
 .../cli/RegisterDriverCommandDUnitTest.java        |   2 +-
 .../geode/test/dunit/internal/ProcessManager.java  |  47 +++-
 .../cli/commands/DiskStoreCommandsDUnitTest.java   |   4 +-
 22 files changed, 480 insertions(+), 119 deletions(-)

[geode] 02/03: GEODE-9460: Add testing for mutli-user scenarios (#6755)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8507bed1c7b182791c50eb5b96029d0f6a29b9d9
Author: Joris Melchior <jo...@gmail.com>
AuthorDate: Fri Aug 20 12:43:30 2021 -0400

    GEODE-9460: Add testing for mutli-user scenarios (#6755)
    
    Co-Authored-By Jinmei Liao <ji...@vmware.com>
---
 .../geode/security/AuthExpirationDUnitTest.java    | 72 ++++++++++++++++++++--
 .../geode/security/ExpirableSecurityManager.java   | 19 +++++-
 .../security/UpdatableUserAuthInitialize.java      |  1 +
 3 files changed, 85 insertions(+), 7 deletions(-)

diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
index 117cc77..0aae286 100644
--- a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
+++ b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
@@ -15,12 +15,14 @@
 package org.apache.geode.security;
 
 import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTH_INIT;
-import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
 import static org.apache.geode.test.version.VersionManager.CURRENT_VERSION;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
 
 import org.junit.After;
 import org.junit.Rule;
@@ -31,6 +33,7 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionService;
 import org.apache.geode.cache.RegionShortcut;
 import org.apache.geode.cache.client.ClientCache;
 import org.apache.geode.cache.client.ClientRegionFactory;
@@ -45,6 +48,8 @@ import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactor
 @RunWith(Parameterized.class)
 @Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
 public class AuthExpirationDUnitTest {
+  static RegionService regionService0;
+  static RegionService regionService1;
 
   @Parameterized.Parameter
   public String clientVersion;
@@ -58,13 +63,12 @@ public class AuthExpirationDUnitTest {
   @Rule
   public ClusterStartupRule lsRule = new ClusterStartupRule();
 
-
   @Rule
   public RestoreSystemProperties restore = new RestoreSystemProperties();
 
   @Rule
   public ServerStarterRule server = new ServerStarterRule()
-      .withProperty(SECURITY_MANAGER, ExpirableSecurityManager.class.getName())
+      .withSecurityManager(ExpirableSecurityManager.class)
       .withRegion(RegionShortcut.REPLICATE, "region");
 
   @After
@@ -85,9 +89,10 @@ public class AuthExpirationDUnitTest {
     clientVM.invoke(() -> {
       ClientCache clientCache = ClusterStartupRule.getClientCache();
       UpdatableUserAuthInitialize.setUser("user1");
-      ClientRegionFactory clientRegionFactory =
+      assert clientCache != null;
+      ClientRegionFactory<Object, Object> clientRegionFactory =
           clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY);
-      Region region = clientRegionFactory.create("region");
+      Region<Object, Object> region = clientRegionFactory.create("region");
       region.put(0, "value0");
     });
 
@@ -98,7 +103,8 @@ public class AuthExpirationDUnitTest {
     clientVM.invoke(() -> {
       UpdatableUserAuthInitialize.setUser("user2");
       ClientCache clientCache = ClusterStartupRule.getClientCache();
-      Region region = clientCache.getRegion("region");
+      assert clientCache != null;
+      Region<Object, Object> region = clientCache.getRegion("region");
       region.put(1, "value1");
     });
 
@@ -109,4 +115,58 @@ public class AuthExpirationDUnitTest {
     assertThat(region.size()).isEqualTo(2);
   }
 
+  @Test
+  public void userShouldReAuthenticateWhenCredentialExpiredAndOperationSucceed() throws Exception {
+    int serverPort = server.getPort();
+    ClientVM clientVM = lsRule.startClientVM(0, clientVersion,
+        c -> c.withMultiUser(true)
+            .withProperty(SECURITY_CLIENT_AUTH_INIT, UpdatableUserAuthInitialize.class.getName())
+            .withPoolSubscription(true)
+            .withServerConnection(serverPort));
+
+    clientVM.invoke(() -> {
+      UpdatableUserAuthInitialize.setUser("serviceUser0");
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      assert clientCache != null;
+      clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY).create("region");
+      Properties userSecurityProperties = new Properties();
+      userSecurityProperties.put(SECURITY_CLIENT_AUTH_INIT,
+          UpdatableUserAuthInitialize.class.getName());
+      regionService0 = clientCache.createAuthenticatedView(userSecurityProperties);
+      Region<Object, Object> region = regionService0.getRegion("/region");
+      region.put(0, "value0");
+
+      UpdatableUserAuthInitialize.setUser("serviceUser1");
+      userSecurityProperties.put(SECURITY_CLIENT_AUTH_INIT,
+          UpdatableUserAuthInitialize.class.getName());
+      regionService1 = clientCache.createAuthenticatedView(userSecurityProperties);
+      region = regionService1.getRegion("/region");
+      region.put(1, "value1");
+    });
+
+    ExpirableSecurityManager.addExpiredUser("serviceUser1");
+
+    clientVM.invoke(() -> {
+      Region<Object, Object> region = regionService1.getRegion("/region");
+      UpdatableUserAuthInitialize.setUser("serviceUser2");
+      region.put(2, "value2");
+
+      region = regionService0.getRegion("/region");
+      region.put(3, "value3");
+      regionService0.close();
+      regionService1.close();
+    });
+
+    Region<Object, Object> region = server.getCache().getRegion("/region");
+    assertThat(ExpirableSecurityManager.getExpiredUsers().size()).isEqualTo(1);
+    assertThat(ExpirableSecurityManager.getExpiredUsers().contains("serviceUser1")).isTrue();
+    Map<Object, List<ResourcePermission>> authorizedOps =
+        ExpirableSecurityManager.getAuthorizedOps();
+    assertThat(authorizedOps.size()).isEqualTo(3);
+    assertThat(authorizedOps.get("serviceUser0").size()).isEqualTo(2);
+    assertThat(authorizedOps.get("serviceUser1").size()).isEqualTo(1);
+    assertThat(authorizedOps.get("serviceUser2").size()).isEqualTo(1);
+    assertThat(region.size()).isEqualTo(4);
+  }
+
 }
diff --git a/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
index fc1021d..5c4d177 100644
--- a/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
+++ b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
@@ -15,6 +15,9 @@
 
 package org.apache.geode.security;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -32,12 +35,21 @@ public class ExpirableSecurityManager extends SimpleSecurityManager {
   // use static field for ease of testing since there is only one instance of this in each VM
   // we only need ConcurrentHashSet here, but map is only construct available in the library
   private static final Set<String> EXPIRED_USERS = ConcurrentHashMap.newKeySet();
+  private static final Map<Object, List<ResourcePermission>> AUTHORIZED_OPS =
+      new ConcurrentHashMap<>();
 
   @Override
   public boolean authorize(Object principal, ResourcePermission permission) {
-    if (EXPIRED_USERS.contains(principal)) {
+    if (EXPIRED_USERS.contains((String) principal)) {
       throw new AuthenticationExpiredException("User authentication expired.");
     }
+    List<ResourcePermission> permissions = AUTHORIZED_OPS.get(principal);
+    if (permissions == null) {
+      permissions = new ArrayList<>();
+    }
+    permissions.add(permission);
+    AUTHORIZED_OPS.put(principal, permissions);
+
     // always authorized
     return true;
   }
@@ -50,7 +62,12 @@ public class ExpirableSecurityManager extends SimpleSecurityManager {
     return EXPIRED_USERS;
   }
 
+  public static Map<Object, List<ResourcePermission>> getAuthorizedOps() {
+    return AUTHORIZED_OPS;
+  }
+
   public static void reset() {
     EXPIRED_USERS.clear();
+    AUTHORIZED_OPS.clear();
   }
 }
diff --git a/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java b/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java
index 0a0e6b4..2b1a0b1 100644
--- a/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java
+++ b/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java
@@ -37,6 +37,7 @@ public class UpdatableUserAuthInitialize implements AuthInitialize {
     Properties credentials = new Properties();
     credentials.put("security-username", user.get());
     credentials.put("security-password", user.get());
+
     return credentials;
   }
 

[geode] 03/03: GEODE-9521: Add test to cover multi-servers scenario for re-authentic… (#6782)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5bbd1c9e33b130f0ae0eec77bf7316681a457714
Author: Jinmei Liao <ji...@pivotal.io>
AuthorDate: Mon Aug 23 08:58:40 2021 -0700

    GEODE-9521: Add test to cover multi-servers scenario for re-authentic… (#6782)
---
 .../geode/security/AuthExpirationDUnitTest.java    |  65 +++++-----
 .../AuthExpirationMultiServerDUnitTest.java        | 136 +++++++++++++++++++++
 .../geode/security/ExpirableSecurityManager.java   |  29 +++--
 3 files changed, 194 insertions(+), 36 deletions(-)

diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
index 0aae286..8c8eaff 100644
--- a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
+++ b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
@@ -48,8 +48,8 @@ import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactor
 @RunWith(Parameterized.class)
 @Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
 public class AuthExpirationDUnitTest {
-  static RegionService regionService0;
-  static RegionService regionService1;
+  static RegionService user0Service;
+  static RegionService user1Service;
 
   @Parameterized.Parameter
   public String clientVersion;
@@ -110,9 +110,14 @@ public class AuthExpirationDUnitTest {
 
     // all put operation succeeded
     Region<Object, Object> region = server.getCache().getRegion("/region");
-    assertThat(ExpirableSecurityManager.getExpiredUsers().size()).isEqualTo(1);
-    assertThat(ExpirableSecurityManager.getExpiredUsers().contains("user1")).isTrue();
     assertThat(region.size()).isEqualTo(2);
+    Map<String, List<String>> authorizedOps = ExpirableSecurityManager.getAuthorizedOps();
+    Map<String, List<String>> unAuthorizedOps = ExpirableSecurityManager.getUnAuthorizedOps();
+    assertThat(authorizedOps.keySet().size()).isEqualTo(2);
+    assertThat(authorizedOps.get("user1")).asList().containsExactly("DATA:WRITE:region:0");
+    assertThat(authorizedOps.get("user2")).asList().containsExactly("DATA:WRITE:region:1");
+    assertThat(unAuthorizedOps.keySet().size()).isEqualTo(1);
+    assertThat(unAuthorizedOps.get("user1")).asList().containsExactly("DATA:WRITE:region:1");
   }
 
   @Test
@@ -125,48 +130,52 @@ public class AuthExpirationDUnitTest {
             .withServerConnection(serverPort));
 
     clientVM.invoke(() -> {
-      UpdatableUserAuthInitialize.setUser("serviceUser0");
+      UpdatableUserAuthInitialize.setUser("user0");
       ClientCache clientCache = ClusterStartupRule.getClientCache();
-      assert clientCache != null;
       clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY).create("region");
       Properties userSecurityProperties = new Properties();
       userSecurityProperties.put(SECURITY_CLIENT_AUTH_INIT,
           UpdatableUserAuthInitialize.class.getName());
-      regionService0 = clientCache.createAuthenticatedView(userSecurityProperties);
-      Region<Object, Object> region = regionService0.getRegion("/region");
+      user0Service = clientCache.createAuthenticatedView(userSecurityProperties);
+      Region<Object, Object> region = user0Service.getRegion("/region");
       region.put(0, "value0");
 
-      UpdatableUserAuthInitialize.setUser("serviceUser1");
+      UpdatableUserAuthInitialize.setUser("user1");
       userSecurityProperties.put(SECURITY_CLIENT_AUTH_INIT,
           UpdatableUserAuthInitialize.class.getName());
-      regionService1 = clientCache.createAuthenticatedView(userSecurityProperties);
-      region = regionService1.getRegion("/region");
+      user1Service = clientCache.createAuthenticatedView(userSecurityProperties);
+      region = user1Service.getRegion("/region");
       region.put(1, "value1");
     });
 
-    ExpirableSecurityManager.addExpiredUser("serviceUser1");
+    ExpirableSecurityManager.addExpiredUser("user1");
 
     clientVM.invoke(() -> {
-      Region<Object, Object> region = regionService1.getRegion("/region");
-      UpdatableUserAuthInitialize.setUser("serviceUser2");
-      region.put(2, "value2");
-
-      region = regionService0.getRegion("/region");
-      region.put(3, "value3");
-      regionService0.close();
-      regionService1.close();
+
+      Region<Object, Object> region = user0Service.getRegion("/region");
+      region.put(2, "value3");
+
+      UpdatableUserAuthInitialize.setUser("user1_extended");
+      region = user1Service.getRegion("/region");
+      region.put(3, "value2");
+
+      user0Service.close();
+      user1Service.close();
     });
 
     Region<Object, Object> region = server.getCache().getRegion("/region");
-    assertThat(ExpirableSecurityManager.getExpiredUsers().size()).isEqualTo(1);
-    assertThat(ExpirableSecurityManager.getExpiredUsers().contains("serviceUser1")).isTrue();
-    Map<Object, List<ResourcePermission>> authorizedOps =
-        ExpirableSecurityManager.getAuthorizedOps();
-    assertThat(authorizedOps.size()).isEqualTo(3);
-    assertThat(authorizedOps.get("serviceUser0").size()).isEqualTo(2);
-    assertThat(authorizedOps.get("serviceUser1").size()).isEqualTo(1);
-    assertThat(authorizedOps.get("serviceUser2").size()).isEqualTo(1);
     assertThat(region.size()).isEqualTo(4);
+
+    Map<String, List<String>> authorizedOps = ExpirableSecurityManager.getAuthorizedOps();
+    assertThat(authorizedOps.keySet().size()).isEqualTo(3);
+    assertThat(authorizedOps.get("user0")).asList().containsExactly("DATA:WRITE:region:0",
+        "DATA:WRITE:region:2");
+    assertThat(authorizedOps.get("user1")).asList().containsExactly("DATA:WRITE:region:1");
+    assertThat(authorizedOps.get("user1_extended")).asList().containsExactly("DATA:WRITE:region:3");
+
+    Map<String, List<String>> unAuthorizedOps = ExpirableSecurityManager.getUnAuthorizedOps();
+    assertThat(unAuthorizedOps.keySet().size()).isEqualTo(1);
+    assertThat(unAuthorizedOps.get("user1")).asList().containsExactly("DATA:WRITE:region:3");
   }
 
 }
diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationMultiServerDUnitTest.java b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationMultiServerDUnitTest.java
new file mode 100644
index 0000000..0dbc8ac
--- /dev/null
+++ b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationMultiServerDUnitTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTH_INIT;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ClientCacheRule;
+
+@Category({SecurityTest.class})
+public class AuthExpirationMultiServerDUnitTest implements Serializable {
+  public static final String REPLICATE_REGION = "replicateRegion";
+  public static final String PARTITION_REGION = "partitionRegion";
+  private MemberVM locator, server1, server2;
+  private int locatorPort;
+
+  @Rule
+  public ClusterStartupRule lsRule = new ClusterStartupRule();
+
+  @Rule
+  public ClientCacheRule clientCacheRule = new ClientCacheRule();
+
+  @Before
+  public void setup() {
+    locator = lsRule.startLocatorVM(0, l -> l.withSecurityManager(ExpirableSecurityManager.class));
+    locatorPort = locator.getPort();
+    server1 = lsRule.startServerVM(1, s -> s.withSecurityManager(ExpirableSecurityManager.class)
+        .withCredential("test", "test")
+        .withRegion(RegionShortcut.REPLICATE, REPLICATE_REGION)
+        .withRegion(RegionShortcut.PARTITION, PARTITION_REGION)
+        .withConnectionToLocator(locatorPort));
+    server2 = lsRule.startServerVM(2, s -> s.withSecurityManager(ExpirableSecurityManager.class)
+        .withCredential("test", "test")
+        .withRegion(RegionShortcut.REPLICATE, REPLICATE_REGION)
+        .withRegion(RegionShortcut.PARTITION, PARTITION_REGION)
+        .withConnectionToLocator(locatorPort));
+  }
+
+  @Test
+  public void clientReAuthenticationWorksOnMultipleServers() throws Exception {
+    UpdatableUserAuthInitialize.setUser("user1");
+    clientCacheRule
+        .withProperty(SECURITY_CLIENT_AUTH_INIT, UpdatableUserAuthInitialize.class.getName())
+        .withPoolSubscription(true)
+        .withServerConnection(server1.getPort());
+    clientCacheRule.createCache();
+    Region<Object, Object> region1 = clientCacheRule.createProxyRegion(REPLICATE_REGION);
+    Region<Object, Object> region2 = clientCacheRule.createProxyRegion(PARTITION_REGION);
+    region1.put("0", "value0");
+    region2.put("0", "value0");
+
+    expireUserOnAllVms("user1");
+
+    UpdatableUserAuthInitialize.setUser("user2");
+    region1.put("1", "value1");
+    region2.put("1", "value1");
+
+    // locator only validates peer
+    locator.invoke(() -> {
+      Map<String, List<String>> authorizedOps = ExpirableSecurityManager.getAuthorizedOps();
+      assertThat(authorizedOps.keySet().contains("test")).isTrue();
+      assertThat(authorizedOps.keySet().size()).isEqualTo(1);
+      Map<String, List<String>> unAuthorizedOps = ExpirableSecurityManager.getUnAuthorizedOps();
+      assertThat(unAuthorizedOps.keySet().size()).isEqualTo(0);
+    });
+
+    // client is connected to server1, server1 gets all the initial contact,
+    // authorization checks happens here
+    server1.invoke(() -> {
+      Map<String, List<String>> authorizedOps = ExpirableSecurityManager.getAuthorizedOps();
+      assertThat(authorizedOps.get("user1")).asList().containsExactlyInAnyOrder(
+          "DATA:WRITE:replicateRegion:0", "DATA:WRITE:partitionRegion:0");
+      assertThat(authorizedOps.get("user2")).asList().containsExactlyInAnyOrder(
+          "DATA:WRITE:replicateRegion:1", "DATA:WRITE:partitionRegion:1");
+      Map<String, List<String>> unAuthorizedOps = ExpirableSecurityManager.getUnAuthorizedOps();
+      assertThat(unAuthorizedOps.get("user1")).asList()
+          .containsExactly("DATA:WRITE:replicateRegion:1");
+    });
+
+    // server2 performs no authorization checks
+    server2.invoke(() -> {
+      Map<String, List<String>> authorizedOps = ExpirableSecurityManager.getAuthorizedOps();
+      Map<String, List<String>> unAuthorizedOps = ExpirableSecurityManager.getUnAuthorizedOps();
+      assertThat(authorizedOps.size()).isEqualTo(0);
+      assertThat(unAuthorizedOps.size()).isEqualTo(0);
+    });
+
+    MemberVM.invokeInEveryMember(() -> {
+      InternalCache cache = ClusterStartupRule.getCache();
+      Region<Object, Object> serverRegion1 = cache.getRegion(REPLICATE_REGION);
+      assertThat(serverRegion1.size()).isEqualTo(2);
+      Region<Object, Object> serverRegion2 = cache.getRegion(PARTITION_REGION);
+      assertThat(serverRegion2.size()).isEqualTo(2);
+    }, server1, server2);
+
+    MemberVM.invokeInEveryMember(() -> {
+      ExpirableSecurityManager.reset();
+      UpdatableUserAuthInitialize.reset();
+    }, locator, server1, server2);
+  }
+
+  private void expireUserOnAllVms(String user) {
+    MemberVM.invokeInEveryMember(() -> {
+      ExpirableSecurityManager.addExpiredUser(user);
+    }, locator, server1, server2);
+  }
+
+
+}
diff --git a/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
index 5c4d177..59a8b4cb 100644
--- a/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
+++ b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
@@ -35,20 +35,18 @@ public class ExpirableSecurityManager extends SimpleSecurityManager {
   // use static field for ease of testing since there is only one instance of this in each VM
   // we only need ConcurrentHashSet here, but map is only construct available in the library
   private static final Set<String> EXPIRED_USERS = ConcurrentHashMap.newKeySet();
-  private static final Map<Object, List<ResourcePermission>> AUTHORIZED_OPS =
+  private static final Map<String, List<String>> AUTHORIZED_OPS =
+      new ConcurrentHashMap<>();
+  private static final Map<String, List<String>> UNAUTHORIZED_OPS =
       new ConcurrentHashMap<>();
 
   @Override
   public boolean authorize(Object principal, ResourcePermission permission) {
     if (EXPIRED_USERS.contains((String) principal)) {
+      addToMap(UNAUTHORIZED_OPS, principal, permission);
       throw new AuthenticationExpiredException("User authentication expired.");
     }
-    List<ResourcePermission> permissions = AUTHORIZED_OPS.get(principal);
-    if (permissions == null) {
-      permissions = new ArrayList<>();
-    }
-    permissions.add(permission);
-    AUTHORIZED_OPS.put(principal, permissions);
+    addToMap(AUTHORIZED_OPS, principal, permission);
 
     // always authorized
     return true;
@@ -62,12 +60,27 @@ public class ExpirableSecurityManager extends SimpleSecurityManager {
     return EXPIRED_USERS;
   }
 
-  public static Map<Object, List<ResourcePermission>> getAuthorizedOps() {
+  public static Map<String, List<String>> getAuthorizedOps() {
     return AUTHORIZED_OPS;
   }
 
+  public static Map<String, List<String>> getUnAuthorizedOps() {
+    return UNAUTHORIZED_OPS;
+  }
+
+  private static void addToMap(Map<String, List<String>> maps, Object user,
+      ResourcePermission permission) {
+    List<String> list = maps.get(user);
+    if (list == null) {
+      list = new ArrayList<>();
+    }
+    list.add(permission.toString());
+    maps.put(user.toString(), list);
+  }
+
   public static void reset() {
     EXPIRED_USERS.clear();
     AUTHORIZED_OPS.clear();
+    UNAUTHORIZED_OPS.clear();
   }
 }

[geode] 01/03: GEODE-9456, GEODE-9452: Authentication Expiration (#6721)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c79b75681f6530380d64dcf86f5561d02a0f432c
Author: Jinmei Liao <ji...@pivotal.io>
AuthorDate: Fri Jul 30 12:33:54 2021 -0700

    GEODE-9456, GEODE-9452: Authentication Expiration (#6721)
    
    * Add tests and throw AuthenticationExpiredException
    Co-authored-by: Joris Melchior <jo...@gmail.com>
---
 .../integrationTest/resources/assembly_content.txt |   5 +-
 geode-core/build.gradle                            |   2 +-
 .../SecurityWithExpirationIntegrationTest.java     |  66 ++++++++++++
 geode-core/src/integrationTest/resources/shiro.ini |  40 ++++++++
 .../cache/client/internal/OpExecutorImpl.java      |   8 +-
 .../cache/tier/sockets/ServerConnection.java       |   6 +-
 .../security/AuthenticationExpiredException.java   |  32 ++++++
 .../org/apache/geode/security/SecurityManager.java |   5 +-
 .../sanctioned-geode-core-serializables.txt        |   2 +-
 .../geode/security/AuthExpirationDUnitTest.java    | 112 +++++++++++++++++++++
 .../geode/security/ExpirableSecurityManager.java   |  56 +++++++++++
 .../security/UpdatableUserAuthInitialize.java      |  54 ++++++++++
 .../gemstone/gemfire/OldClientSupportProvider.java |  16 ++-
 13 files changed, 394 insertions(+), 10 deletions(-)

diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 915c5c1..42ffc32 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -929,6 +929,7 @@ javadoc/org/apache/geode/ra/package-tree.html
 javadoc/org/apache/geode/security/AccessControl.html
 javadoc/org/apache/geode/security/AuthInitialize.html
 javadoc/org/apache/geode/security/AuthTokenEnabledComponents.html
+javadoc/org/apache/geode/security/AuthenticationExpiredException.html
 javadoc/org/apache/geode/security/AuthenticationFailedException.html
 javadoc/org/apache/geode/security/AuthenticationRequiredException.html
 javadoc/org/apache/geode/security/Authenticator.html
@@ -975,11 +976,12 @@ lib/commons-lang3-3.12.0.jar
 lib/commons-logging-1.2.jar
 lib/commons-modeler-2.0.1.jar
 lib/commons-validator-1.7.jar
+lib/fastutil-8.5.4.jar
 lib/fastutil-core-8.5.4.jar
 lib/fastutil-extra-8.5.4.jar
-lib/fastutil-8.5.4.jar
 lib/findbugs-annotations-1.3.9-1.jar
 lib/geo-0.7.7.jar
+lib/geode-apis-compatible-with-redis-0.0.0.jar
 lib/geode-common-0.0.0.jar
 lib/geode-connectors-0.0.0.jar
 lib/geode-core-0.0.0.jar
@@ -997,7 +999,6 @@ lib/geode-membership-0.0.0.jar
 lib/geode-memcached-0.0.0.jar
 lib/geode-old-client-support-0.0.0.jar
 lib/geode-rebalancer-0.0.0.jar
-lib/geode-apis-compatible-with-redis-0.0.0.jar
 lib/geode-serialization-0.0.0.jar
 lib/geode-tcp-server-0.0.0.jar
 lib/geode-unsafe-0.0.0.jar
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index ecb51b7..8035775 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -387,7 +387,7 @@ dependencies {
 
     upgradeTestRuntimeOnly(project(path: ':geode-old-versions', configuration: 'classpathsOutput'))
     upgradeTestRuntimeOnly(project(':geode-log4j'))
-
+    upgradeTestRuntimeOnly(project(':geode-old-client-support'))
 
     performanceTestImplementation(project(':geode-junit')) {
         exclude module: 'geode-core'
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/security/SecurityWithExpirationIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/security/SecurityWithExpirationIntegrationTest.java
new file mode 100644
index 0000000..cb0a24f
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/security/SecurityWithExpirationIntegrationTest.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.security;
+
+import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Properties;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.security.SecurityService;
+import org.apache.geode.internal.security.SecurityServiceFactory;
+import org.apache.geode.security.AuthenticationExpiredException;
+import org.apache.geode.security.ExpirableSecurityManager;
+import org.apache.geode.test.junit.categories.SecurityTest;
+
+@Category({SecurityTest.class})
+public class SecurityWithExpirationIntegrationTest {
+
+  protected Properties props = new Properties();
+
+  protected SecurityService securityService;
+
+  @Before
+  public void before() throws Exception {
+    props.setProperty(SECURITY_MANAGER, ExpirableSecurityManager.class.getName());
+    securityService = SecurityServiceFactory.create(this.props);
+  }
+
+  @After
+  public void after() throws Exception {
+    securityService.logout();
+    ExpirableSecurityManager.reset();
+  }
+
+  @Test
+  public void testThrowAuthenticationExpiredException() {
+    ExpirableSecurityManager.addExpiredUser("data");
+    this.securityService.login(loginCredentials("data", "data"));
+    assertThatThrownBy(() -> this.securityService.authorize(ResourcePermissions.DATA_READ))
+        .isInstanceOf(AuthenticationExpiredException.class);
+  }
+
+  private Properties loginCredentials(String username, String password) {
+    Properties credentials = new Properties();
+    credentials.put(ResourceConstants.USER_NAME, username);
+    credentials.put(ResourceConstants.PASSWORD, password);
+    return credentials;
+  }
+}
diff --git a/geode-core/src/integrationTest/resources/shiro.ini b/geode-core/src/integrationTest/resources/shiro.ini
new file mode 100644
index 0000000..a9746a5
--- /dev/null
+++ b/geode-core/src/integrationTest/resources/shiro.ini
@@ -0,0 +1,40 @@
+# 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.
+
+# the users and roles in this file needs to be kept in sync with shiro.ini
+# since they are used by the same test to test ShiroUtil
+# -----------------------------------------------------------------------------
+# Users and their (optional) assigned roles
+# username = password, role1, role2, ..., roleN
+# -----------------------------------------------------------------------------
+[users]
+root = secret, admin
+guest = guest, guest
+regionAReader = password, readRegionA
+regionAUser = password, useRegionA
+dataReader = 12345, readData
+reader = 12345, readAll
+
+# -----------------------------------------------------------------------------
+# Roles with assigned permissions
+# roleName = perm1, perm2, ..., permN
+# -----------------------------------------------------------------------------
+[roles]
+admin = *
+guest = none
+readRegionA = DATA:READ:RegionA
+useRegionA = *:*:RegionA
+readData = DATA:READ
+readAll = *:READ
\ No newline at end of file
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/OpExecutorImpl.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/OpExecutorImpl.java
index 69fb338..c5fe32a 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/OpExecutorImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/OpExecutorImpl.java
@@ -15,6 +15,8 @@
 
 package org.apache.geode.cache.client.internal;
 
+import static org.apache.geode.internal.cache.tier.sockets.ServerConnection.USER_NOT_FOUND;
+
 import java.io.IOException;
 import java.io.NotSerializableException;
 import java.net.SocketException;
@@ -59,6 +61,7 @@ import org.apache.geode.internal.cache.tier.sockets.MessageTooLargeException;
 import org.apache.geode.internal.cache.wan.BatchException70;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.logging.internal.log4j.api.LogService;
+import org.apache.geode.security.AuthenticationExpiredException;
 import org.apache.geode.security.AuthenticationRequiredException;
 import org.apache.geode.security.GemFireSecurityException;
 import org.apache.geode.util.internal.GeodeGlossary;
@@ -740,8 +743,9 @@ public class OpExecutorImpl implements ExecutablePool {
     } catch (final ServerConnectivityException sce) {
       final Throwable cause = sce.getCause();
       if ((cause instanceof AuthenticationRequiredException
-          && "User authorization attributes not found.".equals(cause.getMessage()))
-          || sce.getMessage().contains("Connection error while authenticating user")) {
+          && USER_NOT_FOUND.equals(cause.getMessage()))
+          || sce.getMessage().contains("Connection error while authenticating user")
+          || cause instanceof AuthenticationExpiredException) {
         // 2nd exception-message above is from AbstractOp.sendMessage()
 
         if (pool.getMultiuserAuthentication()) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ServerConnection.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ServerConnection.java
index 8ee4993..5435f9b 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ServerConnection.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ServerConnection.java
@@ -100,6 +100,8 @@ public class ServerConnection implements Runnable {
   private static final String DISALLOW_INTERNAL_MESSAGES_WITHOUT_CREDENTIALS_NAME =
       "geode.disallow-internal-messages-without-credentials";
 
+  public static final String USER_NOT_FOUND = "User authorization attributes not found.";
+
   /**
    * When true requires some formerly credential-less messages to carry credentials. See GEODE-3249
    * and ServerConnection.isInternalMessage()
@@ -1774,7 +1776,7 @@ public class ServerConnection implements Runnable {
       logger.debug("Unexpected exception {}", npe.toString());
     }
     if (uaa == null) {
-      throw new AuthenticationRequiredException("User authorization attributes not found.");
+      throw new AuthenticationRequiredException(USER_NOT_FOUND);
     }
     return uaa;
   }
@@ -1818,7 +1820,7 @@ public class ServerConnection implements Runnable {
       logger.debug("Unexpected exception", npe);
     }
     if (uaa == null) {
-      throw new AuthenticationRequiredException("User authorization attributes not found.");
+      throw new AuthenticationRequiredException(USER_NOT_FOUND);
     }
 
     return uaa.getPostAuthzRequest();
diff --git a/geode-core/src/main/java/org/apache/geode/security/AuthenticationExpiredException.java b/geode-core/src/main/java/org/apache/geode/security/AuthenticationExpiredException.java
new file mode 100644
index 0000000..d4ec79d
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/security/AuthenticationExpiredException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * This exception is thrown by the SecurityManager's authorize method to indicate that the
+ * authentication has expired.
+ */
+public class AuthenticationExpiredException extends GemFireSecurityException {
+  private static final long serialVersionUID = 1771792091260325297L;
+
+  public AuthenticationExpiredException(String message) {
+    super(message);
+  }
+
+  public AuthenticationExpiredException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/security/SecurityManager.java b/geode-core/src/main/java/org/apache/geode/security/SecurityManager.java
index 301b3d5..7487d5f 100644
--- a/geode-core/src/main/java/org/apache/geode/security/SecurityManager.java
+++ b/geode-core/src/main/java/org/apache/geode/security/SecurityManager.java
@@ -74,8 +74,11 @@ public interface SecurityManager {
    * @param principal The principal that's requesting the permission
    * @param permission The permission requested
    * @return true if authorized, false if not
+   *
+   * @throw AuthenticationExpiredException if the principal has expired.
    */
-  default boolean authorize(Object principal, ResourcePermission permission) {
+  default boolean authorize(Object principal, ResourcePermission permission)
+      throws AuthenticationExpiredException {
     return true;
   }
 
diff --git a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
index 1953b1d..fd5f65f 100644
--- a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
+++ b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
@@ -464,7 +464,6 @@ org/apache/geode/management/internal/functions/RebalanceFunction,true,1
 org/apache/geode/management/internal/functions/RestoreRedundancyFunction,true,-8991672237560920252
 org/apache/geode/management/internal/operation/OperationState,true,8212319653561969588,locator:java/lang/String,opId:java/lang/String,operation:org/apache/geode/management/api/ClusterManagementOperation,operationEnd:java/util/Date,operationStart:java/util/Date,result:org/apache/geode/management/runtime/OperationResult,throwable:java/lang/Throwable
 org/apache/geode/management/internal/web/domain/QueryParameterSource,true,34131123582155,objectName:javax/management/ObjectName,queryExpression:javax/management/QueryExp
-org/apache/geode/management/internal/web/shell/MBeanAccessException,true,813768898269516238
 org/apache/geode/pdx/FieldType,false,defaultSerializedValue:java/nio/ByteBuffer,defaultValue:java/lang/Object,isFixedWidth:boolean,name:java/lang/String,width:int
 org/apache/geode/pdx/JSONFormatter$states,false
 org/apache/geode/pdx/JSONFormatterException,true,1
@@ -480,6 +479,7 @@ org/apache/geode/pdx/internal/EnumInfo$PdxInstanceEnumInfo,true,7907582104525106
 org/apache/geode/pdx/internal/PdxInputStream,false
 org/apache/geode/pdx/internal/PdxReaderImpl,true,-6094553093860427759,blobType:org/apache/geode/pdx/internal/PdxType,dis:org/apache/geode/pdx/internal/PdxInputStream
 org/apache/geode/security/AuthTokenEnabledComponents,false
+org/apache/geode/security/AuthenticationExpiredException,true,1771792091260325297
 org/apache/geode/security/AuthenticationFailedException,true,-8202866472279088879
 org/apache/geode/security/AuthenticationRequiredException,true,4675976651103154919
 org/apache/geode/security/GemFireSecurityException,true,3814254578203076926,cause:java/lang/Throwable
diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
new file mode 100644
index 0000000..117cc77
--- /dev/null
+++ b/geode-core/src/upgradeTest/java/org/apache/geode/security/AuthExpirationDUnitTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTH_INIT;
+import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
+import static org.apache.geode.test.version.VersionManager.CURRENT_VERSION;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientRegionFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.test.dunit.rules.ClientVM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerStarterRule;
+import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;
+
+@Category({SecurityTest.class})
+@RunWith(Parameterized.class)
+@Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
+public class AuthExpirationDUnitTest {
+
+  @Parameterized.Parameter
+  public String clientVersion;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<String> data() {
+    // only test the current version and the latest released version
+    return Arrays.asList(CURRENT_VERSION, "1.13.3");
+  }
+
+  @Rule
+  public ClusterStartupRule lsRule = new ClusterStartupRule();
+
+
+  @Rule
+  public RestoreSystemProperties restore = new RestoreSystemProperties();
+
+  @Rule
+  public ServerStarterRule server = new ServerStarterRule()
+      .withProperty(SECURITY_MANAGER, ExpirableSecurityManager.class.getName())
+      .withRegion(RegionShortcut.REPLICATE, "region");
+
+  @After
+  public void after() {
+    // make sure after each test, the values of the ExpirationManager are reset
+    ExpirableSecurityManager.reset();
+  }
+
+  @Test
+  public void clientShouldReAuthenticateWhenCredentialExpiredAndOperationSucceed()
+      throws Exception {
+    int serverPort = server.getPort();
+    ClientVM clientVM = lsRule.startClientVM(0, clientVersion,
+        c -> c.withProperty(SECURITY_CLIENT_AUTH_INIT, UpdatableUserAuthInitialize.class.getName())
+            .withPoolSubscription(true)
+            .withServerConnection(serverPort));
+
+    clientVM.invoke(() -> {
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      UpdatableUserAuthInitialize.setUser("user1");
+      ClientRegionFactory clientRegionFactory =
+          clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY);
+      Region region = clientRegionFactory.create("region");
+      region.put(0, "value0");
+    });
+
+    // expire the current user
+    ExpirableSecurityManager.addExpiredUser("user1");
+
+    // do a second put, if this is successful, it means new credentials are provided
+    clientVM.invoke(() -> {
+      UpdatableUserAuthInitialize.setUser("user2");
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      Region region = clientCache.getRegion("region");
+      region.put(1, "value1");
+    });
+
+    // all put operation succeeded
+    Region<Object, Object> region = server.getCache().getRegion("/region");
+    assertThat(ExpirableSecurityManager.getExpiredUsers().size()).isEqualTo(1);
+    assertThat(ExpirableSecurityManager.getExpiredUsers().contains("user1")).isTrue();
+    assertThat(region.size()).isEqualTo(2);
+  }
+
+}
diff --git a/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
new file mode 100644
index 0000000..fc1021d
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/security/ExpirableSecurityManager.java
@@ -0,0 +1,56 @@
+/*
+ * 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 java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.geode.examples.SimpleSecurityManager;
+
+/**
+ * this is a test security manager that will authenticate credentials when username matches the
+ * password. It will authorize all operations. It keeps a list of expired users, and will throw
+ * AuthenticationExpiredException if the user is in that list. This security manager is usually used
+ * with NewCredentialAuthInitialize.
+ *
+ * make sure to call reset after each test to clean things up.
+ */
+public class ExpirableSecurityManager extends SimpleSecurityManager {
+  // use static field for ease of testing since there is only one instance of this in each VM
+  // we only need ConcurrentHashSet here, but map is only construct available in the library
+  private static final Set<String> EXPIRED_USERS = ConcurrentHashMap.newKeySet();
+
+  @Override
+  public boolean authorize(Object principal, ResourcePermission permission) {
+    if (EXPIRED_USERS.contains(principal)) {
+      throw new AuthenticationExpiredException("User authentication expired.");
+    }
+    // always authorized
+    return true;
+  }
+
+  public static void addExpiredUser(String user) {
+    EXPIRED_USERS.add(user);
+  }
+
+  public static Set<String> getExpiredUsers() {
+    return EXPIRED_USERS;
+  }
+
+  public static void reset() {
+    EXPIRED_USERS.clear();
+  }
+}
diff --git a/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java b/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java
new file mode 100644
index 0000000..0a0e6b4
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/security/UpdatableUserAuthInitialize.java
@@ -0,0 +1,54 @@
+/*
+ * 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 java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.geode.distributed.DistributedMember;
+
+/**
+ * this is used in conjunction with ExpirableSecurityManager. It will create a new set of
+ * credentials every time getCredentials are called, and they will always be authenticated
+ * and authorized by the ExpirableSecurityManager.
+ *
+ * make sure reset is called after each test to clean things up.
+ */
+public class UpdatableUserAuthInitialize implements AuthInitialize {
+  // use static field for ease of testing since there is only one instance of this in each VM
+  private static final AtomicReference<String> user = new AtomicReference<>();
+
+  @Override
+  public Properties getCredentials(Properties securityProps, DistributedMember server,
+      boolean isPeer) throws AuthenticationFailedException {
+    Properties credentials = new Properties();
+    credentials.put("security-username", user.get());
+    credentials.put("security-password", user.get());
+    return credentials;
+  }
+
+  public static String getUser() {
+    return user.get();
+  }
+
+  public static void setUser(String newValue) {
+    user.set(newValue);
+  }
+
+  public static void reset() {
+    user.set(null);
+  }
+}
diff --git a/geode-old-client-support/src/main/java/com/gemstone/gemfire/OldClientSupportProvider.java b/geode-old-client-support/src/main/java/com/gemstone/gemfire/OldClientSupportProvider.java
index ccf3c1a..581fc98 100644
--- a/geode-old-client-support/src/main/java/com/gemstone/gemfire/OldClientSupportProvider.java
+++ b/geode-old-client-support/src/main/java/com/gemstone/gemfire/OldClientSupportProvider.java
@@ -14,6 +14,8 @@
  */
 package com.gemstone.gemfire;
 
+import static org.apache.geode.internal.cache.tier.sockets.ServerConnection.USER_NOT_FOUND;
+
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.util.Map;
@@ -27,6 +29,8 @@ import org.apache.geode.internal.cache.tier.sockets.OldClientSupportService;
 import org.apache.geode.internal.serialization.KnownVersion;
 import org.apache.geode.internal.serialization.VersionedDataOutputStream;
 import org.apache.geode.management.internal.beans.CacheServiceMBeanBase;
+import org.apache.geode.security.AuthenticationExpiredException;
+import org.apache.geode.security.AuthenticationRequiredException;
 import org.apache.geode.util.internal.GeodeGlossary;
 
 import com.gemstone.gemfire.cache.execute.EmtpyRegionFunctionException;
@@ -121,11 +125,21 @@ public class OldClientSupportProvider implements OldClientSupportService {
     if (theThrowable == null) {
       return theThrowable;
     }
+
+    String className = theThrowable.getClass().getName();
+
+    // backward compatibility for authentication expiration
+    if (clientVersion.isOlderThan(KnownVersion.GEODE_1_15_0)) {
+      if (className.equals(AuthenticationExpiredException.class.getName())) {
+        return new AuthenticationRequiredException(USER_NOT_FOUND);
+      }
+    }
+
     if (clientVersion.isNotOlderThan(KnownVersion.GFE_90)) {
       return theThrowable;
     }
 
-    String className = theThrowable.getClass().getName();
+
 
     // this class has been renamed, so it cannot be automatically translated
     // during java deserialization