From 1ebd71dc855c01416fdacfd28790a1bd1c1d5295 Mon Sep 17 00:00:00 2001 From: Allen Terleto Date: Mon, 17 Jul 2023 17:23:05 -0400 Subject: [PATCH 1/4] Initial Commit Includes missing unit tests and workarounds that will be corrected once contribution is approved following a demo --- legend-shared-pac4j/pom.xml | 4 + .../server/pac4j/LegendPac4jBundle.java | 56 ++-- .../pac4j/LegendPac4jConfiguration.java | 147 +--------- .../pac4j/internal/HttpSessionStore.java | 4 +- .../mongoauthorizer/MongoDbAuthorizer.java | 71 ----- .../MongoDbSessionAuthority.java | 68 +++++ .../authorization/RedisSessionAuthority.java | 71 +++++ .../authorization/SessionAuthority.java | 35 +++ .../config/SessionStoreConfiguration.java | 125 +++++++++ .../consumer/SessionConsumer.java} | 10 +- .../context/SessionContext.java} | 57 ++-- .../session/store/MongoDbSessionStore.java | 87 ++++++ .../session/store/RedisSessionStore.java | 109 ++++++++ .../pac4j/session/store/SessionStore.java | 62 +++++ .../utils}/SessionCrypt.java | 10 +- .../utils}/SessionToken.java | 13 +- .../utils}/UuidUtils.java | 6 +- .../pac4j/LegendPac4JConfigurationTest.java | 17 +- .../mongostore/MongoDbSessionStoreTest.java | 253 +++++++++--------- pom.xml | 9 + 20 files changed, 788 insertions(+), 426 deletions(-) delete mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/mongoauthorizer/MongoDbAuthorizer.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/MongoDbSessionAuthority.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/RedisSessionAuthority.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/SessionAuthority.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java rename legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/{MongoDbConsumer.java => session/consumer/SessionConsumer.java} (70%) rename legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/{mongostore/MongoDbSessionStore.java => session/context/SessionContext.java} (75%) create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/MongoDbSessionStore.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/RedisSessionStore.java create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStore.java rename legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/{mongostore => session/utils}/SessionCrypt.java (82%) rename legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/{mongostore => session/utils}/SessionToken.java (89%) rename legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/{mongostore => session/utils}/UuidUtils.java (92%) 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..7d360461 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; @@ -29,7 +25,6 @@ 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 +32,15 @@ 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.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(() -> SessionStore.createInstance(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.getDatabase(), 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> + private final Map, SessionStore> underlyingStores; public HttpSessionStore( - Map, SessionStore> 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..4dc16fe7 --- /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 Mongo Collection", id); + return true; + } + else + { + logger.warn("Disallowing user {} - not found in Mongo Collection", 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..c25740d6 --- /dev/null +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/RedisSessionAuthority.java @@ -0,0 +1,71 @@ +// 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.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 Mongo Collection", id); + return true; + } + else + { + logger.warn("Disallowing user {} - not found in Mongo Collection", id); + return false; + } + } + + @Override + public void configureDatabase(Object database, SessionStoreConfiguration config) + { + if (config == null || StringUtils.isEmpty(config.getDatabaseURI()) || StringUtils.isEmpty(port)) + { + throw new RuntimeException("Redis URI and port name must be specified"); + } + + jedis = new JedisPooled(new HostAndPort(config.getDatabaseURI(), 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..f6023ad5 --- /dev/null +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java @@ -0,0 +1,125 @@ +package org.finos.legend.server.pac4j.session.config; + +import com.google.common.base.Strings; + +import java.util.HashMap; +import java.util.Map; + +public class SessionStoreConfiguration +{ + + private static final String DEFAULT_CRYPTO_ALGORITHM = "AES"; + private static final int DEFAULT_MAX_SESSION_LENGTH = 7200; + + private String type; + private boolean enabled; + private String databaseURI; + + private String cryptoAlgorithm = DEFAULT_CRYPTO_ALGORITHM; + private int maxSessionLength = DEFAULT_MAX_SESSION_LENGTH; + + private Map customConfigurations; + + + public void defaults(SessionStoreConfiguration other) + { + this.defaultCryptoAlgorithm(other.getCryptoAlgorithm()); + this.defaultEnabled(other.isEnabled()); + this.defaultMaxSessionLength(other.getMaxSessionLength()); + this.defaultDatabaseURI(other.getDatabaseURI()); + } + + public void defaultCryptoAlgorithm(String cryptoAlgorithm) + { + if (this.cryptoAlgorithm.equals(DEFAULT_CRYPTO_ALGORITHM)) + { + this.cryptoAlgorithm = cryptoAlgorithm; + } + } + + private void defaultDatabaseURI(String databaseURI) + { + if (Strings.isNullOrEmpty(this.databaseURI)) + { + this.databaseURI = databaseURI; + } + } + + 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 Map getCustomConfigurations() + { + if (customConfigurations == null) + { + customConfigurations = new HashMap<>(); + } + return customConfigurations; + } + + public String getDatabaseURI() + { + return databaseURI; + } + + public void setDatabaseURI(String databaseURI) + { + this.databaseURI = databaseURI; + } + + public String getType() + { + return type; + } + + 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 setCustomConfigurations(Map customConfigurations) + { + this.customConfigurations = customConfigurations; + } + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public void setMaxSessionLength(int maxSessionLength) + { + this.maxSessionLength = maxSessionLength; + } + + public void setType(String type) + { + this.type = type; + } + +} 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 75% 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..b7955b4b 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,17 @@ // 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; @@ -38,44 +38,43 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -public class MongoDbSessionStore extends HttpSessionStore +public class SessionContext extends HttpSessionStore //TODO rename { - 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> 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> 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 +84,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 +108,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,7 +128,7 @@ 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()); + Document doc = this.subjectExecutor.execute(() -> userSessions.getSession(token)); if (doc != null) { String serialized = doc.getString(key); @@ -179,9 +171,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 +186,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..e4e72644 --- /dev/null +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/MongoDbSessionStore.java @@ -0,0 +1,87 @@ +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.bson.Document; + +import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration; +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.Map; +import java.util.concurrent.TimeUnit; + + +public class MongoDbSessionStore extends SessionStore +{ + public static final String CUSTOM_CONFIG_MONGODB_DATABASE_NAME = "databaseName"; + public static final String CUSTOM_CONFIG_MONGODB_COLLECTION = "collection"; + + private MongoDatabase mongoDB; + private MongoCollection mongoCollection; + + public MongoDbSessionStore(SessionStoreConfiguration config) + { + validateCustomConfiguration(config.getCustomConfigurations()); + + mongoDB = new MongoClient(new MongoClientURI(config.getDatabaseURI())).getDatabase( + config.getCustomConfigurations().get(CUSTOM_CONFIG_MONGODB_DATABASE_NAME)); + + mongoCollection = mongoDB.getCollection(config.getCustomConfigurations().get(CUSTOM_CONFIG_MONGODB_COLLECTION)); + } + + private void validateCustomConfiguration(Map customConfigurations) + { + + if (!customConfigurations.containsKey(CUSTOM_CONFIG_MONGODB_DATABASE_NAME)) + { + throw new RuntimeException("MongoDB session store requires 'databaseName' custom attribute to be configured if enabled"); + } + + if (!customConfigurations.containsKey(CUSTOM_CONFIG_MONGODB_COLLECTION)) + { + throw new RuntimeException("MongoDB session store requires 'collection' custom attribute to be configured if enabled"); + } + } + + 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 getDatabase() + { + return mongoDB; + } + + public Document getSession(SessionToken token) + { + return mongoCollection.find(convertTokenToSessionDocument(token)).first(); + } + + public Object updateSession(SessionToken token, String key, Object value) + { + return mongoCollection.updateOne(convertTokenToSessionDocument(token), new Document("$set", new Document(key, value))); + } + +} \ 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..9a6512bc --- /dev/null +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/RedisSessionStore.java @@ -0,0 +1,109 @@ +package org.finos.legend.server.pac4j.session.store; + +import org.bson.Document; +import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration; +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.UnifiedJedis; +import redis.clients.jedis.json.Path; + +import java.util.Date; +import java.util.Map; +import java.util.concurrent.TimeUnit; + + +public class RedisSessionStore extends SessionStore +{ + private static final String CUSTOM_CONFIG_REDIS_PORT = "port"; + + private static long maxSessionLength; + + private UnifiedJedis jedis; + + public RedisSessionStore(SessionStoreConfiguration config) + { + validateCustomConfiguration(config.getCustomConfigurations()); + + jedis = new JedisPooled(new HostAndPort(config.getDatabaseURI(), + Integer.parseInt(config.getCustomConfigurations().get(CUSTOM_CONFIG_REDIS_PORT)))); + + maxSessionLength = config.getMaxSessionLength(); + } + + private void validateCustomConfiguration(Map customConfigurations) + { + + if (!customConfigurations.containsKey(CUSTOM_CONFIG_REDIS_PORT)) + { + throw new RuntimeException("Redis session store requires 'port' custom attribute to be configured if enabled"); + } + } + + 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()); + + jedis.jsonSet(key, Path.ROOT_PATH, value); //TODO check on transactions + jedis.expire(key, maxSessionLength); + } + + private String 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); + + String result = jedis.jsonSet(sessionId, Path.ROOT_PATH, doc); //TODO check on transactions + jedis.expire(sessionId, maxSessionLength); + + return result; + } + + public Object deleteSession(SessionToken token) + { + return jedis.jsonDel(getSessionIdFromToken(token)); + } + + public Object getDatabase() + { + return jedis; + } + + private String getSessionIdFromToken(SessionToken token) + { + return UuidUtils.toHexString(token.getSessionId()); + } + + public Document getSession(SessionToken token) + { + Object jsonStringResult = jedis.jsonGet(getSessionIdFromToken(token)); + if (jsonStringResult == null) + { + return null; + } + + return new Document((Map) jsonStringResult); + } + + 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); + } + +} \ 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..5f8b2576 --- /dev/null +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStore.java @@ -0,0 +1,62 @@ +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; +import org.finos.legend.server.pac4j.session.utils.SessionToken; + +import java.util.concurrent.TimeUnit; + +public abstract class SessionStore +{ + public static final String SESSION_PROPERTY_ID = "_id"; + protected static final String SESSION_PROPERTY_CREATED = "created"; + + public static SessionStore createInstance(SessionStoreConfiguration config) + { + validateSessionStoreConfiguration(config); + + String type = config.getType(); + + if (SessionStoreTypes.mongoDb.name().equals(type)) + { + return new MongoDbSessionStore(config); + } + else if (SessionStoreTypes.redis.name().equals(type)) + { + return new RedisSessionStore(config); + } + return null; + } + + private static void validateSessionStoreConfiguration(SessionStoreConfiguration config) + { + if (StringUtils.isEmpty(config.getType())) + { + throw new RuntimeException("Session store requires 'type' attribute to be configured if enabled"); + } + + if (StringUtils.isEmpty(config.getDatabaseURI())) + { + throw new RuntimeException("Session store requires 'databaseURI' attribute to be configured if enabled"); + } + } + + public abstract void createIndex(long maxSessionLength, TimeUnit seconds); + + public abstract void createSession(SessionToken token); + + public abstract Object deleteSession(SessionToken token); + + public abstract Object getDatabase(); + + public abstract Document getSession(SessionToken token); + + public abstract Object updateSession(SessionToken token, String key, Object value); + + enum SessionStoreTypes + { + mongoDb, redis + } + +} \ 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..e28c1474 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,9 @@ 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.finos.legend.server.pac4j.session.store.MongoDbSessionStore; import org.junit.Assert; import org.junit.Test; @@ -29,14 +32,18 @@ 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.getCustomConfigurations().put( + MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_DATABASE_NAME, "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.getCustomConfigurations() + .get(MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_DATABASE_NAME)); + Assert.assertEquals("defaultMongoUri", sessionStoreConfiguration.getDatabaseURI()); + Assert.assertEquals("defaultMongoSession", sessionStoreConfiguration.getCustomConfigurations() + .get(MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_COLLECTION)); } } 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..bf99896e 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; @@ -38,133 +42,126 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -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); - - } +public class MongoDbSessionStoreTest { + + 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"); + + //TODO + //store = new SessionContext("AES", 100, db.getCollection("sessionData"), ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages); + store = new SessionContext("AES", 100, new MongoDbSessionStore(null), 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..e1dc8dd6 100644 --- a/pom.xml +++ b/pom.xml @@ -65,12 +65,14 @@ 3.5.10 3.12.8 1.21.0 + 3.4.1 8.0 0.5.0 0.37.2 0.32.0 3.8.3 0.8.1 + 4.3.0 4.3.24.RELEASE 2.3.3.RELEASE 2.4.2 @@ -537,6 +539,13 @@ + + + redis.clients + jedis + ${redis.client.version} + + junit From 5fa7911196b3a0b4d8a0833133a9bb7120c622d3 Mon Sep 17 00:00:00 2001 From: Allen Terleto Date: Mon, 31 Jul 2023 20:10:07 -0400 Subject: [PATCH 2/4] Refactored Configuration so MongoDB and Redis have distinct enumerated fields - Created SessionStoreFactory - Created inner classes to represent MongoDB and Redis Configurations uniquely - Added new abstract method, getSessionAttribute, implemented by both MongoDB and Redis - Changed SessionStore from abstract class to interface - Added transactions within Redis create session so expire is set atomically with key creation --- .../server/pac4j/LegendPac4jBundle.java | 6 +- .../MongoDbSessionAuthority.java | 4 +- .../authorization/RedisSessionAuthority.java | 18 +- .../config/SessionStoreConfiguration.java | 154 ++++++++++++------ .../pac4j/session/context/SessionContext.java | 9 +- .../session/store/MongoDbSessionStore.java | 62 ++++--- .../session/store/RedisSessionStore.java | 80 +++++---- .../pac4j/session/store/SessionStore.java | 56 ++----- .../session/store/SessionStoreFactory.java | 25 +++ .../pac4j/LegendPac4JConfigurationTest.java | 12 +- .../mongostore/MongoDbSessionStoreTest.java | 36 ++-- pom.xml | 3 +- 12 files changed, 269 insertions(+), 196 deletions(-) create mode 100644 legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/store/SessionStoreFactory.java 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 7d360461..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 @@ -24,7 +24,6 @@ import io.dropwizard.setup.Environment; import javax.security.auth.Subject; import javax.servlet.DispatcherType; -import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletHandler; @@ -36,6 +35,7 @@ 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; @@ -139,7 +139,7 @@ public Pac4jFactory getPac4jFactory(C configuration) SessionStoreConfiguration sessionStoreConfiguration = legendConfig.getSessionStoreConfiguration(); if (sessionStoreConfiguration != null && sessionStoreConfiguration.isEnabled()) { - sessionStore = subjectExecutor.execute(() -> SessionStore.createInstance(legendConfig.getSessionStoreConfiguration())); + sessionStore = subjectExecutor.execute(() -> SessionStoreFactory.INSTANCE.getInstance(legendConfig.getSessionStoreConfiguration())); } SessionStore finalSessionStore = sessionStore; @@ -197,7 +197,7 @@ JaxRsContext.class, new ServletSessionStore()), legendConfig.getAuthorizers().stream() .filter(a -> a instanceof SessionConsumer) - .forEach(a -> ((SessionConsumer) a).configureDatabase(finalSessionStore.getDatabase(), sessionStoreConfiguration)); + .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/session/authorization/MongoDbSessionAuthority.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/authorization/MongoDbSessionAuthority.java index 4dc16fe7..c81c9b5b 100644 --- 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 @@ -44,12 +44,12 @@ protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u) Document doc = mongoCollection.find(new Document("_id", id)).first(); if (doc != null) { - logger.debug("Allowing user {} - found in Mongo Collection", id); + logger.debug("Allowing user {} - found in Profile Authorization Store", id); return true; } else { - logger.warn("Disallowing user {} - not found in Mongo Collection", id); + logger.warn("Disallowing user {} - not found in Profile Authorization Store", id); return false; } } 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 index c25740d6..f611136d 100644 --- 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 @@ -18,6 +18,7 @@ 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; @@ -47,12 +48,12 @@ protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u) Document doc = new Document((Map) jedis.jsonGet(id)); if (doc != null) { - logger.debug("Allowing user {} - found in Mongo Collection", id); + logger.debug("Allowing user {} - found in Profile Authorization Store", id); return true; } else { - logger.warn("Disallowing user {} - not found in Mongo Collection", id); + logger.warn("Disallowing user {} - not found in Profile Authorization Store", id); return false; } } @@ -60,12 +61,19 @@ protected boolean isProfileAuthorized(WebContext webContext, CommonProfile u) @Override public void configureDatabase(Object database, SessionStoreConfiguration config) { - if (config == null || StringUtils.isEmpty(config.getDatabaseURI()) || StringUtils.isEmpty(port)) + if (config == null) { - throw new RuntimeException("Redis URI and port name must be specified"); + throw new RuntimeException("Session store configuration is required for session authority"); } - jedis = new JedisPooled(new HostAndPort(config.getDatabaseURI(), Integer.parseInt(port))); + 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/config/SessionStoreConfiguration.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/config/SessionStoreConfiguration.java index f6023ad5..f707cb05 100644 --- 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 @@ -1,32 +1,108 @@ -package org.finos.legend.server.pac4j.session.config; - -import com.google.common.base.Strings; +// 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. -import java.util.HashMap; -import java.util.Map; +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 String type; private boolean enabled; - private String databaseURI; + + private MongodbConfiguration mongodbConfiguration; + private RedisConfiguration redisConfiguration; private String cryptoAlgorithm = DEFAULT_CRYPTO_ALGORITHM; private int maxSessionLength = DEFAULT_MAX_SESSION_LENGTH; - private Map customConfigurations; + 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()); - this.defaultDatabaseURI(other.getDatabaseURI()); } public void defaultCryptoAlgorithm(String cryptoAlgorithm) @@ -37,14 +113,6 @@ public void defaultCryptoAlgorithm(String cryptoAlgorithm) } } - private void defaultDatabaseURI(String databaseURI) - { - if (Strings.isNullOrEmpty(this.databaseURI)) - { - this.databaseURI = databaseURI; - } - } - public void defaultEnabled(boolean enabled) { this.enabled = this.enabled || enabled; @@ -58,30 +126,6 @@ public void defaultMaxSessionLength(int maxSessionLength) } } - public Map getCustomConfigurations() - { - if (customConfigurations == null) - { - customConfigurations = new HashMap<>(); - } - return customConfigurations; - } - - public String getDatabaseURI() - { - return databaseURI; - } - - public void setDatabaseURI(String databaseURI) - { - this.databaseURI = databaseURI; - } - - public String getType() - { - return type; - } - public String getCryptoAlgorithm() { return cryptoAlgorithm; @@ -102,11 +146,6 @@ public void setCryptoAlgorithm(String cryptoAlgorithm) this.cryptoAlgorithm = cryptoAlgorithm; } - public void setCustomConfigurations(Map customConfigurations) - { - this.customConfigurations = customConfigurations; - } - public void setEnabled(boolean enabled) { this.enabled = enabled; @@ -117,9 +156,24 @@ public void setMaxSessionLength(int maxSessionLength) this.maxSessionLength = maxSessionLength; } - public void setType(String type) + public MongodbConfiguration getMongodbConfiguration() + { + return mongodbConfiguration; + } + + public void setMongodbConfiguration(MongodbConfiguration mongodbConfiguration) + { + this.mongodbConfiguration = mongodbConfiguration; + } + + public RedisConfiguration getRedisConfiguration() + { + return redisConfiguration; + } + + public void setRedisConfiguration(RedisConfiguration redisConfiguration) { - this.type = type; + this.redisConfiguration = redisConfiguration; } -} +} \ No newline at end of file diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java index b7955b4b..84c2b099 100644 --- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/session/context/SessionContext.java @@ -14,7 +14,6 @@ package org.finos.legend.server.pac4j.session.context; -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; @@ -32,13 +31,13 @@ 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 SessionContext extends HttpSessionStore //TODO rename + +public class SessionContext extends HttpSessionStore { private static final Logger logger = LoggerFactory.getLogger(SessionContext.class); @@ -128,10 +127,10 @@ public Object get(WebContext context, String key) if (res == null) { final SessionToken token = getOrCreateSsoKey(context); - Document doc = this.subjectExecutor.execute(() -> userSessions.getSession(token)); + 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 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 index e4e72644..78583563 100644 --- 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 @@ -5,47 +5,36 @@ 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; +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.Map; import java.util.concurrent.TimeUnit; -public class MongoDbSessionStore extends SessionStore +public class MongoDbSessionStore implements SessionStore { - public static final String CUSTOM_CONFIG_MONGODB_DATABASE_NAME = "databaseName"; - public static final String CUSTOM_CONFIG_MONGODB_COLLECTION = "collection"; - - private MongoDatabase mongoDB; + private MongoDatabase mongoDatabase; private MongoCollection mongoCollection; - public MongoDbSessionStore(SessionStoreConfiguration config) + public MongoDbSessionStore(MongodbConfiguration mongodbConfiguration) { - validateCustomConfiguration(config.getCustomConfigurations()); + validateConfiguration(mongodbConfiguration); - mongoDB = new MongoClient(new MongoClientURI(config.getDatabaseURI())).getDatabase( - config.getCustomConfigurations().get(CUSTOM_CONFIG_MONGODB_DATABASE_NAME)); + this.mongoDatabase = new MongoClient(new MongoClientURI(mongodbConfiguration.getDatabaseURI())) + .getDatabase(mongodbConfiguration.getDatabaseName()); - mongoCollection = mongoDB.getCollection(config.getCustomConfigurations().get(CUSTOM_CONFIG_MONGODB_COLLECTION)); + this.mongoCollection = mongoDatabase.getCollection(mongodbConfiguration.getCollection()); } - private void validateCustomConfiguration(Map customConfigurations) + public MongoDbSessionStore(MongoDatabase mongoDatabase, String collectionName) { - - if (!customConfigurations.containsKey(CUSTOM_CONFIG_MONGODB_DATABASE_NAME)) - { - throw new RuntimeException("MongoDB session store requires 'databaseName' custom attribute to be configured if enabled"); - } - - if (!customConfigurations.containsKey(CUSTOM_CONFIG_MONGODB_COLLECTION)) - { - throw new RuntimeException("MongoDB session store requires 'collection' custom attribute to be configured if enabled"); - } + this.mongoDatabase = mongoDatabase; + this.mongoCollection = mongoDatabase.getCollection(collectionName); } public void createIndex(long maxSessionLength, TimeUnit timeUnit) @@ -69,9 +58,9 @@ public Object deleteSession(SessionToken token) return mongoCollection.deleteMany(convertTokenToSessionDocument(token)); } - public Object getDatabase() + public Object getDatabaseClient() { - return mongoDB; + return mongoDatabase; } public Document getSession(SessionToken token) @@ -79,9 +68,32 @@ 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 index 9a6512bc..8874643a 100644 --- 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 @@ -1,12 +1,14 @@ 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; +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.UnifiedJedis; +import redis.clients.jedis.Transaction; import redis.clients.jedis.json.Path; import java.util.Date; @@ -14,31 +16,19 @@ import java.util.concurrent.TimeUnit; -public class RedisSessionStore extends SessionStore +public class RedisSessionStore implements SessionStore { - private static final String CUSTOM_CONFIG_REDIS_PORT = "port"; - private static long maxSessionLength; - private UnifiedJedis jedis; + private JedisPooled jedis; - public RedisSessionStore(SessionStoreConfiguration config) + public RedisSessionStore(RedisConfiguration config, long maxSessionLength) { - validateCustomConfiguration(config.getCustomConfigurations()); - - jedis = new JedisPooled(new HostAndPort(config.getDatabaseURI(), - Integer.parseInt(config.getCustomConfigurations().get(CUSTOM_CONFIG_REDIS_PORT)))); + validateConfiguration(config); - maxSessionLength = config.getMaxSessionLength(); - } + jedis = new JedisPooled(new HostAndPort(config.getHostname(), Integer.parseInt(config.getPort()))); - private void validateCustomConfiguration(Map customConfigurations) - { - - if (!customConfigurations.containsKey(CUSTOM_CONFIG_REDIS_PORT)) - { - throw new RuntimeException("Redis session store requires 'port' custom attribute to be configured if enabled"); - } + this.maxSessionLength = maxSessionLength; } public void createIndex(long maxSessionLength, TimeUnit timeUnit) @@ -52,20 +42,26 @@ public void createSession(SessionToken token) Document value = new Document(SESSION_PROPERTY_ID, key).append(SessionStore.SESSION_PROPERTY_CREATED, new Date()); - jedis.jsonSet(key, Path.ROOT_PATH, value); //TODO check on transactions - jedis.expire(key, maxSessionLength); + Transaction transaction = new Transaction(jedis.getPool().getResource()); + + transaction.jsonSet(key, Path.ROOT_PATH, value); + transaction.expire(key, maxSessionLength); + + transaction.exec(); } - private String createSession(String sessionId, String attributeKey, Object attributeValue) + 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); - String result = jedis.jsonSet(sessionId, Path.ROOT_PATH, doc); //TODO check on transactions - jedis.expire(sessionId, maxSessionLength); + Transaction transaction = new Transaction(jedis.getPool().getResource()); - return result; + transaction.jsonSet(sessionId, Path.ROOT_PATH, doc); + transaction.expire(sessionId, maxSessionLength); + + return transaction.exec(); } public Object deleteSession(SessionToken token) @@ -73,25 +69,19 @@ public Object deleteSession(SessionToken token) return jedis.jsonDel(getSessionIdFromToken(token)); } - public Object getDatabase() + public Object getDatabaseClient() { return jedis; } - private String getSessionIdFromToken(SessionToken token) + public Object getSession(SessionToken token) { - return UuidUtils.toHexString(token.getSessionId()); + return jedis.jsonGet(getSessionIdFromToken(token)); } - public Document getSession(SessionToken token) + public String getSessionAttribute(Object document, String attributeKey) { - Object jsonStringResult = jedis.jsonGet(getSessionIdFromToken(token)); - if (jsonStringResult == null) - { - return null; - } - - return new Document((Map) jsonStringResult); + return String.valueOf(((Map) document).get(attributeKey)); } public Object updateSession(SessionToken token, String key, Object value) @@ -106,4 +96,22 @@ public Object updateSession(SessionToken token, String key, Object 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 index 5f8b2576..dee8a223 100644 --- 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 @@ -1,62 +1,26 @@ 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; import org.finos.legend.server.pac4j.session.utils.SessionToken; import java.util.concurrent.TimeUnit; -public abstract class SessionStore +public interface SessionStore { - public static final String SESSION_PROPERTY_ID = "_id"; - protected static final String SESSION_PROPERTY_CREATED = "created"; + String SESSION_PROPERTY_ID = "_id"; + String SESSION_PROPERTY_CREATED = "created"; - public static SessionStore createInstance(SessionStoreConfiguration config) - { - validateSessionStoreConfiguration(config); + void createIndex(long maxSessionLength, TimeUnit seconds); - String type = config.getType(); + void createSession(SessionToken token); - if (SessionStoreTypes.mongoDb.name().equals(type)) - { - return new MongoDbSessionStore(config); - } - else if (SessionStoreTypes.redis.name().equals(type)) - { - return new RedisSessionStore(config); - } - return null; - } + Object deleteSession(SessionToken token); - private static void validateSessionStoreConfiguration(SessionStoreConfiguration config) - { - if (StringUtils.isEmpty(config.getType())) - { - throw new RuntimeException("Session store requires 'type' attribute to be configured if enabled"); - } + Object getDatabaseClient(); - if (StringUtils.isEmpty(config.getDatabaseURI())) - { - throw new RuntimeException("Session store requires 'databaseURI' attribute to be configured if enabled"); - } - } + Object getSession(SessionToken token); - public abstract void createIndex(long maxSessionLength, TimeUnit seconds); + String getSessionAttribute(Object document, String attributeKey); - public abstract void createSession(SessionToken token); - - public abstract Object deleteSession(SessionToken token); - - public abstract Object getDatabase(); - - public abstract Document getSession(SessionToken token); - - public abstract Object updateSession(SessionToken token, String key, Object value); - - enum SessionStoreTypes - { - mongoDb, redis - } + 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/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendPac4JConfigurationTest.java index e28c1474..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 @@ -20,7 +20,6 @@ import java.io.IOException; import org.finos.legend.server.pac4j.session.config.SessionStoreConfiguration; -import org.finos.legend.server.pac4j.session.store.MongoDbSessionStore; import org.junit.Assert; import org.junit.Test; @@ -34,16 +33,13 @@ public void testDefaults() throws IOException, ConfigurationException config.setDefaults("defaults.json"); SessionStoreConfiguration sessionStoreConfiguration = new SessionStoreConfiguration(); - sessionStoreConfiguration.getCustomConfigurations().put( - MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_DATABASE_NAME, "overrideMongoDb"); + sessionStoreConfiguration.getMongodbConfiguration().setDatabaseName("overrideMongoDb"); config.loadDefaults(new ResourceConfigurationSourceProvider(), new ObjectMapper()); - Assert.assertEquals("overrideMongoDb", sessionStoreConfiguration.getCustomConfigurations() - .get(MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_DATABASE_NAME)); - Assert.assertEquals("defaultMongoUri", sessionStoreConfiguration.getDatabaseURI()); - Assert.assertEquals("defaultMongoSession", sessionStoreConfiguration.getCustomConfigurations() - .get(MongoDbSessionStore.CUSTOM_CONFIG_MONGODB_COLLECTION)); + 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 bf99896e..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 @@ -42,15 +42,16 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -public class MongoDbSessionStoreTest { - +public class MongoDbSessionStoreTest +{ private static MongoServer server; private static MongoClient client; private static MongoDatabase db; private SessionContext store; @BeforeClass - public static void setup() { + public static void setup() + { server = new MongoServer(new MemoryBackend()); InetSocketAddress serverAddress = server.bind(); @@ -59,23 +60,25 @@ public static void setup() { } @AfterClass - public static void teardown() { + public static void teardown() + { server.shutdown(); client.close(); } @Before - public void before() { + public void before() + { List testTrustedPackages = new ArrayList<>(); testTrustedPackages.add("test.trusted.package"); - //TODO - //store = new SessionContext("AES", 100, db.getCollection("sessionData"), ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages); - store = new SessionContext("AES", 100, new MongoDbSessionStore(null), ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages); + store = new SessionContext("AES", 100, new MongoDbSessionStore(db, "sessionData"), + ImmutableMap.of(J2EContext.class, new J2ESessionStore()), testTrustedPackages); } @Test - public void testSetCreatesCookie() { + public void testSetCreatesCookie() + { MockHttpServletResponse response = new MockHttpServletResponse(); J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response); store.set(requestContext, "testKey", "testValue"); @@ -90,7 +93,8 @@ public void testSetCreatesCookie() { @Test - public void testMultipleSetsOnlyCreateOneCookie() { + public void testMultipleSetsOnlyCreateOneCookie() + { MockHttpServletResponse response = new MockHttpServletResponse(); J2EContext requestContext = new J2EContext(new MockHttpServletRequest(), response); store.set(requestContext, "testKey", "testValue"); @@ -102,12 +106,14 @@ public void testMultipleSetsOnlyCreateOneCookie() { } @Test - public void testTrustedPackageAdded() { + public void testTrustedPackageAdded() + { assertTrue(this.store.getSerializationHelper().getTrustedPackages().contains("test.trusted.package")); } @Test - public void testSetThenGetFromSession() { + public void testSetThenGetFromSession() + { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); J2EContext requestContext = new J2EContext(request, response); @@ -125,7 +131,8 @@ public void testSetThenGetFromSession() { } @Test - public void testSetThenGetFromMongo() { + public void testSetThenGetFromMongo() + { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); J2EContext requestContext = new J2EContext(request, response); @@ -144,7 +151,8 @@ public void testSetThenGetFromMongo() { } @Test - public void testSimulateCookieExpiryThenGetFromSession() { + public void testSimulateCookieExpiryThenGetFromSession() + { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); J2EContext requestContext = new J2EContext(request, response); diff --git a/pom.xml b/pom.xml index e1dc8dd6..80286489 100644 --- a/pom.xml +++ b/pom.xml @@ -65,14 +65,13 @@ 3.5.10 3.12.8 1.21.0 - 3.4.1 8.0 0.5.0 0.37.2 0.32.0 3.8.3 0.8.1 - 4.3.0 + 4.4.3 4.3.24.RELEASE 2.3.3.RELEASE 2.4.2 From 3002f01fa20d92670ed8a257b90f55fb47be5958 Mon Sep 17 00:00:00 2001 From: Allen Terleto Date: Tue, 1 Aug 2023 14:54:55 -0400 Subject: [PATCH 3/4] Added comments --- .../legend/server/pac4j/session/store/RedisSessionStore.java | 4 ++++ 1 file changed, 4 insertions(+) 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 index 8874643a..998c40ef 100644 --- 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 @@ -42,6 +42,8 @@ public void createSession(SessionToken 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); @@ -56,6 +58,8 @@ private Object createSession(String sessionId, String attributeKey, Object attri .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); From dabffc316f260e86379c3c3671ab8547b81c3dd4 Mon Sep 17 00:00:00 2001 From: Allen Terleto Date: Mon, 17 Jul 2023 17:23:05 -0400 Subject: [PATCH 4/4] Initial Commit Includes missing unit tests and workarounds that will be corrected once contribution is approved following a demo --- .../legend/server/pac4j/session/store/RedisSessionStore.java | 1 - 1 file changed, 1 deletion(-) 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 index 998c40ef..a5b8e1d7 100644 --- 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 @@ -2,7 +2,6 @@ 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.finos.legend.server.pac4j.session.utils.SessionToken; import org.finos.legend.server.pac4j.session.utils.UuidUtils;