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

amagyar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 82cd96d99 KNOX-2777 - Add configurations for concurrent session 
verifier feature (#608)
82cd96d99 is described below

commit 82cd96d99398cc9aad86596fb65240ccd0244498
Author: MrtnBalazs <77810082+mrtnbal...@users.noreply.github.com>
AuthorDate: Fri Jul 22 13:24:34 2022 +0200

    KNOX-2777 - Add configurations for concurrent session verifier feature 
(#608)
    
    Co-authored-by:  MrtnBalazs <mbal...@cloudera.com>
---
 .../gateway/config/impl/GatewayConfigImpl.java     |  31 ++++
 .../gateway/config/impl/GatewayConfigImplTest.java |  47 ++++++
 .../org/apache/knox/gateway/GatewayTestConfig.java |  20 +++
 .../session/control/ConcurrentSessionVerifier.java | 106 ++++++++++++++
 .../control/ConcurrentSessionVerifierTest.java     | 161 +++++++++++++++++++++
 .../apache/knox/gateway/config/GatewayConfig.java  |   8 +
 6 files changed, 373 insertions(+)

diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index cf0d42ba8..3e45bac36 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -293,6 +293,16 @@ public class GatewayConfigImpl extends Configuration 
implements GatewayConfig {
   private static final String GATEWAY_DATABASE_VERIFY_SERVER_CERT =  
GATEWAY_CONFIG_FILE_PREFIX + ".database.ssl.verify.server.cert";
   private static final String GATEWAY_DATABASE_TRUSTSTORE_FILE =  
GATEWAY_CONFIG_FILE_PREFIX + ".database.ssl.truststore.file";
 
+  // Concurrent session properties
+  private static final String PRIVILEGED_USERS = "privileged.users";
+  private static final String NON_PRIVILEGED_USERS = "non." + PRIVILEGED_USERS;
+  private static final String 
GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT = GATEWAY_CONFIG_FILE_PREFIX 
+ "." + PRIVILEGED_USERS + ".concurrent.session.limit";
+  private static final String 
GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT = 
GATEWAY_CONFIG_FILE_PREFIX + "." + NON_PRIVILEGED_USERS + 
".concurrent.session.limit";
+  private static final int 
GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT = 3;
+  private static final int 
GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT = 2;
+  private static final String GATEWAY_PRIVILEGED_USERS = 
GATEWAY_CONFIG_FILE_PREFIX + "." + PRIVILEGED_USERS;
+  private static final String GATEWAY_NON_PRIVILEGED_USERS = 
GATEWAY_CONFIG_FILE_PREFIX + "." + NON_PRIVILEGED_USERS;
+
   public GatewayConfigImpl() {
     init();
   }
@@ -1335,4 +1345,25 @@ public class GatewayConfigImpl extends Configuration 
implements GatewayConfig {
     return getInt(JETTY_MAX_FORM_KEYS, ContextHandler.DEFAULT_MAX_FORM_KEYS);
   }
 
+  @Override
+  public int getPrivilegedUsersConcurrentSessionLimit() {
+    return getInt(GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT, 
GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT);
+  }
+
+  @Override
+  public int getNonPrivilegedUsersConcurrentSessionLimit() {
+    return getInt(GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT, 
GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT);
+  }
+
+  @Override
+  public Set<String> getPrivilegedUsers() {
+    final Collection<String> privilegedUsers = 
getTrimmedStringCollection(GATEWAY_PRIVILEGED_USERS);
+    return privilegedUsers == null ? Collections.emptySet() : new 
HashSet<>(privilegedUsers);
+  }
+
+  @Override
+  public Set<String> getNonPrivilegedUsers() {
+    final Collection<String> nonPrivilegedUsers = 
getTrimmedStringCollection(GATEWAY_NON_PRIVILEGED_USERS);
+    return nonPrivilegedUsers == null ? Collections.emptySet() : new 
HashSet<>(nonPrivilegedUsers);
+  }
 }
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
index a9c3be6ad..5ec699b53 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
@@ -24,6 +24,8 @@ import org.junit.Test;
 
 import java.nio.file.Paths;
 import java.security.KeyStore;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -424,4 +426,49 @@ public class GatewayConfigImplTest {
     assertEquals("myTokenStateService", 
gatewayConfig.getServiceParameter("tokenstate", "impl"));
   }
 
+  @Test
+  public void testDefaultConcurrentSessionLimitParameters() {
+    GatewayConfigImpl config = new GatewayConfigImpl();
+
+    assertThat(config.getPrivilegedUsersConcurrentSessionLimit(), is(3));
+    assertThat(config.getNonPrivilegedUsersConcurrentSessionLimit(), is(2));
+    assertThat(config.getPrivilegedUsers(), is(new HashSet<>()));
+    assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>()));
+  }
+
+  @Test
+  public void testNormalConcurrentSessionLimitParameters() {
+    GatewayConfigImpl config = new GatewayConfigImpl();
+
+    config.set("gateway.privileged.users.concurrent.session.limit", "5");
+    assertThat(config.getPrivilegedUsersConcurrentSessionLimit(), is(5));
+    config.set("gateway.non.privileged.users.concurrent.session.limit", "6");
+    assertThat(config.getNonPrivilegedUsersConcurrentSessionLimit(), is(6));
+    config.set("gateway.privileged.users", "admin,jeff");
+    assertThat(config.getPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("admin", "jeff"))));
+    config.set("gateway.non.privileged.users", "tom,sam");
+    assertThat(config.getNonPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("tom", "sam"))));
+  }
+
+  @Test
+  public void testAbnormalConcurrentSessionLimitParameters() {
+    GatewayConfigImpl config = new GatewayConfigImpl();
+
+    config.set("gateway.privileged.users", "");
+    assertThat(config.getPrivilegedUsers(), is(new HashSet<>()));
+    config.set("gateway.non.privileged.users", "");
+    config.set("gateway.privileged.users", "   ");
+    assertThat(config.getPrivilegedUsers(), is(new HashSet<>()));
+    config.set("gateway.non.privileged.users", "   ");
+    assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>()));
+
+    config.set("gateway.privileged.users", " admin , jeff ");
+    assertThat(config.getPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("admin", "jeff"))));
+    config.set("gateway.non.privileged.users", " tom , sam ");
+    assertThat(config.getNonPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("tom", "sam"))));
+    config.set("gateway.privileged.users", "  guest  ");
+    assertThat(config.getPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("guest"))));
+    config.set("gateway.non.privileged.users", "  guest  ");
+    assertThat(config.getNonPrivilegedUsers(), is(new 
HashSet<>(Arrays.asList("guest"))));
+  }
 }
