diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index eeecd5c8aaa..2b75c45e0cc 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -18,7 +18,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 988b0078371..fa713e19392 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -19,7 +19,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 67f953e4d72..7c96bd6a9b4 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -18,7 +18,7 @@ - + diff --git a/java/com.sap.sse.replication/src/com/sap/sse/replication/Replicable.java b/java/com.sap.sse.replication/src/com/sap/sse/replication/Replicable.java index 6edf84ab50b..9900da8a626 100755 --- a/java/com.sap.sse.replication/src/com/sap/sse/replication/Replicable.java +++ b/java/com.sap.sse.replication/src/com/sap/sse/replication/Replicable.java @@ -83,8 +83,11 @@ public interface Replicable> extends With /** * Before {@link #initiallyFillFrom(ObjectInputStream) initially loading a replica's state from a master instance}, - * the replica's old state needs to be "detached". This method clears all top-level data structures and stops all - * tracking currently going on.

+ * the replica's old state needs to be "detached". This method clears all top-level in-memory data structures and stops all + * tracking currently going on. It may choose to leave any persistent content unchanged as the persistence layer is in + * an undefined state on a replica anyhow. This seems like the safer bet, particularly in case of an accidental + * mis-configuration of the replica's DB connection parameters which may lead to an inadvertent overwriting of the + * master's DB contents.

* * The reason this operation needs to be callable separate from {@link #initiallyFillFrom(ObjectInputStream)} is that * it needs to happen before subscribing to the operation feed received from the master instance through the message bus diff --git a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini index 7c0466025e0..8e7d14f8c4b 100755 --- a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini @@ -1,10 +1,4 @@ - [main] -#shiro.loginUrl = /security/ui/Login.html -#shiro.successUrl = /UserManagement.html -# Use Built-in Chache Manager - - credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher # base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false @@ -19,8 +13,6 @@ securityManager.realms = $upRealm, $oauthRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager -#globalCookie = com.sap.sse.security.GlobalCookie -#securityManager.sessionManager.sessionIdCookie = $globalCookie authc = com.sap.sse.security.CustomFilter authc.loginUrl = /security/ui/Login.html @@ -32,8 +24,6 @@ securityManager.sessionManager.sessionDAO = $sessionDAO cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager -#securityManager.sessionMode = native - [urls] /Login.html = anon /UserManagement.html = authc,roles[admin] diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 4917c55bb69..5f9697bc2c6 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -1,7 +1,7 @@ package com.sap.sse.security.userstore.mongodb; import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; @@ -73,6 +73,34 @@ public class UserStoreImpl implements UserStore { } } + @Override + public void clear() { + preferences.clear(); + emailForUsername.clear(); + settings.clear(); + settingTypes.clear(); + users.clear(); + usersByEmail.clear(); + } + + @Override + public void replaceContentsFrom(UserStore newUserStore) { + clear(); + for (User user : newUserStore.getUsers()) { + users.put(user.getName(), user); + addToUsersByEmail(user); + for (Entry userPref : newUserStore.getAllPreferences(user.getName()).entrySet()) { + setPreference(user.getName(), userPref.getKey(), userPref.getValue()); + } + } + for (Entry setting : newUserStore.getAllSettings().entrySet()) { + settings.put(setting.getKey(), setting.getValue()); + } + for (Entry> settingType : newUserStore.getAllSettingTypes().entrySet()) { + settingTypes.put(settingType.getKey(), settingType.getValue()); + } + } + private void addToUsersByEmail(User u) { if (u.getEmail() != null && !u.getEmail().isEmpty()) { Set set = usersByEmail.get(u.getEmail()); @@ -141,7 +169,7 @@ public class UserStoreImpl implements UserStore { } @Override - public Collection getUserCollection() { + public Iterable getUsers() { return new ArrayList(users.values()); } @@ -290,6 +318,18 @@ public class UserStoreImpl implements UserStore { return result; } + @Override + public Map getAllPreferences(String username) { + final Map userPrefs = preferences.get(username); + final Map result; + if (userPrefs == null) { + result = Collections.emptyMap(); + } else { + result = Collections.unmodifiableMap(userPrefs); + } + return result; + } + @Override public Map getAllSettings() { return settings; @@ -299,17 +339,4 @@ public class UserStoreImpl implements UserStore { public Map> getAllSettingTypes() { return settingTypes; } - - @Override - public void clear() { - preferences.clear(); - emailForUsername.clear(); - settings.clear(); - settingTypes.clear(); - users.clear(); - usersByEmail.clear(); - mongoObjectFactory.clearAllPreferences(); - mongoObjectFactory.clearAllUsers(); - mongoObjectFactory.clearAllSettings(); - } } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index ba281c2ad33..cc3b00857ab 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -1,6 +1,5 @@ package com.sap.sse.security; -import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -22,7 +21,7 @@ public interface SecurityService extends ReplicableWithObjectInputStream getUserList(); + Iterable getUserList(); User getUserByName(String username); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SessionCacheManager.java b/java/com.sap.sse.security/src/com/sap/sse/security/SessionCacheManager.java index b3c314a4e0b..40a933a83e8 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SessionCacheManager.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SessionCacheManager.java @@ -3,15 +3,16 @@ package com.sap.sse.security; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.mgt.SecurityManager; import com.sap.sse.security.impl.Activator; import com.sap.sse.security.impl.ReplicableSecurityService; -import com.sap.sse.security.impl.ReplicatingCache; /** - * Obtains the singleton cache manager instance from the singleton {@link SecurityService} instance. The cache manager - * returned will replicate all modifying operations to the {@link SecurityService} in any replica listening. Using an instance - * of this class as the cache manager in your Shiro configuration as in + * A {@link CacheManager} implementation that can be used in a shiro.ini configuration for a + * {@link SecurityManager}. {@link SecurityService#getCacheManager() Obtains} the singleton cache manager instance from + * the singleton {@link SecurityService} instance. Using an instance of this class as the cache manager in your + * Shiro configuration as in * *

  *      sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
@@ -25,16 +26,19 @@ import com.sap.sse.security.impl.ReplicatingCache;
  * 
  * @see SecurityService#getCacheManager()
  * 
- * @author Benjamin Ebling
+ * @author Benjamin Ebling, Axel Uhl
  *
  */
 public class SessionCacheManager implements CacheManager {
     @Override
     public  Cache getCache(String name) throws CacheException {
         final ReplicableSecurityService securityService = (ReplicableSecurityService) Activator.getSecurityService();
+        final Cache result;
         if (securityService == null) {
-            return null;
+            result = null;
+        } else {
+            result = securityService.getCacheManager().getCache(name);
         }
-        return new ReplicatingCache(securityService, name, securityService.getCacheManager().getCache(name));
+        return result;
     }
 }
diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java
index db5dad1e539..b1d15c87ce5 100644
--- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java
+++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java
@@ -1,6 +1,5 @@
 package com.sap.sse.security;
 
-import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -10,7 +9,7 @@ import com.sap.sse.security.shared.Account;
 import com.sap.sse.security.shared.UserManagementException;
 
 public interface UserStore extends Named {
-    Collection getUserCollection();
+    Iterable getUsers();
 
     User getUserByName(String name);
 
@@ -38,6 +37,11 @@ public interface UserStore extends Named {
     
     void setPreference(String username, String key, String value);
     
+    /**
+     * Always returns a valid map which may be empty.
+     */
+    Map getAllPreferences(String username);
+    
     void unsetPreference(String username, String key);
 
     String getPreference(String username, String key);
@@ -62,12 +66,20 @@ public interface UserStore extends Named {
      T getSetting(String key, Class clazz);
 
     Map getAllSettings();
-
+    
     Map> getAllSettingTypes();
 
     /**
-     * Removes all users and all their preferences and all settings from this store. Use with due care.
+     * Removes all users and all their preferences and all settings from this store's in-memory representation.
+     * For safety reasons and because a replica's DB state is undefined anyhow, leaves persistent content in place.
+     * Use with due care.
      */
     void clear();
 
+    /**
+     * Replaces all existing contents by those provided by the newUserStore. This has no impact on the persistent
+     * representation of this store and is meant for use on a replica only; the replica's database state is undefined.
+     */
+    void replaceContentsFrom(UserStore newUserStore);
+
 }
diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCache.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCache.java
index 91cf6710349..b13177bb20d 100755
--- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCache.java
+++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCache.java
@@ -1,6 +1,7 @@
 package com.sap.sse.security.impl;
 
 import java.util.Collection;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.shiro.cache.Cache;
@@ -19,9 +20,9 @@ public class ReplicatingCache implements Cache, Named {
     private static final long serialVersionUID = 6628512191363526330L;
     private final ReplicableSecurityService securityService;
     private final String name;
-    private final Cache cache;
+    private final Map cache;
 
-    public ReplicatingCache(ReplicableSecurityService securityService, String name, Cache cache) {
+    public ReplicatingCache(ReplicableSecurityService securityService, String name, Map cache) {
         super();
         this.securityService = securityService;
         this.name = name;
@@ -72,7 +73,7 @@ public class ReplicatingCache implements Cache, Named {
 
     @Override
     public Set keys() {
-        return cache.keys();
+        return cache.keySet();
     }
 
     @Override
diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCacheManager.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCacheManager.java
new file mode 100755
index 00000000000..80dfd967529
--- /dev/null
+++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicatingCacheManager.java
@@ -0,0 +1,46 @@
+package com.sap.sse.security.impl;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.CacheManager;
+
+/**
+ * Wraps a {@link CacheManager} and makes all modifying operations replicate their changes. This happens by wrapping
+ * all {@link Cache}s by an object that intercepts the writing operations, producing replicating operations.
+ * 
+ * @author Axel Uhl (D043530)
+ *
+ */
+public class ReplicatingCacheManager implements CacheManager, Serializable {
+    private static final long serialVersionUID = -8035346668009900228L;
+    private ConcurrentHashMap> caches;
+    
+    public ReplicatingCacheManager() {
+        this.caches = new ConcurrentHashMap<>();
+    }
+    
+    @Override
+    public  Cache getCache(String name) throws CacheException {
+        final ReplicableSecurityService securityService = (ReplicableSecurityService) Activator.getSecurityService();
+        if (securityService == null) {
+            return null;
+        }
+        ConcurrentHashMap cache = this.caches.get(name);
+        if (cache == null) {
+            cache = new ConcurrentHashMap<>();
+            caches.put(name, cache);
+        }
+        @SuppressWarnings("unchecked")
+        final Map castCache = (Map) cache;
+        ReplicatingCache result = new ReplicatingCache(securityService, name, castCache);
+        return result;
+    }
+    
+    public void replaceContentsFrom(ReplicatingCacheManager other) {
+        this.caches = other.caches;
+    }
+}
diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
index 1bb8144568c..1e2eeb21ce6 100644
--- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
+++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
@@ -9,7 +9,6 @@ import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URLEncoder;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -38,12 +37,12 @@ import org.apache.shiro.authc.LockedAccountException;
 import org.apache.shiro.authc.UnknownAccountException;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.cache.CacheManager;
-import org.apache.shiro.cache.ehcache.EhCacheManager;
 import org.apache.shiro.config.Ini;
 import org.apache.shiro.config.Ini.Section;
 import org.apache.shiro.crypto.RandomNumberGenerator;
 import org.apache.shiro.crypto.SecureRandomNumberGenerator;
 import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.apache.shiro.mgt.CachingSecurityManager;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.Factory;
@@ -78,6 +77,7 @@ import com.sap.sse.security.GithubApi;
 import com.sap.sse.security.InstagramApi;
 import com.sap.sse.security.OAuthRealm;
 import com.sap.sse.security.OAuthToken;
+import com.sap.sse.security.SessionCacheManager;
 import com.sap.sse.security.SessionUtils;
 import com.sap.sse.security.Social;
 import com.sap.sse.security.SocialSettingsKeys;
@@ -96,8 +96,15 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
 
     private static final Logger logger = Logger.getLogger(SecurityServiceImpl.class.getName());
 
-    private SecurityManager securityManager;
-    private final CacheManager cacheManager = new EhCacheManager();
+    private CachingSecurityManager securityManager;
+    
+    /**
+     * A cache manager that the {@link SessionCacheManager} delegates to. This way, multiple Shiro configurations can
+     * share the cache manager provided as a singleton within this bundle instance. The cache manager is replicating,
+     * forwarding changes to the caches to all replicas registered.
+     */
+    private final ReplicatingCacheManager cacheManager;
+    
     private UserStore store;
     private final Properties mailProperties;
     private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners;
@@ -114,11 +121,12 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
     public SecurityServiceImpl(UserStore store, Properties mailProperties) {
         assert mailProperties != null;
         logger.info("Initializing Security Service with user store " + store+" and mail properties "+mailProperties);
+        cacheManager = new ReplicatingCacheManager();
         this.operationExecutionListeners = new ConcurrentHashMap<>();
         this.store = store;
         this.mailProperties = mailProperties;
         // Create default users if no users exist yet.
-        if (store.getUserCollection().isEmpty()) {
+        if (Util.isEmpty(store.getUsers())) {
             try {
                 logger.info("No users found, creating default user \"admin\" with password \"admin\"");
                 createSimpleUser("admin", "nobody@sapsailing.com", "admin", /* validationBaseURL */ null);
@@ -141,7 +149,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
         }
         logger.info(logMessage.toString());
         System.setProperty("java.net.useSystemProxies", "true");
-        SecurityManager securityManager = factory.getInstance();
+        CachingSecurityManager securityManager = (CachingSecurityManager) factory.getInstance();
         logger.info("Created: " + securityManager);
         SecurityUtils.setSecurityManager(securityManager);
         this.securityManager = securityManager;
@@ -226,13 +234,13 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
     }
 
     @Override
-    public SecurityManager getSecurityManager() {
+    public CachingSecurityManager getSecurityManager() {
         return this.securityManager;
     }
 
     @Override
-    public Collection getUserList() {
-        return store.getUserCollection();
+    public Iterable getUserList() {
+        return store.getUsers();
     }
 
     @Override
@@ -852,6 +860,8 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
     @Override
     public void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException,
             InterruptedException {
+        ReplicatingCacheManager newCacheManager = (ReplicatingCacheManager) is.readObject();
+        cacheManager.replaceContentsFrom(newCacheManager);
         // overriding thread context class loader because the user store may be provided by a different bundle;
         // We're assuming here that the user store service is provided by the same bundle in the replica as on the master.
         ClassLoader oldCCL = Thread.currentThread().getContextClassLoader();
@@ -860,7 +870,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
         }
         try {
             UserStore newUserStore = (UserStore) is.readObject();
-            store = newUserStore;
+            store.replaceContentsFrom(newUserStore);
         } finally {
             Thread.currentThread().setContextClassLoader(oldCCL);
         }
@@ -868,6 +878,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica
 
     @Override
     public void serializeForInitialReplicationInternal(ObjectOutputStream objectOutputStream) throws IOException {
+        objectOutputStream.writeObject(cacheManager);
         objectOutputStream.writeObject(store);
     }