made tests, particularly replication tests, green again

Change-Id: I493aec5ac3ea3f1bd6b1a1fd95b8ddd349ce4198
This commit is contained in:
Axel Uhl
2018-05-17 18:38:00 +02:00
parent 853575a6f7
commit 3186d02711
12 changed files with 129 additions and 27 deletions
@@ -25,7 +25,11 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.server,
com.sap.sailing.server.testsupport,
com.sap.sailing.domain.persistence,
com.sap.sse.filestorage
com.sap.sse.filestorage,
com.sap.sse.security,
com.sap.sse.security.userstore.mongodb,
com.sap.sse.security.common,
org.mongodb.mongo-java-driver
Export-Package: com.sap.sailing.domain.test,
com.sap.sailing.domain.test.mock
Import-Package: org.osgi.util.tracker
@@ -47,9 +47,15 @@ import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.security.AccessControlStore;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.UserStore;
import com.sap.sse.security.impl.SecurityServiceImpl;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserGroup;
import com.sap.sse.security.shared.UserGroupManagementException;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.userstore.mongodb.AccessControlStoreImpl;
import com.sap.sse.security.userstore.mongodb.UserStoreImpl;
public class OfflineSerializationTest extends AbstractSerializationTest {
@@ -105,8 +111,24 @@ public class OfflineSerializationTest extends AbstractSerializationTest {
public void testSerializingUserStore() throws UserGroupManagementException, UserManagementException, ClassNotFoundException, IOException {
DomainFactory receiverDomainFactory = new DomainFactoryImpl((srlid)->null);
UserStore userStore = new UserStoreImpl("defaultTenant");
userStore.clear();
AccessControlStore aclStore = new AccessControlStoreImpl(userStore);
SecurityService securityService = new SecurityServiceImpl(userStore, aclStore);
assertNotNull(securityService);
{
User admin = userStore.getUserByName("admin");
UserGroup adminTenant = admin.getDefaultTenant();
assertTrue(adminTenant.contains(admin));
assertTrue(Util.contains(admin.getUserGroups(), adminTenant));
}
UserStore deserializedUserStore = cloneBySerialization(userStore, receiverDomainFactory);
assertNotNull(deserializedUserStore);
{
User admin = deserializedUserStore.getUserByName("admin");
UserGroup adminTenant = admin.getDefaultTenant();
assertTrue(adminTenant.contains(admin));
assertTrue(Util.contains(admin.getUserGroups(), adminTenant));
}
}
@Test
@@ -37,10 +37,20 @@ public class AbstractMailServiceReplicationTest extends AbstractServerWithSingle
@Override
public Void internalSendMail(String toAddress, String subject, SerializableMultipartSupplier multipartSupplier)
throws MailException {
countOneIfCanSendMail(canSendMail);
return null;
}
private void countOneIfCanSendMail(final boolean canSendMail) {
if (canSendMail) {
Integer old = numberOfMailsSent.get(this);
numberOfMailsSent.put(this, old == null ? 1 : old + 1);
}
}
@Override
public Void internalSendMail(String toAddress, String subject, String body) throws MailException {
countOneIfCanSendMail(canSendMail);
return null;
}
};
@@ -1,8 +1,12 @@
package com.sap.sse.security.shared.impl;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.sap.sse.common.Util;
@@ -16,8 +20,22 @@ import com.sap.sse.security.shared.WildcardPermission;
public class SecurityUserImpl implements SecurityUser {
private static final long serialVersionUID = -3639860207453072248L;
private String name;
private Set<Role> roles;
/**
* Roles can refer back to this user object, e.g., for the user qualification, or during the tenant
* qualification if this user belongs to the tenant for which the role is qualified. Therefore, this
* set has to be transient, and the {@link #roleListForSerialization} field takes over the serialization
* which is resolved by {@link #readResolve}.
*
* @see #writeObject
* @see #readResolve
*/
private transient Set<Role> roles;
private List<Role> roleListForSerialization;
private Set<WildcardPermission> permissions;
/**
@@ -47,7 +65,19 @@ public class SecurityUserImpl implements SecurityUser {
this.permissions.add(permission);
}
}
private void writeObject(ObjectOutputStream oos) throws IOException {
roleListForSerialization = new ArrayList<>(roles);
oos.defaultWriteObject();
roleListForSerialization = null;
}
protected Object readResolve() {
roles = new HashSet<>(roleListForSerialization);
roleListForSerialization = null;
return this;
}
@Override
public String getName() {
return name;
@@ -1,12 +1,17 @@
package com.sap.sse.security.shared.impl;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.sap.sse.common.Util;
import com.sap.sse.security.shared.SecurityUser;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserGroup;
public class UserGroupImpl implements UserGroup {
@@ -15,7 +20,18 @@ public class UserGroupImpl implements UserGroup {
private UUID id;
private String name;
private Set<SecurityUser> users;
/**
* A {@link HashSet} such as the one used for {@link #users} must not reference back to this object, or
* serialization / deserialization will be corrupt. In particular, during de-serialization the {@link User}
* objects will not have been fully constructed yet when they are to be added to the {@link HashSet}, causing
* {@link NullPointerException}s in their {@link User#hashCode} method execution. Therefore, we use a list for
* transporting the users through serialization, but outside of this use case the field is left {@code null}.
*
* @see #writeObject
* @see #readObject
*/
private List<SecurityUser> usersAsListForSerialization;
private transient Set<SecurityUser> users;
@Deprecated
protected UserGroupImpl() {} // for GWT serialization only
@@ -31,6 +47,19 @@ public class UserGroupImpl implements UserGroup {
Util.addAll(users, this.users);
}
private void writeObject(ObjectOutputStream oos) throws IOException {
usersAsListForSerialization = new ArrayList<>(users);
oos.defaultWriteObject();
usersAsListForSerialization = null;
}
protected Object readResolve() {
users = new HashSet<>();
users.addAll(usersAsListForSerialization);
usersAsListForSerialization = null;
return this;
}
@Override
public UUID getId() {
return id;
@@ -37,12 +37,11 @@ public abstract class AbstractSecurityReplicationTest extends AbstractServerWith
final UserStoreImpl userStore = new UserStoreImpl("TestDefaultTenant");
final AccessControlStore accessControlStore = new AccessControlStoreImpl(userStore);
SecurityServiceImpl result = new SecurityServiceImpl(userStore, accessControlStore);
result.clearReplicaState();
return result;
}
@Override
protected SecurityServiceImpl createNewReplica() throws UserGroupManagementException, UserManagementException {
protected SecurityServiceImpl createNewReplica() throws UserGroupManagementException, UserManagementException, MalformedURLException, IOException, InterruptedException {
final UserStoreImpl userStore = new UserStoreImpl("TestDefaultTenant");
final AccessControlStore accessControlStore = new AccessControlStoreImpl(userStore);
return new SecurityServiceImpl(userStore, accessControlStore);
@@ -57,18 +57,18 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe
final UserStoreImpl userStore = new UserStoreImpl("TestDefaultTenant");
final AccessControlStore accessControlStore = new AccessControlStoreImpl(userStore);
SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, userStore, accessControlStore);
result.clearReplicaState();
return result;
}
@Override
protected SecurityServiceImpl createNewReplica() throws UserGroupManagementException, UserManagementException {
protected SecurityServiceImpl createNewReplica() throws UserGroupManagementException, UserManagementException, MalformedURLException, IOException, InterruptedException {
@SuppressWarnings("unchecked")
ServiceTracker<MailService, MailService> trackerMock = mock(ServiceTracker.class);
doReturn(replicaMailService).when(trackerMock).getService();
final UserStoreImpl userStore = new UserStoreImpl("TestDefaultTenant");
final AccessControlStore accessControlStore = new AccessControlStoreImpl(userStore);
SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, userStore, accessControlStore);
result.clearReplicaState();
return result;
}
}
@@ -101,21 +101,16 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe
//same message queue, but don't know about each other (unlike actual OSGi setup, where there is only
//one replication service per instance that nows all Replicables)
SecurityService masterSecurityService = securitySetUp.getMaster();
final String username = "Ernie";
final String email = "ernie@sesame-street.com";
final String password = "BertMyFriend";
final String validationBaseURL = null; //so that validation email is not sent
masterSecurityService.createSimpleUser(username, email, password,
/* fullName */ null, /* company */ null, validationBaseURL);
masterSecurityService.sendMail(username, "subject", "body");
securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty();
mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty();
Thread.sleep(3000);
assertThat("mail was not sent on replica",
AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null));
assertThat("mail was sent on master",
@@ -132,21 +127,16 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe
public void triggerEmailSendByAddingUserOnReplica()
throws UserManagementException, MailException, IllegalAccessException, InterruptedException, UserGroupManagementException {
SecurityService replicaSecurityService = securitySetUp.getReplica();
final String username = "Ernie";
final String email = "ernie@sesame-street.com";
final String password = "BertMyFriend";
final String validationBaseURL = null; //so that validation email is not sent
replicaSecurityService.createSimpleUser(username, email, password,
/* fullName */ null, /* company */ null, validationBaseURL);
replicaSecurityService.sendMail(username, "subject", "body");
securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty();
mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty();
Thread.sleep(3000);
assertThat("mail was not sent on replica",
AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null));
assertThat("mail was sent on master",
@@ -16,6 +16,7 @@ import com.sap.sse.security.impl.SecurityServiceImpl;
import com.sap.sse.security.shared.UserGroupManagementException;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.userstore.mongodb.AccessControlStoreImpl;
import com.sap.sse.security.userstore.mongodb.PersistenceFactory;
import com.sap.sse.security.userstore.mongodb.UserStoreImpl;
public class SecurityServiceInitialLoadTest extends AbstractServerWithSingleServiceReplicationTest<SecurityService, SecurityServiceImpl> {
@@ -31,10 +32,11 @@ public class SecurityServiceInitialLoadTest extends AbstractServerWithSingleServ
@Override
protected SecurityServiceImpl createNewMaster()
throws MalformedURLException, IOException, InterruptedException, UserManagementException, MailException, UserGroupManagementException {
final UserStore userStore = new UserStoreImpl(null, null, "TestDefaultTenant"); // no persistence
final AccessControlStore accessControlStore = new AccessControlStoreImpl(null, null, userStore); // no persistence
final UserStore userStore = new UserStoreImpl(PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), "TestDefaultTenant");
final AccessControlStore accessControlStore = new AccessControlStoreImpl(PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), userStore);
final SecurityServiceImpl newMaster = new SecurityServiceImpl(userStore, accessControlStore);
newMaster.clearReplicaState();
newMaster.createSimpleUser(username, email, password, fullName, company, /* validationBaseURL */ null);
accessToken = newMaster.createAccessToken(username);
return newMaster;
@@ -2,7 +2,6 @@ package com.sap.sse.security.userstore.mongodb;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -266,10 +265,15 @@ public class UserStoreImpl implements UserStore {
UserStoreImpl.class.getSimpleName() + " lock for listeners collection", false);
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
protected Object readResolve() {
for (final User user : getUsers()) {
if (user instanceof UserImpl) {
((UserImpl) user).setUserGroupProvider(this);
}
}
return this;
}
@Override
public void clear() {
userGroups.clear();
@@ -497,12 +501,12 @@ public class UserStoreImpl implements UserStore {
@Override
public UserGroup getUserGroupByName(String name) {
return userGroupsByName.get(name);
return name == null ? null : userGroupsByName.get(name);
}
@Override
public UserGroup getUserGroup(UUID id) {
return userGroups.get(id);
return id == null ? null : userGroups.get(id);
}
@Override
@@ -36,5 +36,6 @@ Require-Bundle: javax.servlet;bundle-version="3.1.0",
com.sap.sse.shared.android
Web-ContextPath: /security
Export-Package: com.sap.sse.security,
com.sap.sse.security.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sse.security.jaxrs
Automatic-Module-Name: com.sap.sse.security
@@ -61,7 +61,7 @@ public class UserImpl extends SecurityUserImpl implements User {
private final Map<AccountType, Account> accounts;
private transient final UserGroupProvider userGroupProvider;
private transient UserGroupProvider userGroupProvider;
public UserImpl(String name, String email, UserGroup defaultTenant, UserGroupProvider userGroupProvider, Account... accounts) {
this(name, email, defaultTenant, Arrays.asList(accounts), userGroupProvider);
@@ -88,6 +88,14 @@ public class UserImpl extends SecurityUserImpl implements User {
this.accounts.put(a.getAccountType(), a);
}
}
/**
* The main use case for this method is to restore the link to a {@link UserStore} after de-serialization, e.g.,
* on a replica.
*/
public void setUserGroupProvider(UserGroupProvider userGroupProvider) {
this.userGroupProvider = userGroupProvider;
}
/**
* For the time being, the user {@link #getName() name} is used as ID
@@ -661,6 +661,9 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
String hashedPasswordBase64 = hashPassword(password, salt);
UsernamePasswordAccount upa = new UsernamePasswordAccount(username, hashedPasswordBase64, salt);
final UserImpl result = userStore.createUser(username, email, tenant, upa); // 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));
addRoleForUser(result, new RoleImpl(UserRole.getInstance(), /* tenant qualifier */ null, /* user qualifier */ result));
addUserToUserGroup(tenant, result);
// the new user becomes the owning user of its own specific tenant which initially only contains the new user