diff --git 
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index ec7367fce..24d07b430 100644
--- 
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++ 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -940,4 +940,24 @@ public class GatewayTestConfig extends Configuration 
implements GatewayConfig {
   public int getJettyMaxFormKeys() {
     return 0;
   }
+
+  @Override
+  public int getPrivilegedUsersConcurrentSessionLimit() {
+    return 0;
+  }
+
+  @Override
+  public int getNonPrivilegedUsersConcurrentSessionLimit() {
+    return 0;
+  }
+
+  @Override
+  public Set<String> getPrivilegedUsers() {
+    return null;
+  }
+
+  @Override
+  public Set<String> getNonPrivilegedUsers() {
+    return null;
+  }
 }
diff --git 
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java
 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java
new file mode 100644
index 000000000..e06633ff6
--- /dev/null
+++ 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java
@@ -0,0 +1,106 @@
+/*
+ * 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.knox.gateway.session.control;
+
+
+import org.apache.knox.gateway.config.GatewayConfig;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ConcurrentSessionVerifier {
+  public static final ConcurrentSessionVerifier INSTANCE = new 
ConcurrentSessionVerifier();
+  private Set<String> privilegedUsers;
+  private Set<String> nonPrivilegedUsers;
+  private int privilegedUserConcurrentSessionLimit;
+  private int nonPrivilegedUserConcurrentSessionLimit;
+  private Map<String, Integer> concurrentSessionCounter;
+  private final Lock sessionCountModifyLock = new ReentrantLock();
+
+  private ConcurrentSessionVerifier() {
+  }
+
+  public static ConcurrentSessionVerifier getInstance() {
+    return INSTANCE;
+  }
+
+  public void init(GatewayConfig config) {
+    this.privilegedUsers = config.getPrivilegedUsers();
+    this.nonPrivilegedUsers = config.getNonPrivilegedUsers();
+    this.privilegedUserConcurrentSessionLimit = 
config.getPrivilegedUsersConcurrentSessionLimit();
+    this.nonPrivilegedUserConcurrentSessionLimit = 
config.getNonPrivilegedUsersConcurrentSessionLimit();
+    this.concurrentSessionCounter = new ConcurrentHashMap<>();
+  }
+
+  public boolean verifySessionForUser(String username) {
+    if (!privilegedUsers.contains(username) && 
!nonPrivilegedUsers.contains(username)) {
+      return true;
+    }
+
+    sessionCountModifyLock.lock();
+    try {
+      concurrentSessionCounter.putIfAbsent(username, 0);
+      if (privilegedUserCheckLimitReached(username) || 
nonPrivilegedUserCheckLimitReached(username)) {
+        return false;
+      }
+      concurrentSessionCounter.compute(username, (key, value) -> value + 1);
+    } finally {
+      sessionCountModifyLock.unlock();
+    }
+    return true;
+  }
+
+  private boolean privilegedUserCheckLimitReached(String username) {
+    if (privilegedUserConcurrentSessionLimit < 0) {
+      return false;
+    }
+    return privilegedUsers.contains(username) && 
(concurrentSessionCounter.get(username) >= 
privilegedUserConcurrentSessionLimit);
+  }
+
+  private boolean nonPrivilegedUserCheckLimitReached(String username) {
+    if (nonPrivilegedUserConcurrentSessionLimit < 0) {
+      return false;
+    }
+    return nonPrivilegedUsers.contains(username) && 
(concurrentSessionCounter.get(username) >= 
nonPrivilegedUserConcurrentSessionLimit);
+  }
+
+  public void sessionEndedForUser(String username) {
+    sessionCountModifyLock.lock();
+    try {
+      concurrentSessionCounter.computeIfPresent(username, (key, counter) -> 
decreaseCounter(counter));
+    } finally {
+      sessionCountModifyLock.unlock();
+    }
+  }
+
+  private Integer decreaseCounter(Integer counter) {
+    counter--;
+    if (counter < 1) {
+      return null;
+    } else {
+      return counter;
+    }
+  }
+
+  Integer getUserConcurrentSessionCount(String username) {
+    return concurrentSessionCounter.get(username);
+  }
+}
diff --git 
a/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java
 
b/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java
new file mode 100644
index 000000000..d70eca356
--- /dev/null
+++ 
b/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.knox.gateway.session.control;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ConcurrentSessionVerifierTest {
+
+  private ConcurrentSessionVerifier verifier;
+
+  @Before
+  public void setUp() {
+    verifier = ConcurrentSessionVerifier.getInstance();
+  }
+
+  private GatewayConfig mockConfig(Set<String> privilegedUsers, Set<String> 
nonPrivilegedUsers, int privilegedUsersLimit, int nonPrivilegedUsersLimit) {
+    GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(config.getPrivilegedUsers()).andReturn(privilegedUsers);
+    
EasyMock.expect(config.getNonPrivilegedUsers()).andReturn(nonPrivilegedUsers);
+    
EasyMock.expect(config.getPrivilegedUsersConcurrentSessionLimit()).andReturn(privilegedUsersLimit);
+    
EasyMock.expect(config.getNonPrivilegedUsersConcurrentSessionLimit()).andReturn(nonPrivilegedUsersLimit);
+    EasyMock.replay(config);
+    return config;
+  }
+
+
+  @Test
+  public void userIsInNeitherOfTheGroups() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 3, 2);
+    verifier.init(config);
+    for (int i = 0; i < 4; i++) {
+      Assert.assertTrue(verifier.verifySessionForUser("sam"));
+    }
+  }
+
+  @Test
+  public void userIsInBothOfTheGroups() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin", 
"tom")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 2);
+    verifier.init(config);
+
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+
+    config = mockConfig(new HashSet<>(Arrays.asList("admin", "tom")), new 
HashSet<>(Arrays.asList("tom", "guest")), 3, 4);
+    verifier.init(config);
+
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+  }
+
+  @Test
+  public void userIsPrivileged() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 3, 2);
+    verifier.init(config);
+
+    Assert.assertTrue(verifier.verifySessionForUser("admin"));
+    Assert.assertTrue(verifier.verifySessionForUser("admin"));
+    Assert.assertTrue(verifier.verifySessionForUser("admin"));
+    Assert.assertFalse(verifier.verifySessionForUser("admin"));
+    Assert.assertFalse(verifier.verifySessionForUser("admin"));
+    verifier.sessionEndedForUser("admin");
+    Assert.assertTrue(verifier.verifySessionForUser("admin"));
+    Assert.assertFalse(verifier.verifySessionForUser("admin"));
+    Assert.assertFalse(verifier.verifySessionForUser("admin"));
+  }
+
+  @Test
+  public void userIsNotPrivileged() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 3, 2);
+    verifier.init(config);
+
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+    verifier.sessionEndedForUser("tom");
+    Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+  }
+
+  @Test
+  public void privilegedLimitIsZero() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 0, 2);
+    verifier.init(config);
+
+    Assert.assertFalse(verifier.verifySessionForUser("admin"));
+  }
+
+  @Test
+  public void nonPrivilegedLimitIsZero() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 3, 0);
+    verifier.init(config);
+
+    Assert.assertFalse(verifier.verifySessionForUser("tom"));
+  }
+
+  @Test
+  public void sessionsDoNotGoToNegative() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), 2, 2);
+    verifier.init(config);
+
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("admin"));
+    verifier.verifySessionForUser("admin");
+    Assert.assertEquals(1, 
verifier.getUserConcurrentSessionCount("admin").intValue());
+    verifier.sessionEndedForUser("admin");
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("admin"));
+    verifier.sessionEndedForUser("admin");
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("admin"));
+    verifier.verifySessionForUser("admin");
+    Assert.assertEquals(1, 
verifier.getUserConcurrentSessionCount("admin").intValue());
+
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("tom"));
+    verifier.verifySessionForUser("tom");
+    Assert.assertEquals(1, 
verifier.getUserConcurrentSessionCount("tom").intValue());
+    verifier.sessionEndedForUser("tom");
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("tom"));
+    verifier.sessionEndedForUser("tom");
+    Assert.assertNull(verifier.getUserConcurrentSessionCount("tom"));
+    verifier.verifySessionForUser("tom");
+    Assert.assertEquals(1, 
verifier.getUserConcurrentSessionCount("tom").intValue());
+  }
+
+  @Test
+  public void negativeLimitMeansUnlimited() {
+    GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), 
new HashSet<>(Arrays.asList("tom", "guest")), -2, -2);
+    verifier.init(config);
+
+    for (int i = 0; i < 10; i++) {
+      Assert.assertTrue(verifier.verifySessionForUser("admin"));
+      Assert.assertTrue(verifier.verifySessionForUser("tom"));
+    }
+  }
+}
+
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java 
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index faa5ea32a..bf6eee3b1 100644
--- 
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -813,4 +813,12 @@ public interface GatewayConfig {
   int getJettyMaxFormContentSize();
 
   int getJettyMaxFormKeys();
+
+  int getPrivilegedUsersConcurrentSessionLimit();
+
+  int getNonPrivilegedUsersConcurrentSessionLimit();
+
+  Set<String> getPrivilegedUsers();
+
+  Set<String> getNonPrivilegedUsers();
 }

Reply via email to