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 defaultTennants = new BasicDBList(); for (Entry entries : user.getDefaultTenantMap().entrySet()) { Document tenant = new Document(); diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/UserProxy.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/UserProxy.java index a4e073b0aa0..3bd59671d74 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/UserProxy.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/UserProxy.java @@ -124,7 +124,7 @@ public class UserProxy implements User { } @Override - public String setEmail(String email) { + public void setEmail(String email) { throw new UnsupportedOperationException(); } @@ -134,12 +134,12 @@ public class UserProxy implements User { } @Override - public String startPasswordReset() { + public void startPasswordReset(String randomSecret) { throw new UnsupportedOperationException(); } @Override - public String startEmailValidation() { + public void startEmailValidation(String randomSecret) { throw new UnsupportedOperationException(); } @@ -202,4 +202,9 @@ public class UserProxy implements User { public UserGroupProvider getUserGroupProvider() { throw new UnsupportedOperationException(); } + + @Override + public String createRandomSecret() { + throw new UnsupportedOperationException(); + } } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java index 3dc61e62282..c439f7a6442 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java @@ -117,7 +117,6 @@ public class Activator implements BundleActivator { if (cm instanceof ReplicatingCacheManager) { ((ReplicatingCacheManager) cm).clear(); } - UserStore userStore = userStoreTracker.waitForService(0); AccessControlStore accessControlStore = accessControlStoreTracker.waitForService(0); userStore.clear(); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java index b60a52a17bf..d63954dd147 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java @@ -1,11 +1,13 @@ package com.sap.sse.security.impl; +import java.util.Locale; import java.util.Set; import java.util.UUID; import org.apache.shiro.session.Session; import com.sap.sse.security.SecurityService; +import com.sap.sse.security.shared.Account; import com.sap.sse.security.shared.QualifiedObjectIdentifier; import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.UserGroupManagementException; @@ -51,7 +53,19 @@ public interface ReplicableSecurityService extends SecurityService { Void internalRemoveRoleDefinitionFromUserGroup(UUID groupId, UUID roleDefinitionId) throws UserGroupManagementException; - Void internalStoreUser(User user); + /** + * Creates and stores a new user in the system. Don't use this to update an existing user. + * Use other {@code internal...} methods to update individual user properties instead. + */ + User internalCreateUser(String username, String email, Account... accounts) throws UserManagementException; + + Void internalUpdateSimpleUserEmail(String username, String newEmail, String validationSecret); + + Void internalUpdateSimpleUserPassword(String username, byte[] salt, String hashedPasswordBase64); + + Void internalUpdateUserProperties(User user, String fullName, String company, Locale locale); + + Boolean internalValidateEmail(String username, String validationSecret); Void internalSetPreference(String username, String key, String value); @@ -94,4 +108,6 @@ public interface ReplicableSecurityService extends SecurityService { Void internalSetDefaultTenantForServerForUser(String username, UUID defaultTenantId, String serverName); + Void internalResetPassword(String username, String passwordResetSecret); + } 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 92b8ba036ed..a5476c0fa31 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 @@ -316,7 +316,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat if (store.getUserByName(SecurityService.ALL_USERNAME) == null) { isInitialOrMigration = true; logger.info(SecurityService.ALL_USERNAME + " not found -> creating it now"); - User allUser = createUserInternal(SecurityService.ALL_USERNAME, null); + User allUser = apply(s->s.internalCreateUser(SecurityService.ALL_USERNAME, null)); // user is explicitly not owned by itself because this would enable anybody to modify this user setOwnership(allUser.getIdentifier(), null, getDefaultTenant()); @@ -369,8 +369,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat if (!user.isEmailValidated()) { throw new UserManagementException(UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL); } - final String passwordResetSecret = user.startPasswordReset(); - apply(s->s.internalStoreUser(user)); // durably storing the password reset secret + final String passwordResetSecret = user.createRandomSecret(); + apply(s->s.internalResetPassword(username, passwordResetSecret)); Map urlParameters = new HashMap<>(); try { urlParameters.put("u", URLEncoder.encode(user.getName(), "UTF-8")); @@ -397,6 +397,12 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat } } + @Override + public Void internalResetPassword(String username, String passwordResetSecret) { + getUserByName(username).startPasswordReset(passwordResetSecret); + return null; + } + @Override public CachingSecurityManager getSecurityManager() { return this.securityManager; @@ -784,7 +790,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat @Override public User createSimpleUser(final String username, final String email, String password, String fullName, - String company, Locale locale, final String validationBaseURL, UserGroup userOwner) + String company, Locale locale, final String validationBaseURL, UserGroup groupOwningUser) throws UserManagementException, MailException, UserGroupManagementException { logger.info("Creating user "+username); if (store.getUserByName(username) != null) { @@ -801,35 +807,15 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat byte[] salt = rng.nextBytes().getBytes(); String hashedPasswordBase64 = hashPassword(password, salt); UsernamePasswordAccount upa = new UsernamePasswordAccount(username, hashedPasswordBase64, salt); - final User result = createUserInternal(username, email, upa); + final User result = apply(s->s.internalCreateUser(username, email, upa)); // This also replicated the user creation addUserRoleToUser(result); - final UserGroup tenant = getOrCreateTenantForUser(result); setDefaultTenantForCurrentServerForUser(username, tenant.getId()); - // the new user becomes its owner to ensure the user role is correctly working // the default tenant is the owning tenant to allow users having admin role for a specific server tenant to also be able to delete users - accessControlStore.setOwnership(result.getIdentifier(), result, userOwner, username); - - result.setFullName(fullName); - result.setCompany(company); - result.setLocale(locale); - final String emailValidationSecret = result.startEmailValidation(); - // don't replicate exception handling; replicate only the effect on the user store - apply(s->s.internalStoreUser(result)); - if (validationBaseURL != null && email != null && !email.trim().isEmpty()) { - new Thread("e-mail validation for user " + username + " with e-mail address " + email) { - @Override - public void run() { - try { - startEmailValidation(result, emailValidationSecret, validationBaseURL); - } catch (MailException e) { - logger.log(Level.SEVERE, "Error sending mail for new account validation of user " + username - + " to address " + email, e); - } - } - }.start(); - } + apply(s->s.internalSetOwnership(result.getIdentifier(), username, groupOwningUser.getId(), username)); + updateUserProperties(username, fullName, company, locale); + updateSimpleUserEmail(username, email, validationBaseURL); return result; } @@ -858,13 +844,9 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat return tenant; } - private User createUserInternal(String username, String email, Account... accounts) - throws UserManagementException { - final User result = store.createUser(username, email, accounts); // TODO: get the principal - // as owner - // now the user creation needs to be replicated so that when replicating role addition and group assignment - // the replica will be able to resolve the user correctly - apply(s -> s.internalStoreUser(result)); + @Override + public User internalCreateUser(String username, String email, Account... accounts) throws UserManagementException { + final User result = store.createUser(username, email, accounts); // TODO: get the principal as owner return result; } @@ -872,12 +854,6 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat return username + "-tenant"; } - @Override - public Void internalStoreUser(User user) { - store.updateUser(user); - return null; - } - @Override public void updateSimpleUserPassword(String username, String newPassword) throws UserManagementException { final User user = store.getUserByName(username); @@ -892,14 +868,20 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat throw new UserManagementException(UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS); } // for non-admins, check that the old password is correct - final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD); RandomNumberGenerator rng = new SecureRandomNumberGenerator(); byte[] salt = rng.nextBytes().getBytes(); String hashedPasswordBase64 = hashPassword(newPassword, salt); + apply(s->s.internalUpdateSimpleUserPassword(user.getName(), salt, hashedPasswordBase64)); + } + + @Override + public Void internalUpdateSimpleUserPassword(String username, byte[] salt, String hashedPasswordBase64) { + final User user = getUserByName(username); + final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD); account.setSalt(salt); account.setSaltedPassword(hashedPasswordBase64); user.passwordWasReset(); - apply(s->s.internalStoreUser(user)); + return null; } @Override @@ -908,14 +890,16 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat if (user == null) { throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST); } - updateUserProperties(user, fullName, company, locale); + apply(s->s.internalUpdateUserProperties(user, fullName, company, locale)); } - private void updateUserProperties(User user, String fullName, String company, Locale locale) { + @Override + public Void internalUpdateUserProperties(User user, String fullName, String company, Locale locale) { user.setFullName(fullName); user.setCompany(company); user.setLocale(locale); - apply(s->s.internalStoreUser(user)); + store.updateUser(user); + return null; } @Override @@ -945,7 +929,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST); } logger.info("Changing e-mail address of user "+username+" to "+newEmail); - final String validationSecret = user.setEmail(newEmail); + final String validationSecret = user.createRandomSecret(); + apply(s->s.internalUpdateSimpleUserEmail(username, newEmail, validationSecret)); new Thread("e-mail validation after changing e-mail of user " + username + " to " + newEmail) { @Override public void run() { @@ -957,7 +942,14 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat } } }.start(); - apply(s->s.internalStoreUser(user)); + } + + @Override + public Void internalUpdateSimpleUserEmail(final String username, final String newEmail, final String validationSecret) { + final User user = getUserByName(username); + user.setEmail(newEmail); + user.startEmailValidation(validationSecret); + return null; } @Override @@ -966,17 +958,25 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat if (user == null) { throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST); } + return apply(s->s.internalValidateEmail(username, validationSecret)); + } + + @Override + public Boolean internalValidateEmail(String username, String validationSecret) { + final User user = store.getUserByName(username); final boolean result = user.validate(validationSecret); - apply(s->s.internalStoreUser(user)); + if (result) { + store.updateUser(user); + } return result; } /** - * {@link UserImpl#startEmailValidation() Triggers} e-mail validation for the user object and sends out a + * {@link UserImpl#startEmailValidation(String) Triggers} e-mail validation for the user object and sends out a * URL to the user's e-mail that has the validation secret ready for validation by clicking. * * @param validationSecret - * the result of either {@link UserImpl#startEmailValidation()} or {@link UserImpl#setEmail(String)}. + * the result of either {@link UserImpl#startEmailValidation(String)} or {@link UserImpl#setEmail(String)}. * @param baseURL * the URL under which the user can reach the e-mail validation service; this URL is required to assemble * a validation URL that is sent by e-mail to the user, to make the user return the validation secret to