diff --git a/legend-shared-pac4j/pom.xml b/legend-shared-pac4j/pom.xml
index 47bf1d08..cedf898d 100644
--- a/legend-shared-pac4j/pom.xml
+++ b/legend-shared-pac4j/pom.xml
@@ -67,6 +67,10 @@
org.mongodb
mongo-java-driver
+
+ redis.clients
+ jedis
+
com.fasterxml.jackson.core
jackson-annotations
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jBundle.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jBundle.java
index 2c430502..77a3e4e3 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jBundle.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jBundle.java
@@ -16,10 +16,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
-import com.mongodb.MongoClient;
-import com.mongodb.MongoClientURI;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
import io.dropwizard.Configuration;
import io.dropwizard.configuration.ConfigurationException;
import io.dropwizard.configuration.ConfigurationSourceProvider;
@@ -28,8 +24,6 @@
import io.dropwizard.setup.Environment;
import javax.security.auth.Subject;
import javax.servlet.DispatcherType;
-import org.apache.commons.lang.StringUtils;
-import org.bson.Document;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletHandler;
@@ -37,13 +31,16 @@
import org.finos.legend.server.pac4j.internal.SecurityFilterHandler;
import org.finos.legend.server.pac4j.internal.UsernameFilter;
import org.finos.legend.server.pac4j.kerberos.SubjectExecutor;
-import org.finos.legend.server.pac4j.mongostore.MongoDbSessionStore;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
+import org.finos.legend.server.pac4j.session.consumer.SessionConsumer;
+import org.finos.legend.server.pac4j.session.context.SessionContext;
+import org.finos.legend.server.pac4j.session.store.SessionStore;
+import org.finos.legend.server.pac4j.session.store.SessionStoreFactory;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.session.J2ESessionStore;
import org.pac4j.core.engine.DefaultSecurityLogic;
-import org.pac4j.core.engine.decision.AlwaysUseSessionProfileStorageDecision;
import org.pac4j.core.http.url.DefaultUrlResolver;
import org.pac4j.core.matching.Matcher;
import org.pac4j.core.matching.PathMatcher;
@@ -137,15 +134,15 @@ public Pac4jFactory getPac4jFactory(C configuration)
final SubjectExecutor subjectExecutor = new SubjectExecutor(
Objects.isNull(this.subjectSupplierSupplier) ? null : this.subjectSupplierSupplier.apply(configuration));
- MongoDatabase db = null;
- if (StringUtils.isNotEmpty(legendConfig.getMongoDb())
- && StringUtils.isNotEmpty(legendConfig.getMongoUri()))
+ SessionStore sessionStore = null;
+
+ SessionStoreConfiguration sessionStoreConfiguration = legendConfig.getSessionStoreConfiguration();
+ if (sessionStoreConfiguration != null && sessionStoreConfiguration.isEnabled())
{
- MongoClient client = new MongoClient(new MongoClientURI(legendConfig.getMongoUri()));
- db = subjectExecutor.execute(() -> client.getDatabase(legendConfig.getMongoDb()));
+ sessionStore = subjectExecutor.execute(() -> SessionStoreFactory.INSTANCE.getInstance(legendConfig.getSessionStoreConfiguration()));
}
+ SessionStore finalSessionStore = sessionStore;
- MongoDatabase finalDb = db;
Pac4jFactory factory =
new Pac4jFactory()
{
@@ -154,28 +151,19 @@ public Config build()
{
Config config = super.build();
- if (legendConfig.getMongoSession() != null
- && legendConfig.getMongoSession().isEnabled())
+ if (Objects.isNull(finalSessionStore))
{
+ throw new RuntimeException("Session store needs to be configured if enabled");
+ }
- if (Objects.isNull(finalDb))
- {
- throw new RuntimeException(
- "MongoDB needs to be configured if MongoSession is used");
- }
+ config.setSessionStore(
+ new SessionContext(
+ sessionStoreConfiguration.getCryptoAlgorithm(),
+ sessionStoreConfiguration.getMaxSessionLength(),
+ finalSessionStore, ImmutableMap.of(J2EContext.class, new J2ESessionStore(),
+ JaxRsContext.class, new ServletSessionStore()),
+ subjectExecutor, legendConfig.getTrustedPackages()));
- MongoCollection userSessions = subjectExecutor.execute(
- () -> finalDb.getCollection(legendConfig.getMongoSession().getCollection()));
-
- config.setSessionStore(
- new MongoDbSessionStore(
- legendConfig.getMongoSession().getCryptoAlgorithm(),
- legendConfig.getMongoSession().getMaxSessionLength(),
- userSessions, ImmutableMap.of(
- J2EContext.class, new J2ESessionStore(),
- JaxRsContext.class, new ServletSessionStore()),
- subjectExecutor, legendConfig.getTrustedPackages()));
- }
return config;
}
};
@@ -208,8 +196,8 @@ JaxRsContext.class, new ServletSessionStore()),
servletConfiguration.setLogout(Collections.singletonList(logoutConfiguration));
legendConfig.getAuthorizers().stream()
- .filter(a -> a instanceof MongoDbConsumer)
- .forEach(a -> ((MongoDbConsumer) a).setupDb(finalDb));
+ .filter(a -> a instanceof SessionConsumer)
+ .forEach(a -> ((SessionConsumer) a).configureDatabase(finalSessionStore.getDatabaseClient(), sessionStoreConfiguration));
factory.setAuthorizers(legendConfig.getAuthorizers().stream()
.collect(Collectors.toMap(a -> a.getClass().getName(), a -> a)));
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jConfiguration.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jConfiguration.java
index 396e7207..738450b4 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jConfiguration.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendPac4jConfiguration.java
@@ -22,10 +22,11 @@
import io.dropwizard.configuration.YamlConfigurationFactory;
import java.io.IOException;
import java.util.List;
+
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
import org.pac4j.core.authorization.authorizer.Authorizer;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.finder.ClientFinder;
-import org.pac4j.core.client.finder.DefaultSecurityClientFinder;
@SuppressWarnings({"unused", "WeakerAccess"})
public final class LegendPac4jConfiguration
@@ -34,9 +35,9 @@ public final class LegendPac4jConfiguration
private List authorizers = ImmutableList.of();
private List clients;
private String defaultClient;
- private String mongoUri;
- private String mongoDb;
- private MongoSessionConfiguration mongoSession = new MongoSessionConfiguration();
+
+ private SessionStoreConfiguration sessionStoreConfiguration = new SessionStoreConfiguration();
+
private String callbackPrefix = "";
private List bypassPaths = ImmutableList.of();
private List bypassBranches = ImmutableList.of();
@@ -137,24 +138,19 @@ private void defaultAuthorizers(List authorizers)
}
}
- public MongoSessionConfiguration getMongoSession()
+ public SessionStoreConfiguration getSessionStoreConfiguration()
{
- return mongoSession;
+ return sessionStoreConfiguration;
}
- public void setMongoSession(MongoSessionConfiguration mongoSession)
+ public void setSessionStoreConfiguration(SessionStoreConfiguration sessionStoreConfiguration)
{
- this.mongoSession = mongoSession;
+ this.sessionStoreConfiguration = sessionStoreConfiguration;
}
- private void defaultMongoSession(MongoSessionConfiguration mongoSession)
+ private void defaultSessionStoreConfiguration(SessionStoreConfiguration sessionStoreConfiguration)
{
- this.mongoSession.defaults(mongoSession);
- }
-
- public String getMongoUri()
- {
- return mongoUri;
+ this.sessionStoreConfiguration.defaults(sessionStoreConfiguration);
}
public void setDefaultClient(String defaultClient)
@@ -162,37 +158,6 @@ public void setDefaultClient(String defaultClient)
this.defaultClient = defaultClient;
}
- public void setMongoUri(String mongoUri)
- {
- this.mongoUri = mongoUri;
- }
-
- private void defaultMongoUri(String mongoUri)
- {
- if (Strings.isNullOrEmpty(this.mongoUri))
- {
- this.mongoUri = mongoUri;
- }
- }
-
- public String getMongoDb()
- {
- return mongoDb;
- }
-
- public void setMongoDb(String mongoDb)
- {
- this.mongoDb = mongoDb;
- }
-
- private void defaultMongoDb(String mongoDb)
- {
- if (Strings.isNullOrEmpty(this.mongoDb))
- {
- this.mongoDb = mongoDb;
- }
- }
-
public String getCallbackPrefix()
{
return callbackPrefix;
@@ -227,96 +192,8 @@ public void loadDefaults(ConfigurationSourceProvider configurationSourceProvider
this.defaultBypassPaths(other.getBypassPaths());
this.defaultCallbackPrefix(other.getCallbackPrefix());
this.defaultClients(other.getClients());
- this.defaultMongoDb(other.getMongoDb());
- this.defaultMongoSession(other.getMongoSession());
- this.defaultMongoUri(other.getMongoUri());
+ this.defaultSessionStoreConfiguration(other.getSessionStoreConfiguration());
}
}
- public static class MongoSessionConfiguration
- {
- private static final String DEFAULT_CRYPTO_ALGORITHM = "AES";
- private static final int DEFAULT_MAX_SESSION_LENGTH = 7200;
- private boolean enabled;
- private String collection;
- private String cryptoAlgorithm = DEFAULT_CRYPTO_ALGORITHM;
- private int maxSessionLength = DEFAULT_MAX_SESSION_LENGTH;
-
- public boolean isEnabled()
- {
- return enabled;
- }
-
- public void setEnabled(boolean enabled)
- {
- this.enabled = enabled;
- }
-
- public String getCollection()
- {
- return collection;
- }
-
- public void setCollection(String collection)
- {
- this.collection = collection;
- }
-
- public String getCryptoAlgorithm()
- {
- return cryptoAlgorithm;
- }
-
- public void setCryptoAlgorithm(String cryptoAlgorithm)
- {
- this.cryptoAlgorithm = cryptoAlgorithm;
- }
-
- private void defaultCryptoAlgorithm(String cryptoAlgorithm)
- {
- if (this.cryptoAlgorithm.equals(DEFAULT_CRYPTO_ALGORITHM))
- {
- this.cryptoAlgorithm = cryptoAlgorithm;
- }
- }
-
- public int getMaxSessionLength()
- {
- return maxSessionLength;
- }
-
- public void setMaxSessionLength(int maxSessionLength)
- {
- this.maxSessionLength = maxSessionLength;
- }
-
- private void defaultMaxSessionLength(int maxSessionLength)
- {
- if (this.maxSessionLength == DEFAULT_MAX_SESSION_LENGTH)
- {
- this.maxSessionLength = maxSessionLength;
- }
- }
-
- private void defaultEnabled(boolean enabled)
- {
- this.enabled = this.enabled || enabled;
- }
-
- private void defaultCollection(String collection)
- {
- if (Strings.isNullOrEmpty(this.collection))
- {
- this.collection = collection;
- }
- }
-
- private void defaults(MongoSessionConfiguration other)
- {
- this.defaultCollection(other.getCollection());
- this.defaultCryptoAlgorithm(other.getCryptoAlgorithm());
- this.defaultEnabled(other.isEnabled());
- this.defaultMaxSessionLength(other.getMaxSessionLength());
- }
- }
}
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/internal/HttpSessionStore.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/internal/HttpSessionStore.java
index 6fde0b77..34b2b8e9 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/internal/HttpSessionStore.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/internal/HttpSessionStore.java
@@ -21,11 +21,11 @@
public class HttpSessionStore implements SessionStore
{
- private final Map, SessionStore extends WebContext>>
+ private final Map, SessionStore>
underlyingStores;
public HttpSessionStore(
- Map, SessionStore extends WebContext>> underlyingStores)
+ Map, SessionStore> underlyingStores)
{
this.underlyingStores = underlyingStores;
}
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongoauthorizer/MongoDbAuthorizer.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongoauthorizer/MongoDbAuthorizer.java
deleted file mode 100644
index 29ace2c0..00000000
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongoauthorizer/MongoDbAuthorizer.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 Goldman Sachs
-//
-// Licensed 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.finos.legend.server.pac4j.mongoauthorizer;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-import java.util.List;
-import org.bson.Document;
-import org.finos.legend.server.pac4j.MongoDbConsumer;
-import org.pac4j.core.authorization.authorizer.AbstractCheckAuthenticationAuthorizer;
-import org.pac4j.core.context.WebContext;
-import org.pac4j.core.profile.CommonProfile;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@SuppressWarnings("unused")
-public class MongoDbAuthorizer extends AbstractCheckAuthenticationAuthorizer
- implements MongoDbConsumer
-{
- public static final String NAME = "mongoAuthorizer";
- private static final Logger logger = LoggerFactory.getLogger(MongoDbAuthorizer.class);
- private MongoCollection collection;
-
- @JsonProperty
- private String collectionName;
-
- @Override
- protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u)
- {
- String id = u.getId();
- Document doc = collection.find(new Document("_id", id)).first();
- if (doc != null)
- {
- logger.debug("Allowing user {} - found in Mongo Collection", id);
- return true;
- } else
- {
- logger.warn("Disallowing user {} - not found in Mongo Collection", id);
- return false;
- }
- }
-
- @Override
- public boolean isAuthorized(WebContext context, List profiles)
- {
- return isAnyAuthorized(context, profiles);
- }
-
- @Override
- public void setupDb(MongoDatabase database)
- {
- if (collectionName == null)
- {
- throw new RuntimeException("Collection name must be specified");
- }
- collection = database.getCollection(collectionName);
- }
-}
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/MongoDbSessionAuthority.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/MongoDbSessionAuthority.java
new file mode 100644
index 00000000..c81c9b5b
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/MongoDbSessionAuthority.java
@@ -0,0 +1,68 @@
+// Copyright 2020 Goldman Sachs
+//
+// Licensed 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.finos.legend.server.pac4j.session.authorization;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import org.apache.commons.lang.StringUtils;
+import org.bson.Document;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.profile.CommonProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("unused")
+public class MongoDbSessionAuthority extends SessionAuthority
+{
+
+ public static final String NAME = "MongoDbSessionAuthority";
+ private static final Logger logger = LoggerFactory.getLogger(MongoDbSessionAuthority.class);
+
+ private MongoCollection mongoCollection;
+
+ @JsonProperty
+ private String collection;
+
+ @Override
+ protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u)
+ {
+ String id = u.getId();
+ Document doc = mongoCollection.find(new Document("_id", id)).first();
+ if (doc != null)
+ {
+ logger.debug("Allowing user {} - found in Profile Authorization Store", id);
+ return true;
+ }
+ else
+ {
+ logger.warn("Disallowing user {} - not found in Profile Authorization Store", id);
+ return false;
+ }
+ }
+
+ @Override
+ public void configureDatabase(Object database, SessionStoreConfiguration config)
+ {
+ MongoDatabase mongoDB = (MongoDatabase) database;
+ if (mongoDB == null || StringUtils.isEmpty(collection))
+ {
+ throw new RuntimeException("MongoDB database and collection name must be specified");
+ }
+ mongoCollection = mongoDB.getCollection(collection);
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/RedisSessionAuthority.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/RedisSessionAuthority.java
new file mode 100644
index 00000000..f611136d
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/RedisSessionAuthority.java
@@ -0,0 +1,79 @@
+// Copyright 2020 Goldman Sachs
+//
+// Licensed 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.finos.legend.server.pac4j.session.authorization;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang.StringUtils;
+import org.bson.Document;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration.RedisConfiguration;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.profile.CommonProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisPooled;
+import redis.clients.jedis.UnifiedJedis;
+
+import java.util.Map;
+
+@SuppressWarnings("unused")
+public class RedisSessionAuthority extends SessionAuthority
+{
+ public static final String NAME = "RedisSessionAuthority";
+ private static final Logger logger = LoggerFactory.getLogger(RedisSessionAuthority.class);
+
+ @JsonProperty
+ private String port;
+
+ private UnifiedJedis jedis;
+
+ @Override
+ protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u)
+ {
+ String id = u.getId();
+
+ Document doc = new Document((Map) jedis.jsonGet(id));
+ if (doc != null)
+ {
+ logger.debug("Allowing user {} - found in Profile Authorization Store", id);
+ return true;
+ }
+ else
+ {
+ logger.warn("Disallowing user {} - not found in Profile Authorization Store", id);
+ return false;
+ }
+ }
+
+ @Override
+ public void configureDatabase(Object database, SessionStoreConfiguration config)
+ {
+ if (config == null)
+ {
+ throw new RuntimeException("Session store configuration is required for session authority");
+ }
+
+ RedisConfiguration redisConfiguration = config.getRedisConfiguration();
+
+ if (redisConfiguration == null || StringUtils.isEmpty(redisConfiguration.getHostname()) || StringUtils.isEmpty(port))
+ {
+ throw new RuntimeException("Redis hostname and port name must be specified");
+ }
+
+ jedis = new JedisPooled(new HostAndPort(redisConfiguration.getHostname(), Integer.parseInt(port)));
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/SessionAuthority.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/SessionAuthority.java
new file mode 100644
index 00000000..da9f6468
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/SessionAuthority.java
@@ -0,0 +1,35 @@
+// Copyright 2020 Goldman Sachs
+//
+// Licensed 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.finos.legend.server.pac4j.session.authorization;
+
+import org.finos.legend.server.pac4j.session.consumer.SessionConsumer;
+import org.pac4j.core.authorization.authorizer.AbstractCheckAuthenticationAuthorizer;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.profile.CommonProfile;
+
+import java.util.List;
+
+@SuppressWarnings("unused")
+public abstract class SessionAuthority extends AbstractCheckAuthenticationAuthorizer
+ implements SessionConsumer
+{
+
+ @Override
+ public boolean isAuthorized(WebContext context, List profiles)
+ {
+ return isAnyAuthorized(context, profiles);
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java
new file mode 100644
index 00000000..f707cb05
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java
@@ -0,0 +1,179 @@
+// Copyright 2020 Goldman Sachs
+//
+// Licensed 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.finos.legend.server.pac4j.session.config;
+
+public class SessionStoreConfiguration
+{
+ private static final String DEFAULT_CRYPTO_ALGORITHM = "AES";
+ private static final int DEFAULT_MAX_SESSION_LENGTH = 7200;
+
+ private boolean enabled;
+
+ private MongodbConfiguration mongodbConfiguration;
+ private RedisConfiguration redisConfiguration;
+
+ private String cryptoAlgorithm = DEFAULT_CRYPTO_ALGORITHM;
+ private int maxSessionLength = DEFAULT_MAX_SESSION_LENGTH;
+
+ public static class MongodbConfiguration
+ {
+ private String databaseURI;
+ private String databaseName;
+ private String collection;
+
+ public MongodbConfiguration()
+ {
+ }
+
+ public String getDatabaseURI()
+ {
+ return databaseURI;
+ }
+
+ public String getDatabaseName()
+ {
+ return databaseName;
+ }
+
+ public String getCollection()
+ {
+ return collection;
+ }
+
+ public void setDatabaseName(String databaseName)
+ {
+ this.databaseName = databaseName;
+ }
+
+ public void setDatabaseURI(String databaseURI)
+ {
+ this.databaseURI = databaseURI;
+ }
+
+ public void setCollection(String collection)
+ {
+ this.collection = collection;
+ }
+ }
+
+ public static class RedisConfiguration
+ {
+ private String hostname;
+ private String port;
+
+ public RedisConfiguration()
+ {
+ }
+
+ public String getHostname()
+ {
+ return hostname;
+ }
+
+ public String getPort()
+ {
+ return port;
+ }
+
+ public void setHostname(String hostname)
+ {
+ this.hostname = hostname;
+ }
+
+ public void setPort(String port)
+ {
+ this.port = port;
+ }
+ }
+
+ public void defaults(SessionStoreConfiguration other)
+ {
+ this.defaultCryptoAlgorithm(other.getCryptoAlgorithm());
+ this.defaultEnabled(other.isEnabled());
+ this.defaultMaxSessionLength(other.getMaxSessionLength());
+ }
+
+ public void defaultCryptoAlgorithm(String cryptoAlgorithm)
+ {
+ if (this.cryptoAlgorithm.equals(DEFAULT_CRYPTO_ALGORITHM))
+ {
+ this.cryptoAlgorithm = cryptoAlgorithm;
+ }
+ }
+
+ public void defaultEnabled(boolean enabled)
+ {
+ this.enabled = this.enabled || enabled;
+ }
+
+ public void defaultMaxSessionLength(int maxSessionLength)
+ {
+ if (this.maxSessionLength == DEFAULT_MAX_SESSION_LENGTH)
+ {
+ this.maxSessionLength = maxSessionLength;
+ }
+ }
+
+ public String getCryptoAlgorithm()
+ {
+ return cryptoAlgorithm;
+ }
+
+ public int getMaxSessionLength()
+ {
+ return maxSessionLength;
+ }
+
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ public void setCryptoAlgorithm(String cryptoAlgorithm)
+ {
+ this.cryptoAlgorithm = cryptoAlgorithm;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ public void setMaxSessionLength(int maxSessionLength)
+ {
+ this.maxSessionLength = maxSessionLength;
+ }
+
+ public MongodbConfiguration getMongodbConfiguration()
+ {
+ return mongodbConfiguration;
+ }
+
+ public void setMongodbConfiguration(MongodbConfiguration mongodbConfiguration)
+ {
+ this.mongodbConfiguration = mongodbConfiguration;
+ }
+
+ public RedisConfiguration getRedisConfiguration()
+ {
+ return redisConfiguration;
+ }
+
+ public void setRedisConfiguration(RedisConfiguration redisConfiguration)
+ {
+ this.redisConfiguration = redisConfiguration;
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/MongoDbConsumer.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/consumer/SessionConsumer.java
similarity index 70%
rename from legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/MongoDbConsumer.java
rename to legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/consumer/SessionConsumer.java
index 1631ef4a..01c89431 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/MongoDbConsumer.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/consumer/SessionConsumer.java
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.finos.legend.server.pac4j;
+package org.finos.legend.server.pac4j.session.consumer;
-import com.mongodb.client.MongoDatabase;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
-public interface MongoDbConsumer
+public interface SessionConsumer
{
- void setupDb(MongoDatabase database);
+
+ void configureDatabase(Object database, SessionStoreConfiguration config);
+
}
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStore.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java
similarity index 73%
rename from legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStore.java
rename to legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java
index 7f4429d5..84c2b099 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStore.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java
@@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.finos.legend.server.pac4j.mongostore;
+package org.finos.legend.server.pac4j.session.context;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.model.IndexOptions;
-import org.bson.Document;
import org.finos.legend.server.pac4j.LegendPac4jBundle;
import org.finos.legend.server.pac4j.internal.HttpSessionStore;
import org.finos.legend.server.pac4j.kerberos.SubjectExecutor;
+import org.finos.legend.server.pac4j.session.store.SessionStore;
+import org.finos.legend.server.pac4j.session.utils.SessionCrypt;
+import org.finos.legend.server.pac4j.session.utils.SessionToken;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext;
-import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.profile.ProfileHelper;
import org.pac4j.core.util.JavaSerializationHelper;
@@ -32,50 +31,49 @@
import java.security.GeneralSecurityException;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
-import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
-public class MongoDbSessionStore extends HttpSessionStore
+
+public class SessionContext extends HttpSessionStore
{
- private static final Logger logger = LoggerFactory.getLogger(MongoDbSessionStore.class);
- private static final String CREATED_FIELD = "created";
- private static final String ID_FIELD = "_id";
- private final MongoCollection userSessions;
+ private static final Logger logger = LoggerFactory.getLogger(SessionContext.class);
+
+ private final SessionStore userSessions;
private final SessionCrypt sessionCrypt;
private final int maxSessionLength;
private final JavaSerializationHelper serializationHelper;
private final SubjectExecutor subjectExecutor;
/**
- * Create MongoDb session store.
+ * Create session store.
*
* @param algorithm Crypto Algorithm for serialized data
* @param maxSessionLength Expire data after
- * @param userSessions Mongo Collection
+ * @param userSessions Session Store
* @param underlyingStores Fallback stores
*/
- public MongoDbSessionStore(
- String algorithm, int maxSessionLength, MongoCollection userSessions,
- Map, SessionStore extends WebContext>> underlyingStores, List extraTrustedPackages)
+ public SessionContext(
+ String algorithm, int maxSessionLength, SessionStore userSessions,
+ Map, org.pac4j.core.context.session.SessionStore> underlyingStores, List extraTrustedPackages)
{
this(algorithm, maxSessionLength, userSessions, underlyingStores, new SubjectExecutor(null),extraTrustedPackages);
}
/**
- * Create MongoDb session store.
+ * Create session store.
*
* @param algorithm Crypto Algorithm for serialized data
* @param maxSessionLength Expire data after
- * @param userSessions Mongo Collection
+ * @param userSessions Session store
* @param underlyingStores Fallback stores
* @param subjectExecutor Execute DB actions using a Subject
*/
- public MongoDbSessionStore(
- String algorithm, int maxSessionLength, MongoCollection userSessions,
- Map, SessionStore extends WebContext>> underlyingStores,
+ public SessionContext(
+ String algorithm, int maxSessionLength, SessionStore userSessions,
+ Map, org.pac4j.core.context.session.SessionStore> underlyingStores,
SubjectExecutor subjectExecutor, List extraTrustedPackages)
{
super(underlyingStores);
@@ -85,9 +83,7 @@ public MongoDbSessionStore(
this.serializationHelper = LegendPac4jBundle.getSerializationHelper(extraTrustedPackages);
this.subjectExecutor.execute((PrivilegedAction) () ->
{
- userSessions.createIndex(
- new Document(CREATED_FIELD, 1),
- new IndexOptions().name("ttl").expireAfter((long) maxSessionLength, TimeUnit.SECONDS));
+ userSessions.createIndex(maxSessionLength, TimeUnit.SECONDS);
return null;
});
this.userSessions = userSessions;
@@ -111,17 +107,12 @@ private SessionToken createSsoKey(WebContext context)
SessionToken finalToken = token;
this.subjectExecutor.execute((PrivilegedAction) () ->
{
- userSessions.insertOne(getSearchSpec(finalToken).append(CREATED_FIELD, new Date()));
+ userSessions.createSession(finalToken);
return null;
});
return token;
}
- private Document getSearchSpec(SessionToken token)
- {
- return new Document(ID_FIELD, UuidUtils.toHexString(token.getSessionId()));
- }
-
@Override
public String getOrCreateSessionId(WebContext context)
{
@@ -136,10 +127,10 @@ public Object get(WebContext context, String key)
if (res == null)
{
final SessionToken token = getOrCreateSsoKey(context);
- Document doc = this.subjectExecutor.execute(() -> userSessions.find(getSearchSpec(token)).first());
+ Object doc = this.subjectExecutor.execute(() -> userSessions.getSession(token));
if (doc != null)
{
- String serialized = doc.getString(key);
+ String serialized = userSessions.getSessionAttribute(doc, key);
if (serialized != null)
{
try
@@ -179,9 +170,8 @@ public void set(WebContext context, String key, Object value)
byte[] serialized = new JavaSerializationHelper().serializeToBytes(serializable);
try
{
- this.subjectExecutor.executeWithException(() -> userSessions.updateOne(
- getSearchSpec(token),
- new Document("$set", new Document(key, sessionCrypt.toCryptedString(serialized, token)))));
+ this.subjectExecutor.executeWithException(() -> userSessions.updateSession(
+ token, key, sessionCrypt.toCryptedString(serialized, token)));
} catch (PrivilegedActionException e)
{
logger.warn("Unable to serialize session data for user", e);
@@ -195,7 +185,7 @@ public boolean destroySession(WebContext context)
{
final SessionToken token = getOrCreateSsoKey(context);
token.saveInContext(context, 0);
- this.subjectExecutor.execute(() -> userSessions.deleteMany(getSearchSpec(token)));
+ this.subjectExecutor.execute(() -> userSessions.deleteSession(token));
return super.destroySession(context);
}
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/MongoDbSessionStore.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/MongoDbSessionStore.java
new file mode 100644
index 00000000..78583563
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/MongoDbSessionStore.java
@@ -0,0 +1,99 @@
+package org.finos.legend.server.pac4j.session.store;
+
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientURI;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.IndexOptions;
+import org.apache.commons.lang.StringUtils;
+import org.bson.Document;
+
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration.MongodbConfiguration;
+import org.finos.legend.server.pac4j.session.utils.SessionToken;
+import org.finos.legend.server.pac4j.session.utils.UuidUtils;
+
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+
+public class MongoDbSessionStore implements SessionStore
+{
+ private MongoDatabase mongoDatabase;
+ private MongoCollection mongoCollection;
+
+ public MongoDbSessionStore(MongodbConfiguration mongodbConfiguration)
+ {
+ validateConfiguration(mongodbConfiguration);
+
+ this.mongoDatabase = new MongoClient(new MongoClientURI(mongodbConfiguration.getDatabaseURI()))
+ .getDatabase(mongodbConfiguration.getDatabaseName());
+
+ this.mongoCollection = mongoDatabase.getCollection(mongodbConfiguration.getCollection());
+ }
+
+ public MongoDbSessionStore(MongoDatabase mongoDatabase, String collectionName)
+ {
+ this.mongoDatabase = mongoDatabase;
+ this.mongoCollection = mongoDatabase.getCollection(collectionName);
+ }
+
+ public void createIndex(long maxSessionLength, TimeUnit timeUnit)
+ {
+ mongoCollection.createIndex(new Document(SESSION_PROPERTY_CREATED, 1),
+ new IndexOptions().name("ttl").expireAfter(maxSessionLength, timeUnit));
+ }
+
+ public void createSession(SessionToken token)
+ {
+ mongoCollection.insertOne(convertTokenToSessionDocument(token).append(SessionStore.SESSION_PROPERTY_CREATED, new Date()));
+ }
+
+ private Document convertTokenToSessionDocument(SessionToken token)
+ {
+ return new Document(SESSION_PROPERTY_ID, UuidUtils.toHexString(token.getSessionId()));
+ }
+
+ public Object deleteSession(SessionToken token)
+ {
+ return mongoCollection.deleteMany(convertTokenToSessionDocument(token));
+ }
+
+ public Object getDatabaseClient()
+ {
+ return mongoDatabase;
+ }
+
+ public Document getSession(SessionToken token)
+ {
+ return mongoCollection.find(convertTokenToSessionDocument(token)).first();
+ }
+
+ public String getSessionAttribute(Object document, String attributeKey)
+ {
+ return ((Document) document).getString(attributeKey);
+ }
+
+ public Object updateSession(SessionToken token, String key, Object value)
+ {
+ return mongoCollection.updateOne(convertTokenToSessionDocument(token), new Document("$set", new Document(key, value)));
+ }
+
+ private void validateConfiguration(MongodbConfiguration mongodbConfiguration)
+ {
+ if (StringUtils.isEmpty(mongodbConfiguration.getDatabaseURI()))
+ {
+ throw new RuntimeException("MongoDB session store requires 'databaseURI' attribute to be configured if enabled");
+ }
+
+ if (StringUtils.isEmpty(mongodbConfiguration.getDatabaseName()))
+ {
+ throw new RuntimeException("MongoDB session store requires 'databaseName' attribute to be configured if enabled");
+ }
+
+ if (StringUtils.isEmpty(mongodbConfiguration.getCollection()))
+ {
+ throw new RuntimeException("MongoDB session store requires 'collectionName' attribute to be configured if enabled");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/RedisSessionStore.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/RedisSessionStore.java
new file mode 100644
index 00000000..a5b8e1d7
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/RedisSessionStore.java
@@ -0,0 +1,120 @@
+package org.finos.legend.server.pac4j.session.store;
+
+import org.apache.commons.lang.StringUtils;
+import org.bson.Document;
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration.RedisConfiguration;
+import org.finos.legend.server.pac4j.session.utils.SessionToken;
+import org.finos.legend.server.pac4j.session.utils.UuidUtils;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisPooled;
+import redis.clients.jedis.Transaction;
+import redis.clients.jedis.json.Path;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+
+public class RedisSessionStore implements SessionStore
+{
+ private static long maxSessionLength;
+
+ private JedisPooled jedis;
+
+ public RedisSessionStore(RedisConfiguration config, long maxSessionLength)
+ {
+ validateConfiguration(config);
+
+ jedis = new JedisPooled(new HostAndPort(config.getHostname(), Integer.parseInt(config.getPort())));
+
+ this.maxSessionLength = maxSessionLength;
+ }
+
+ public void createIndex(long maxSessionLength, TimeUnit timeUnit)
+ {
+ // Redis doesn't require an index to manage expiration
+ }
+
+ public void createSession(SessionToken token)
+ {
+ String key = getSessionIdFromToken(token);
+
+ Document value = new Document(SESSION_PROPERTY_ID, key).append(SessionStore.SESSION_PROPERTY_CREATED, new Date());
+
+ // Transactions guarantee atomicity and thread safety operations, which means that requests
+ // from other clients will never be handled concurrently during Redis transactions
+ Transaction transaction = new Transaction(jedis.getPool().getResource());
+
+ transaction.jsonSet(key, Path.ROOT_PATH, value);
+ transaction.expire(key, maxSessionLength);
+
+ transaction.exec();
+ }
+
+ private Object createSession(String sessionId, String attributeKey, Object attributeValue)
+ {
+ Document doc = new Document(SESSION_PROPERTY_ID, sessionId)
+ .append(SessionStore.SESSION_PROPERTY_CREATED, new Date())
+ .append(attributeKey, attributeValue);
+
+ // Transactions guarantee atomicity and thread safety operations, which means that requests
+ // from other clients will never be handled concurrently during Redis transactions
+ Transaction transaction = new Transaction(jedis.getPool().getResource());
+
+ transaction.jsonSet(sessionId, Path.ROOT_PATH, doc);
+ transaction.expire(sessionId, maxSessionLength);
+
+ return transaction.exec();
+ }
+
+ public Object deleteSession(SessionToken token)
+ {
+ return jedis.jsonDel(getSessionIdFromToken(token));
+ }
+
+ public Object getDatabaseClient()
+ {
+ return jedis;
+ }
+
+ public Object getSession(SessionToken token)
+ {
+ return jedis.jsonGet(getSessionIdFromToken(token));
+ }
+
+ public String getSessionAttribute(Object document, String attributeKey)
+ {
+ return String.valueOf(((Map) document).get(attributeKey));
+ }
+
+ public Object updateSession(SessionToken token, String key, Object value)
+ {
+ String sessionId = getSessionIdFromToken(token);
+
+ if (!jedis.exists(sessionId))
+ {
+ return createSession(sessionId, key, value);
+ }
+
+ return jedis.jsonSet(sessionId, new Path(key), value);
+ }
+
+ private String getSessionIdFromToken(SessionToken token)
+ {
+ return UuidUtils.toHexString(token.getSessionId());
+ }
+
+ private void validateConfiguration(RedisConfiguration redisConfiguration)
+ {
+ if (StringUtils.isEmpty(redisConfiguration.getHostname()))
+ {
+ throw new RuntimeException("Redis session store requires 'hostname' custom attribute to be configured");
+ }
+
+ if (StringUtils.isEmpty(redisConfiguration.getPort()))
+ {
+ throw new RuntimeException("Redis session store requires 'port' custom attribute to be configured");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStore.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStore.java
new file mode 100644
index 00000000..dee8a223
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStore.java
@@ -0,0 +1,26 @@
+package org.finos.legend.server.pac4j.session.store;
+
+import org.finos.legend.server.pac4j.session.utils.SessionToken;
+
+import java.util.concurrent.TimeUnit;
+
+public interface SessionStore
+{
+ String SESSION_PROPERTY_ID = "_id";
+ String SESSION_PROPERTY_CREATED = "created";
+
+ void createIndex(long maxSessionLength, TimeUnit seconds);
+
+ void createSession(SessionToken token);
+
+ Object deleteSession(SessionToken token);
+
+ Object getDatabaseClient();
+
+ Object getSession(SessionToken token);
+
+ String getSessionAttribute(Object document, String attributeKey);
+
+ Object updateSession(SessionToken token, String key, Object value);
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStoreFactory.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStoreFactory.java
new file mode 100644
index 00000000..72ce4ad5
--- /dev/null
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStoreFactory.java
@@ -0,0 +1,25 @@
+package org.finos.legend.server.pac4j.session.store;
+
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
+
+public enum SessionStoreFactory
+{
+ INSTANCE;
+
+ public SessionStore getInstance(SessionStoreConfiguration config)
+ {
+ if (config.getMongodbConfiguration() != null)
+ {
+ return new MongoDbSessionStore(config.getMongodbConfiguration());
+ }
+ else if (config.getRedisConfiguration() != null)
+ {
+ return new RedisSessionStore(config.getRedisConfiguration(), config.getMaxSessionLength());
+ }
+ else
+ {
+ throw new RuntimeException("Either mongodb or redis must be configured if session store is enabled");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionCrypt.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionCrypt.java
similarity index 82%
rename from legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionCrypt.java
rename to legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionCrypt.java
index 0c0e6076..039680c1 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionCrypt.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionCrypt.java
@@ -12,23 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.finos.legend.server.pac4j.mongostore;
+package org.finos.legend.server.pac4j.session.utils;
import java.security.GeneralSecurityException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
-class SessionCrypt
+public class SessionCrypt
{
private final String cryptAlgorithm;
- SessionCrypt(String cryptAlgorithm)
+ public SessionCrypt(String cryptAlgorithm)
{
this.cryptAlgorithm = cryptAlgorithm;
}
- String toCryptedString(byte[] in, SessionToken token) throws GeneralSecurityException
+ public String toCryptedString(byte[] in, SessionToken token) throws GeneralSecurityException
{
byte[] keyBytes = UuidUtils.toByteArray(token.getSessionKey());
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, cryptAlgorithm);
@@ -38,7 +38,7 @@ String toCryptedString(byte[] in, SessionToken token) throws GeneralSecurityExce
return Base64.getEncoder().encodeToString(crypted);
}
- byte[] fromCryptedString(String in, SessionToken token) throws GeneralSecurityException
+ public byte[] fromCryptedString(String in, SessionToken token) throws GeneralSecurityException
{
byte[] keyBytes = UuidUtils.toByteArray(token.getSessionKey());
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, cryptAlgorithm);
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionToken.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionToken.java
similarity index 89%
rename from legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionToken.java
rename to legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionToken.java
index 027f2cef..ac0d159b 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/SessionToken.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/SessionToken.java
@@ -12,14 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.finos.legend.server.pac4j.mongostore;
+package org.finos.legend.server.pac4j.session.utils;
import com.google.common.base.Strings;
import java.util.UUID;
+
import org.pac4j.core.context.Cookie;
import org.pac4j.core.context.WebContext;
-class SessionToken
+public class SessionToken
{
private static final String SESSION_COOKIE_NAME = "LegendSSO";
@@ -32,12 +33,12 @@ private SessionToken(UUID sessionId, UUID sessionKey)
this.sessionKey = sessionKey;
}
- static SessionToken generate()
+ public static SessionToken generate()
{
return new SessionToken(UuidUtils.newUuid(), UuidUtils.newUuid());
}
- static SessionToken fromContext(WebContext context)
+ public static SessionToken fromContext(WebContext context)
{
String val = (String) context.getRequestAttribute(SESSION_COOKIE_NAME);
if (!Strings.isNullOrEmpty(val))
@@ -71,7 +72,7 @@ private Cookie toCookie()
"%s/%s", UuidUtils.toHexString(sessionId), UuidUtils.toHexString(sessionKey)));
}
- UUID getSessionId()
+ public UUID getSessionId()
{
return sessionId;
}
@@ -81,7 +82,7 @@ UUID getSessionKey()
return sessionKey;
}
- void saveInContext(WebContext context, int ttl)
+ public void saveInContext(WebContext context, int ttl)
{
Cookie cookie = toCookie();
cookie.setDomain(context.getServerName());
diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/UuidUtils.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/UuidUtils.java
similarity index 92%
rename from legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/UuidUtils.java
rename to legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/UuidUtils.java
index 97a8b204..640db346 100644
--- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongostore/UuidUtils.java
+++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/utils/UuidUtils.java
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.finos.legend.server.pac4j.mongostore;
+package org.finos.legend.server.pac4j.session.utils;
import java.nio.ByteBuffer;
import java.util.UUID;
-class UuidUtils
+public class UuidUtils
{
static UUID newUuid()
@@ -25,7 +25,7 @@ static UUID newUuid()
return UUID.randomUUID();
}
- static String toHexString(UUID uuid)
+ public static String toHexString(UUID uuid)
{
return Long.toHexString(uuid.getMostSignificantBits())
+ '-'
diff --git a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java
index c184c89b..8017be3d 100644
--- a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java
+++ b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java
@@ -18,6 +18,8 @@
import io.dropwizard.configuration.ConfigurationException;
import io.dropwizard.configuration.ResourceConfigurationSourceProvider;
import java.io.IOException;
+
+import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration;
import org.junit.Assert;
import org.junit.Test;
@@ -29,14 +31,15 @@ public void testDefaults() throws IOException, ConfigurationException
{
LegendPac4jConfiguration config = new LegendPac4jConfiguration();
config.setDefaults("defaults.json");
- config.setMongoDb("overrideMongoDb");
- config.loadDefaults(new ResourceConfigurationSourceProvider(), new ObjectMapper());
+ SessionStoreConfiguration sessionStoreConfiguration = new SessionStoreConfiguration();
+ sessionStoreConfiguration.getMongodbConfiguration().setDatabaseName("overrideMongoDb");
+ config.loadDefaults(new ResourceConfigurationSourceProvider(), new ObjectMapper());
- Assert.assertEquals("overrideMongoDb", config.getMongoDb());
- Assert.assertEquals("defaultMongoUri", config.getMongoUri());
- Assert.assertEquals("defaultMongoSession", config.getMongoSession().getCollection());
+ Assert.assertEquals("overrideMongoDb", sessionStoreConfiguration.getMongodbConfiguration().getDatabaseName());
+ Assert.assertEquals("defaultMongoUri", sessionStoreConfiguration.getMongodbConfiguration().getDatabaseURI());
+ Assert.assertEquals("defaultMongoSession", sessionStoreConfiguration.getMongodbConfiguration().getCollection());
}
}
diff --git a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStoreTest.java b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStoreTest.java
index 8dcee40c..572aa4a4 100644
--- a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStoreTest.java
+++ b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/mongostore/MongoDbSessionStoreTest.java
@@ -24,11 +24,15 @@
import com.mongodb.client.MongoDatabase;
import de.bwaldvogel.mongo.MongoServer;
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
+
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.servlet.http.Cookie;
+
+import org.finos.legend.server.pac4j.session.context.SessionContext;
+import org.finos.legend.server.pac4j.session.store.MongoDbSessionStore;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -40,131 +44,132 @@
public class MongoDbSessionStoreTest
{
-
- private static MongoServer server;
- private static MongoClient client;
- private static MongoDatabase db;
- private MongoDbSessionStore store;
-
- @BeforeClass
- public static void setup()
- {
- server = new MongoServer(new MemoryBackend());
- InetSocketAddress serverAddress = server.bind();
-
- client = new MongoClient(new ServerAddress(serverAddress));
- db = client.getDatabase("test");
- }
-
- @AfterClass
- public static void teardown()
- {
- server.shutdown();
- client.close();
- }
-
- @Before
- public void before()
- {
- List testTrustedPackages = new ArrayList<>();
- testTrustedPackages.add("test.trusted.package");
- store = new MongoDbSessionStore("AES", 100, db.getCollection("sessionData"), ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages);
- }
-
- @Test
- public void testSetCreatesCookie()
- {
- MockHttpServletResponse response = new MockHttpServletResponse();
- J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response);
- store.set(requestContext, "testKey", "testValue");
- Cookie[] cookies = response.getCookies();
- assertEquals(1, cookies.length);
- Cookie cookie = cookies[0];
- assertEquals("LegendSSO", cookie.getName());
- String val = cookie.getValue();
- Pattern acceptable = Pattern.compile("[0-9a-f]{15,16}-[0-9a-f]{15,16}/[0-9a-f]{15,16}-[0-9a-f]{15,16}");
- assertTrue("testing " + val, acceptable.matcher(val).matches());
- }
-
-
- @Test
- public void testMultipleSetsOnlyCreateOneCookie()
- {
- MockHttpServletResponse response = new MockHttpServletResponse();
- J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response);
- store.set(requestContext, "testKey", "testValue");
- store.set(requestContext, "testKey2", "testValue");
- store.set(requestContext, "testKey3", "testValue");
- store.set(requestContext, "testKey4", "testValue");
- Cookie[] cookies = response.getCookies();
- assertEquals(1, cookies.length);
- }
-
- @Test
- public void testTrustedPackageAdded()
- {
- assertTrue(this.store.getSerializationHelper().getTrustedPackages().contains("test.trusted.package"));
- }
-
- @Test
- public void testSetThenGetFromSession()
- {
- MockHttpServletRequest request = new MockHttpServletRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- J2EContext requestContext = new J2EContext(request, response);
- store.set(requestContext, "testKey", "testValue");
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
-
- // Copy the session to the new request
- MockHttpServletRequest newRequest = new MockHttpServletRequest();
- newRequest.setSession(request.getSession());
- MockHttpServletResponse newResponse = new MockHttpServletResponse();
- requestContext = new J2EContext(newRequest, newResponse);
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
- }
-
- @Test
- public void testSetThenGetFromMongo()
- {
- MockHttpServletRequest request = new MockHttpServletRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- J2EContext requestContext = new J2EContext(request, response);
- store.set(requestContext, "testKey", "testValue");
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
-
- // Copy the cookie to the new request
- MockHttpServletRequest newRequest = new MockHttpServletRequest();
- newRequest.setCookies(response.getCookies());
- MockHttpServletResponse newResponse = new MockHttpServletResponse();
- requestContext = new J2EContext(newRequest, newResponse);
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
-
- }
-
- @Test
- public void testSimulateCookieExpiryThenGetFromSession()
- {
- MockHttpServletRequest request = new MockHttpServletRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- J2EContext requestContext = new J2EContext(request, response);
- store.set(requestContext, "testKey", "testValue");
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
- Cookie[] initialResponseCookies = response.getCookies();
- // Copy the session to the new request but not the cookie. This should force creation of a new cookie;
- MockHttpServletRequest newRequest = new MockHttpServletRequest();
- newRequest.setSession(request.getSession());
- MockHttpServletResponse newResponse = new MockHttpServletResponse();
- requestContext = new J2EContext(newRequest, newResponse);
-
- assertEquals("testValue", store.get(requestContext, "testKey"));
- Cookie[] secondaryResponseCookies = newResponse.getCookies();
- assertNotEquals(secondaryResponseCookies, initialResponseCookies);
-
- }
+ private static MongoServer server;
+ private static MongoClient client;
+ private static MongoDatabase db;
+ private SessionContext store;
+
+ @BeforeClass
+ public static void setup()
+ {
+ server = new MongoServer(new MemoryBackend());
+ InetSocketAddress serverAddress = server.bind();
+
+ client = new MongoClient(new ServerAddress(serverAddress));
+ db = client.getDatabase("test");
+ }
+
+ @AfterClass
+ public static void teardown()
+ {
+ server.shutdown();
+ client.close();
+ }
+
+ @Before
+ public void before()
+ {
+ List testTrustedPackages = new ArrayList<>();
+ testTrustedPackages.add("test.trusted.package");
+
+ store = new SessionContext("AES", 100, new MongoDbSessionStore(db, "sessionData"),
+ ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages);
+ }
+
+ @Test
+ public void testSetCreatesCookie()
+ {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response);
+ store.set(requestContext, "testKey", "testValue");
+ Cookie[] cookies = response.getCookies();
+ assertEquals(1, cookies.length);
+ Cookie cookie = cookies[0];
+ assertEquals("LegendSSO", cookie.getName());
+ String val = cookie.getValue();
+ Pattern acceptable = Pattern.compile("[0-9a-f]{15,16}-[0-9a-f]{15,16}/[0-9a-f]{15,16}-[0-9a-f]{15,16}");
+ assertTrue("testing " + val, acceptable.matcher(val).matches());
+ }
+
+
+ @Test
+ public void testMultipleSetsOnlyCreateOneCookie()
+ {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response);
+ store.set(requestContext, "testKey", "testValue");
+ store.set(requestContext, "testKey2", "testValue");
+ store.set(requestContext, "testKey3", "testValue");
+ store.set(requestContext, "testKey4", "testValue");
+ Cookie[] cookies = response.getCookies();
+ assertEquals(1, cookies.length);
+ }
+
+ @Test
+ public void testTrustedPackageAdded()
+ {
+ assertTrue(this.store.getSerializationHelper().getTrustedPackages().contains("test.trusted.package"));
+ }
+
+ @Test
+ public void testSetThenGetFromSession()
+ {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ J2EContext requestContext = new J2EContext(request, response);
+ store.set(requestContext, "testKey", "testValue");
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+
+ // Copy the session to the new request
+ MockHttpServletRequest newRequest = new MockHttpServletRequest();
+ newRequest.setSession(request.getSession());
+ MockHttpServletResponse newResponse = new MockHttpServletResponse();
+ requestContext = new J2EContext(newRequest, newResponse);
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+ }
+
+ @Test
+ public void testSetThenGetFromMongo()
+ {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ J2EContext requestContext = new J2EContext(request, response);
+ store.set(requestContext, "testKey", "testValue");
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+
+ // Copy the cookie to the new request
+ MockHttpServletRequest newRequest = new MockHttpServletRequest();
+ newRequest.setCookies(response.getCookies());
+ MockHttpServletResponse newResponse = new MockHttpServletResponse();
+ requestContext = new J2EContext(newRequest, newResponse);
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+
+ }
+
+ @Test
+ public void testSimulateCookieExpiryThenGetFromSession()
+ {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ J2EContext requestContext = new J2EContext(request, response);
+ store.set(requestContext, "testKey", "testValue");
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+ Cookie[] initialResponseCookies = response.getCookies();
+ // Copy the session to the new request but not the cookie. This should force creation of a new cookie;
+ MockHttpServletRequest newRequest = new MockHttpServletRequest();
+ newRequest.setSession(request.getSession());
+ MockHttpServletResponse newResponse = new MockHttpServletResponse();
+ requestContext = new J2EContext(newRequest, newResponse);
+
+ assertEquals("testValue", store.get(requestContext, "testKey"));
+ Cookie[] secondaryResponseCookies = newResponse.getCookies();
+ assertNotEquals(secondaryResponseCookies, initialResponseCookies);
+
+ }
}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 0e130131..80286489 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,7 @@
0.32.0
3.8.3
0.8.1
+ 4.4.3
4.3.24.RELEASE
2.3.3.RELEASE
2.4.2
@@ -537,6 +538,13 @@
+
+
+ redis.clients
+ jedis
+ ${redis.client.version}
+
+
junit