diff --git a/java/com.sap.sse.replication.interfaces/src/com/sap/sse/replication/OperationWithResult.java b/java/com.sap.sse.replication.interfaces/src/com/sap/sse/replication/OperationWithResult.java
index a3a698749c0..f85aacbfdba 100755
--- a/java/com.sap.sse.replication.interfaces/src/com/sap/sse/replication/OperationWithResult.java
+++ b/java/com.sap.sse.replication.interfaces/src/com/sap/sse/replication/OperationWithResult.java
@@ -34,7 +34,7 @@ public interface OperationWithResult extends Operation, Serializable {
* Tells whether this operation requires explicit transitive replication to other replicas when received by a
* replica. This is the case for all operations whose {@link #internalApplyTo(Object)} method will not trigger
* replication. An example for an operation that does not require explicit transitive replication is the
- * insertion of a GPS fix into a competitor's track because the insertion of the track, as implemented by
+ * insertion of a GPS fix into a competitor's track because the insertion of the fix, as implemented by
* {@link #internalApplyTo} will trigger the replication.
*
*
diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/BasicUserStore.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/BasicUserStore.java
new file mode 100755
index 00000000000..dae842e6ee7
--- /dev/null
+++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/BasicUserStore.java
@@ -0,0 +1,170 @@
+package com.sap.sse.security.shared;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import com.sap.sse.common.Named;
+import com.sap.sse.common.Util.Pair;
+import com.sap.sse.security.shared.impl.Ownership;
+import com.sap.sse.security.shared.impl.Role;
+import com.sap.sse.security.shared.impl.User;
+import com.sap.sse.security.shared.impl.UserGroup;
+import com.sap.sse.security.shared.impl.UserGroupImpl;
+
+/**
+ * Keeps track of all {@link User}, {@link UserGroupImpl} and {@link Role}
+ * objects persistently; furthermore, aspects such as user access tokens, preferences and
+ * settings are stored durably.
+ *
+ * @author Axel Uhl (d043530)
+ *
+ */
+public interface BasicUserStore extends UserGroupProvider, Named {
+ Iterable getUserGroups();
+
+ UserGroup getUserGroup(UUID groupId);
+
+ UserGroup getUserGroupByName(String name);
+
+ UserGroup createUserGroup(UUID groupId, String name) throws UserGroupManagementException;
+
+ void updateUserGroup(UserGroup userGroup);
+
+ void deleteUserGroup(UserGroup userGroup) throws UserGroupManagementException;
+
+ Iterable getUsers();
+
+ boolean hasUsers();
+
+ /**
+ * The user with that {@link UserImpl#getName() name} or {@code null} if no such user exists
+ */
+ User getUserByName(String username);
+
+ /**
+ * The user with that {@link UserImpl#getEmail() email} or {@code null} if no such user exists
+ */
+ User getUserByEmail(String email);
+
+ User getUserByAccessToken(String accessToken);
+
+ User createUser(String name, String email, Account... accounts)
+ throws UserManagementException;
+
+ void updateUser(User user);
+
+ Iterable getRolesFromUser(String username) throws UserManagementException;
+
+ void addRoleForUser(String username, Role role) throws UserManagementException;
+
+ void removeRoleFromUser(String username, Role role) throws UserManagementException;
+
+ Iterable getPermissionsFromUser(String username) throws UserManagementException;
+
+ void removePermissionFromUser(String username, WildcardPermission permission) throws UserManagementException;
+
+ void addPermissionForUser(String username, WildcardPermission permission) throws UserManagementException;
+
+ void deleteUser(String username) throws UserManagementException;
+
+ Iterable getRoleDefinitions();
+ RoleDefinition getRoleDefinition(UUID roleDefinitionId);
+ RoleDefinition createRoleDefinition(UUID roleDefinitionId, String displayName, Iterable permissions);
+ void setRoleDefinitionPermissions(UUID roleDefinitionId, Set permissions);
+ void addRoleDefinitionPermission(UUID roleDefinitionId, WildcardPermission permission);
+ void removeRoleDefinitionPermission(UUID roleDefinitionId, WildcardPermission permission);
+ void setRoleDefinitionDisplayName(UUID roleDefinitionId, String displayName);
+ void removeRoleDefinition(RoleDefinition roleDefinition);
+
+ /**
+ * Registers a settings key together with its type. Calling this method is necessary for {@link #setSetting(String, Object)}
+ * to have an effect for key. Calls to {@link #setSetting(String, Object)} will only accept values whose type
+ * is compatible with type. Note that the store implementation may impose constraints on the types supported.
+ * All store implementations are required to support at least {@link String} and {@link UUID} as types.
+ */
+ void addSetting(String key, Class> type);
+
+ 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);
+
+ /**
+ * Sets a value for a key if that key was previously added to this store using {@link #addSetting(String, Class)}.
+ * For user store implementations that maintain their data persistently and make it available after a server
+ * restart, it is sufficient to register the settings key once because these registrations will be stored
+ * persistently, too.
+ *
+ *
+ * If the key was not registered before by a call to {@link #addSetting(String, Class)}, or if the
+ * setting object does not conform with the type passed to {@link #addSetting(String, Class)}, a call
+ * to this method will have no effect and return false.
+ *
+ * @Return whether applying the setting was successful; false means that no update was performed to the
+ * setting because either the key was not registered before by {@link #addSetting(String, Class)} or the type of the
+ * setting object does not conform to the type used in {@link #addSetting(String, Class)}
+ */
+ boolean setSetting(String key, Object setting);
+
+ T getSetting(String key, Class clazz);
+
+ Map getAllSettings();
+
+ Map> getAllSettingTypes();
+
+ /**
+ * 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.
+ * Registered listeners will not be removed automatically.
+ * Use with due care.
+ */
+ void clear();
+
+ /**
+ * Stores an access token that can be used to authenticate the user identified by username.
+ * If there is no user by that name, calling this method has no effect and it will return false.
+ *
+ * @return whether a user could be identified by username
+ */
+ boolean setAccessToken(String username, String accessToken);
+
+ void removeAccessToken(String username);
+
+ /**
+ * The owner and any subject having the {@link DefaultRoles#ADMIN} role can retrieve an existing
+ * authentication token for the user. {@code null} may result in case for the user identified by
+ * {@code username} no access token has previously been {@link #setAccessToken(String, String) set}.
+ */
+ String getAccessToken(String username);
+
+ /**
+ * If a valid default tenant name was passed to the constructor, this field will contain a valid
+ * {@link UserGroupImpl} object whose name equals that of the default tenant name. It will have been used
+ * during role migration where string-based roles are mapped to a corresponding {@link RoleDefinition}
+ * and the users with the original role will obtain a corresponding {@link Role} with this default
+ * tenant as the {@link Role#getQualifiedForTenant() tenant qualifier}.
+ */
+ UserGroup getDefaultTenant();
+
+ /**
+ * Ensures that the predefined role definitions, particularly the "admin" and the "user" role, exist.
+ */
+ void ensureDefaultRolesExist();
+
+ /**
+ * @return a pair with:
+ * If A is true, at least one user has an unqualified version of the {@link #roleToCheck} (without tenant or
+ * user qualification). In this case, B is null.
+ * If A is false, B contains all the ownerships of {@link #roleToCheck}
+ */
+ Pair> getExistingQualificationsForRoleDefinition(RoleDefinition roleToCheck);
+
+ Set> getRolesQualifiedByUserGroup(UserGroup groupQualification);
+}
diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleDefinition.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleDefinition.java
index 898041a656f..3dfbb85cd8e 100755
--- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleDefinition.java
+++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleDefinition.java
@@ -5,6 +5,8 @@ import java.util.UUID;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.Renamable;
+import com.sap.sse.security.shared.impl.Ownership;
+import com.sap.sse.security.shared.impl.Role;
/**
* A role definition provides an ID, a (changeable) name and a set of permissions. As such, it represents a group of
diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/User.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/User.java
index 3055f098c80..186f6ab105c 100755
--- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/User.java
+++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/User.java
@@ -51,7 +51,12 @@ public interface User extends SecurityUser {
Map getAllAccounts();
- String setEmail(String email);
+ /**
+ * Sets an e-mail address for this user. The address is considered not yet validated, therefore the
+ * caller shall also ensure that {@link #startEmailValidation} is invoked hereafter, with a secret
+ * produced by {@link #createRandomSecret()}.
+ */
+ void setEmail(String email);
/**
* When someone has requested a password reset, only the owner of the validated e-mail address is
@@ -61,9 +66,14 @@ public interface User extends SecurityUser {
*/
String getPasswordResetSecret();
- String startPasswordReset();
+ void startPasswordReset(String randomSecret);
- String startEmailValidation();
+ /**
+ * Resets the {@link #isEmailValidated()} property and stores the new {@code randomSecret} as the
+ * e-mail validation secret. The {@link #isEmailValidated()} method will return {@code true} only
+ * after {@link #validate(String)} has been called with the {@code randomSecret} passed here.
+ */
+ void startEmailValidation(String randomSecret);
boolean validate(String validationSecret);
@@ -99,4 +109,6 @@ public interface User extends SecurityUser {
void setUserGroupProvider(UserGroupProvider userGroupProvider);
UserGroupProvider getUserGroupProvider();
+
+ String createRandomSecret();
}
diff --git a/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserImpl.java b/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserImpl.java
index eeb6a1e03f8..8b92cf2d2fe 100644
--- a/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserImpl.java
+++ b/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserImpl.java
@@ -225,15 +225,9 @@ public class UserImpl extends SecurityUserImplvalidationSecret passed matches {@link #validationSecret}, the e-mail is
- * {@link #emailValidated marked as validated}, and true is returned. Otherwise, the validation secret
- * on this user remains in place, and the e-mail address is not marked as validated.
+ * If the user's e-mail has already been {@link #isEmailValidated() validated}, or the validationSecret
+ * passed matches {@link #validationSecret}, the e-mail is {@link #emailValidated marked as validated}, and
+ * true is returned. Otherwise, the validation secret on this user remains in place, and the e-mail
+ * address is not marked as validated.
*/
@Override
public boolean validate(final String validationSecret) {
diff --git a/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserStore.java b/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserStore.java
index bddb1cbf919..5bac9a22b71 100644
--- a/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserStore.java
+++ b/java/com.sap.sse.security.interface/src/com/sap/sse/security/interfaces/UserStore.java
@@ -1,21 +1,10 @@
package com.sap.sse.security.interfaces;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import com.sap.sse.common.Named;
-import com.sap.sse.common.Util.Pair;
-import com.sap.sse.security.shared.Account;
-import com.sap.sse.security.shared.RoleDefinition;
+import com.sap.sse.security.shared.BasicUserStore;
import com.sap.sse.security.shared.UserGroupManagementException;
-import com.sap.sse.security.shared.UserGroupProvider;
import com.sap.sse.security.shared.UserManagementException;
-import com.sap.sse.security.shared.WildcardPermission;
-import com.sap.sse.security.shared.impl.Ownership;
import com.sap.sse.security.shared.impl.Role;
import com.sap.sse.security.shared.impl.User;
-import com.sap.sse.security.shared.impl.UserGroup;
import com.sap.sse.security.shared.impl.UserGroupImpl;
/**
@@ -26,7 +15,7 @@ import com.sap.sse.security.shared.impl.UserGroupImpl;
* @author Axel Uhl (d043530)
*
*/
-public interface UserStore extends UserGroupProvider, Named {
+public interface UserStore extends BasicUserStore {
/**
* An instance of the bundle hosting this service may have a default tenant. If so, the default tenant's name is
* read from a system property whose name is provided by this constant.
@@ -34,82 +23,6 @@ public interface UserStore extends UserGroupProvider, Named {
String DEFAULT_TENANT_NAME_PROPERTY_NAME = "security.defaultTenantName";
String ADMIN_USERNAME = "admin";
-
- Iterable getUserGroups();
-
- UserGroup getUserGroup(UUID groupId);
-
- UserGroup getUserGroupByName(String name);
-
- UserGroup createUserGroup(UUID groupId, String name) throws UserGroupManagementException;
-
- void updateUserGroup(UserGroup userGroup);
-
- void deleteUserGroup(UserGroup userGroup) throws UserGroupManagementException;
-
- Iterable getUsers();
-
- boolean hasUsers();
-
- /**
- * The user with that {@link UserImpl#getName() name} or {@code null} if no such user exists
- */
- User getUserByName(String username);
-
- /**
- * The user with that {@link UserImpl#getEmail() email} or {@code null} if no such user exists
- */
- User getUserByEmail(String email);
-
- User getUserByAccessToken(String accessToken);
-
- User createUser(String name, String email, Account... accounts)
- throws UserManagementException;
-
- void updateUser(User user);
-
- Iterable getRolesFromUser(String username) throws UserManagementException;
-
- void addRoleForUser(String username, Role role) throws UserManagementException;
-
- void removeRoleFromUser(String username, Role role) throws UserManagementException;
-
- Iterable getPermissionsFromUser(String username) throws UserManagementException;
-
- void removePermissionFromUser(String username, WildcardPermission permission) throws UserManagementException;
-
- void addPermissionForUser(String username, WildcardPermission permission) throws UserManagementException;
-
- void deleteUser(String username) throws UserManagementException;
-
- Iterable getRoleDefinitions();
- RoleDefinition getRoleDefinition(UUID roleDefinitionId);
- RoleDefinition createRoleDefinition(UUID roleDefinitionId, String displayName, Iterable permissions);
- void setRoleDefinitionPermissions(UUID roleDefinitionId, Set permissions);
- void addRoleDefinitionPermission(UUID roleDefinitionId, WildcardPermission permission);
- void removeRoleDefinitionPermission(UUID roleDefinitionId, WildcardPermission permission);
- void setRoleDefinitionDisplayName(UUID roleDefinitionId, String displayName);
- void removeRoleDefinition(RoleDefinition roleDefinition);
-
- /**
- * Registers a settings key together with its type. Calling this method is necessary for {@link #setSetting(String, Object)}
- * to have an effect for key. Calls to {@link #setSetting(String, Object)} will only accept values whose type
- * is compatible with type. Note that the store implementation may impose constraints on the types supported.
- * All store implementations are required to support at least {@link String} and {@link UUID} as types.
- */
- void addSetting(String key, Class> type);
-
- 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);
-
/**
*
* In an OSGi environment, this shouldn't be called manually, but instead automatically managed by setting a
@@ -164,88 +77,16 @@ public interface UserStore extends UserGroupProvider, Named {
*/
String setPreferenceObject(String username, String key, Object preferenceObject) throws IllegalArgumentException;
- /**
- * Sets a value for a key if that key was previously added to this store using {@link #addSetting(String, Class)}.
- * For user store implementations that maintain their data persistently and make it available after a server
- * restart, it is sufficient to register the settings key once because these registrations will be stored
- * persistently, too.
- *
- *
- * If the key was not registered before by a call to {@link #addSetting(String, Class)}, or if the
- * setting object does not conform with the type passed to {@link #addSetting(String, Class)}, a call
- * to this method will have no effect and return false.
- *
- * @Return whether applying the setting was successful; false means that no update was performed to the
- * setting because either the key was not registered before by {@link #addSetting(String, Class)} or the type of the
- * setting object does not conform to the type used in {@link #addSetting(String, Class)}
- */
- boolean setSetting(String key, Object setting);
-
- T getSetting(String key, Class clazz);
-
- Map getAllSettings();
-
- Map> getAllSettingTypes();
-
- /**
- * 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.
- * Registered listeners will not be removed automatically.
- * 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);
-
- /**
- * Stores an access token that can be used to authenticate the user identified by username.
- * If there is no user by that name, calling this method has no effect and it will return false.
- *
- * @return whether a user could be identified by username
- */
- boolean setAccessToken(String username, String accessToken);
-
- void removeAccessToken(String username);
-
- /**
- * The owner and any subject having the {@link DefaultRoles#ADMIN} role can retrieve an existing
- * authentication token for the user. {@code null} may result in case for the user identified by
- * {@code username} no access token has previously been {@link #setAccessToken(String, String) set}.
- */
- String getAccessToken(String username);
void addPreferenceObjectListener(String key, PreferenceObjectListener> listener, boolean fireForAlreadyExistingPreferences);
void removePreferenceObjectListener(PreferenceObjectListener> listener);
- /**
- * If a valid default tenant name was passed to the constructor, this field will contain a valid
- * {@link UserGroupImpl} object whose name equals that of the default tenant name. It will have been used
- * during role migration where string-based roles are mapped to a corresponding {@link RoleDefinition}
- * and the users with the original role will obtain a corresponding {@link Role} with this default
- * tenant as the {@link Role#getQualifiedForTenant() tenant qualifier}.
- */
- UserGroup getDefaultTenant();
-
- /**
- * Ensures that the predefined role definitions, particularly the "admin" and the "user" role, exist.
- */
- void ensureDefaultRolesExist();
-
- /**
- * @return a pair with:
- * If A is true, at least one user has an unqualified version of the {@link #roleToCheck} (without tenant or
- * user qualification). In this case, B is null.
- * If A is false, B contains all the ownerships of {@link #roleToCheck}
- */
- Pair> getExistingQualificationsForRoleDefinition(RoleDefinition roleToCheck);
-
- Set> getRolesQualifiedByUserGroup(UserGroup groupQualification);
-
/**
* Do not call this before the RolePrototypes are created/loaded, as else a migration cannot succeed. But do call
* this before the SecurityService is created, as else new defaults (eg admin user) will be created
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 acbb8fa86d8..42c8e37df5e 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
@@ -694,10 +694,6 @@ public class UserStoreImpl implements UserStore {
@Override
public void updateUser(User user) {
logger.info("Updating user " + user + " in DB");
- if (user.getUserGroupProvider() != this) {
- logger.info("Adjusting user group provider for user "+user+"; probably after de-serialization.");
- user.setUserGroupProvider(this);
- }
users.put(user.getName(), user);
removeFromUsersByEmail(user);
addToUsersByEmail(user);
diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java
index 491f40485ac..0e1bea4f4cf 100644
--- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java
+++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java
@@ -207,7 +207,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
dbPermissions.add(permission.toString());
}
dbUser.put(FieldNames.User.PERMISSIONS.name(), dbPermissions);
-
List