/*******************************************************************************
* Copyright (c) 2013, 2014 Lectorius, Inc.
* Authors:
* Vijay Pandurangan (vijayp@mitro.co)
* Evan Jones (ej@mitro.co)
* Adam Hilss (ahilss@mitro.co)
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can contact the authors at inbound@mitro.co.
*******************************************************************************/
package co.mitro.core.servlets;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import co.mitro.core.exceptions.MitroServletException;
import co.mitro.core.server.data.DBAcl.CyclicGroupError;
import co.mitro.core.server.data.DBGroup;
import co.mitro.core.server.data.DBIdentity;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.AdminInfo;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.MemberList;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.PendingGroup;
import co.mitro.core.server.data.RPC.AddPendingGroupResponse;
import co.mitro.core.server.data.RPC.GetPendingGroupApprovalsRequest;
import co.mitro.core.server.data.RPC.GetPendingGroupApprovalsResponse;
import co.mitro.core.server.data.RPC.GroupDiff;
import co.mitro.core.server.data.RPC.GroupDiff.GroupModificationType;
import co.mitro.core.servlets.MitroServlet.MitroRequestContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class AddAndGetPendingGroupsTest extends OrganizationsFixture {
private static final String NEW_USERNAME_1 = "nu34@example.com";
private static final String SCOPE1 = "scope1";
private static final String OLDGROUP2 = "oldgroup2";
private static final String OLDGROUP1 = "oldgroup1";
protected AddPendingGroupResponse resp;
protected AddPendingGroupRequest rqst;
protected AddPendingGroupServlet servlet;
protected GetPendingGroupApprovalsResponse getresp;
protected GetPendingGroupApprovalsRequest getrqst;
protected GetPendingGroupApprovals getservlet;
@Before
public void addPendingGroupsSetup() {
rqst = new AddPendingGroupRequest();
servlet = new AddPendingGroupServlet();
getrqst = new GetPendingGroupApprovalsRequest();
getservlet = new GetPendingGroupApprovals();
getrqst.scope = SCOPE1;
}
private void expectException(DBIdentity identity, String substr) throws IOException, SQLException {
try {
getAddPendingResponse(identity);
fail("expected exception");
} catch (MitroServletException|AssertionError expected) {
if (null != substr) {
assertThat(expected.getMessage().toLowerCase(), containsString(substr.toLowerCase()));
}
}
}
private void expectExceptionForGet(DBIdentity identity, String substr) throws IOException, SQLException {
try {
getGetPendingResponse(identity);
fail("expected exception");
} catch (MitroServletException|AssertionError expected) {
if (null != substr) {
assertThat(expected.getMessage().toLowerCase(), containsString(substr.toLowerCase()));
}
}
}
private void getAddPendingResponse(DBIdentity identity) throws MitroServletException, IOException, SQLException {
resp = (AddPendingGroupResponse)servlet.processCommand(
new MitroRequestContext(identity, gson.toJson(rqst), manager, null));
}
private void getGetPendingResponse(DBIdentity identity) throws MitroServletException, IOException, SQLException {
getresp = (GetPendingGroupApprovalsResponse) getservlet.processCommand(
new MitroRequestContext(identity, gson.toJson(getrqst), manager, null));
}
@Test
public void testPreconditionFailures() throws IOException, SQLException {
rqst.pendingGroups = null;
expectException(testIdentity2, null);
rqst.pendingGroups = Lists.newArrayList();
expectException(testIdentity2, null);
rqst.pendingGroups.add(new PendingGroup());
expectException(testIdentity2, null);
rqst.adminInfo = new AdminInfo();
expectException(testIdentity2, null);
rqst.adminInfo.domainAdminEmail = "unknown@example.com";
expectException(testIdentity2, "unknown user");
rqst.adminInfo.domainAdminEmail = testIdentity.getName();
expectException(testIdentity2, "scope must not be null");
rqst.scope = SCOPE1;
expectException(testIdentity2, "null MemberList");
}
private static String makeMemberList(List<String> members, String groupName){
MemberList ml = new MemberList();
ml.memberList = Lists.newArrayList(members);
ml.groupName = groupName;
return gson.toJson(ml);
}
private static void compareDiffs(Map<String, GroupDiff> a, Map<String, GroupDiff> b) {
Map<String, GroupDiff> sortedA = Maps.newTreeMap();
sortedA.putAll(a);
Map<String, GroupDiff> sortedB = Maps.newTreeMap();
sortedB.putAll(b);
assertEquals(gson.toJson(sortedA), gson.toJson(sortedB));
return;
}
@Test
public void testAddingGroupsToOrg() throws MitroServletException, IOException, SQLException {
rqst.pendingGroups = Lists.newArrayList();
String groupName = "new group1";
List<String> userNames = ImmutableList.of(NEW_USERNAME_1);
setAdminInfo();
addPendingGroupToRequest(groupName, userNames);
getAddPendingResponse(testIdentity2);
//private groups shouldn't be permitted
assertEquals(1, resp.diffs.size());
assertTrue(resp.diffs.containsKey(groupName));
assertEquals(GroupModificationType.IS_NEW, resp.diffs.get(groupName).groupModification);
assertEquals(1, resp.diffs.get(groupName).newUsers.size());
assertEquals(NEW_USERNAME_1, resp.diffs.get(groupName).newUsers.get(0));
getGetPendingResponse(testIdentity);
compareDiffs(resp.diffs, getresp.diffs);
assertTrue(getresp.pendingDeletions.isEmpty());
assertEquals(1, getresp.pendingAdditionsAndModifications.size());
assertEquals(null, getresp.pendingAdditionsAndModifications.get(0).matchedGroup);
assertEquals(resp.syncNonce, getresp.syncNonce);
String oldNonce = resp.syncNonce;
getAddPendingResponse(testIdentity2);
assertTrue(resp.diffs.containsKey(groupName));
assertEquals(GroupModificationType.IS_NEW, resp.diffs.get(groupName).groupModification);
assertTrue(resp.diffs.get(groupName).deletedUsers.isEmpty());
assertEquals(1, resp.diffs.get(groupName).newUsers.size());
assertEquals(NEW_USERNAME_1, resp.diffs.get(groupName).newUsers.get(0));
assertFalse(oldNonce.equals(resp.syncNonce));
getGetPendingResponse(testIdentity);
// all non-synced org users will be listed for delete.
assertTrue(getresp.deletedOrgMembers.size() == admins.size() + members.size());
assertTrue(getresp.newOrgMembers.contains(NEW_USERNAME_1));
compareDiffs(resp.diffs, getresp.diffs);
assertTrue(getresp.pendingDeletions.isEmpty());
assertEquals(1, getresp.pendingAdditionsAndModifications.size());
assertEquals(null, getresp.pendingAdditionsAndModifications.get(0).matchedGroup);
assertEquals(resp.syncNonce, getresp.syncNonce);
}
private void setAdminInfo() {
rqst.adminInfo = new AdminInfo();
rqst.adminInfo.domainAdminEmail = testIdentity.getName();
rqst.scope = SCOPE1;
}
private void addPendingGroupToRequest(String groupName, List<String> userNames)
throws MitroServletException, IOException, SQLException {
PendingGroup pg = new PendingGroup();
pg.groupName = groupName;
pg.memberListJson = makeMemberList(userNames, groupName);
rqst.pendingGroups.add(pg);
getAddPendingResponse(testIdentity2);
}
@Test
public void testRemovingGroupsFromOrg() throws CyclicGroupError, SQLException, MitroServletException, IOException {
DBGroup toDelete = createSyncedGroup(OLDGROUP1, SCOPE1, members, org);
createSyncedGroup(OLDGROUP2, SCOPE1, members, org);
rqst.pendingGroups = Lists.newArrayList();
String groupName = OLDGROUP2;
List<String> userNames = Lists.newArrayList();
for (DBIdentity i : members) {
userNames.add(i.getName());
}
setAdminInfo();
addPendingGroupToRequest(groupName, userNames);
getAddPendingResponse(testIdentity2);
//private groups shouldn't be permitted
assertEquals(1, resp.diffs.size());
assertFalse(resp.diffs.containsKey(OLDGROUP2));
assertTrue(resp.diffs.containsKey(OLDGROUP1));
assertEquals(GroupModificationType.IS_DELETED, resp.diffs.get(OLDGROUP1).groupModification);
assertTrue(resp.diffs.get(OLDGROUP1).newUsers.isEmpty());
assertEquals(members.size(), resp.diffs.get(OLDGROUP1).deletedUsers.size());
getGetPendingResponse(testIdentity);
compareDiffs(resp.diffs, getresp.diffs);
assertFalse(getresp.pendingDeletions.isEmpty());
assertEquals(1, getresp.pendingDeletions.size());
assertEquals(toDelete.getId(), getresp.pendingDeletions.get(0).groupId);
assertEquals(resp.syncNonce, getresp.syncNonce);
// test that non-admins can't get pending groups.
expectExceptionForGet(testIdentity2, null);
}
@Test
public void testNoOp() throws MitroServletException, IOException, SQLException, CyclicGroupError {
createSyncedGroup(OLDGROUP2, SCOPE1, members, org);
rqst.pendingGroups = Lists.newArrayList();
String groupName = OLDGROUP2;
List<String> userNames = Lists.newArrayList();
for (DBIdentity i : members) {
userNames.add(i.getName());
}
setAdminInfo();
addPendingGroupToRequest(groupName, userNames);
getAddPendingResponse(testIdentity2);
assertEquals(0, resp.diffs.size());
// TODO: test failure when using testIdentity2
getGetPendingResponse(testIdentity);
compareDiffs(resp.diffs, getresp.diffs);
assertTrue(getresp.pendingDeletions.isEmpty());
assertTrue(getresp.pendingAdditionsAndModifications.isEmpty());
assertTrue(getresp.deletedOrgMembers.isEmpty());
assertTrue(getresp.newOrgMembers.isEmpty());
// TODO noops have no nonce; this is weird.
//assertEquals(resp.syncNonce, getresp.syncNonce);
}
@Test
public void testModifyingGroupsFromOrg() throws CyclicGroupError, SQLException, MitroServletException, IOException {
DBGroup oldGroup = createSyncedGroup(OLDGROUP2, SCOPE1, members, org);
rqst.pendingGroups = Lists.newArrayList();
String groupName = OLDGROUP2;
List<String> userNames = Lists.newArrayList();
int i = 0;
int NUM_MEMBERS_TO_ADD = 5;
for (DBIdentity user : members) {
userNames.add(user.getName());
if (++i >= NUM_MEMBERS_TO_ADD) {
break;
}
}
userNames.add("anon@anon.com");
// TODO add an unchanged group and check that it's still there.
setAdminInfo();
addPendingGroupToRequest(groupName, userNames);
getAddPendingResponse(testIdentity2);
assertEquals(1, resp.diffs.size());
assertFalse(resp.diffs.containsKey(OLDGROUP1));
assertTrue(resp.diffs.containsKey(OLDGROUP2));
assertEquals(GroupModificationType.MEMBERSHIP_MODIFIED, resp.diffs.get(OLDGROUP2).groupModification);
assertEquals("anon@anon.com", resp.diffs.get(OLDGROUP2).newUsers.get(0));
assertEquals(members.size() - (userNames.size() - 1), resp.diffs.get(OLDGROUP2).deletedUsers.size());
getGetPendingResponse(testIdentity);
compareDiffs(resp.diffs, getresp.diffs);
assertTrue(getresp.pendingDeletions.isEmpty());
assertEquals(1, getresp.pendingAdditionsAndModifications.size());
assertEquals(oldGroup.getId(), getresp.pendingAdditionsAndModifications.get(0).matchedGroup.groupId);
assertFalse(getresp.pendingAdditionsAndModifications.get(0).memberListJson.isEmpty());
assertEquals(resp.syncNonce, getresp.syncNonce);
assertEquals(getresp.orgId, org.getId());
// all non-synced org users will be listed for delete.
assertEquals((admins.size() + members.size() - NUM_MEMBERS_TO_ADD), getresp.deletedOrgMembers.size());
assertEquals(1, getresp.newOrgMembers.size());
assertTrue(getresp.newOrgMembers.contains("anon@anon.com"));
}
@Test
public void testAddNoGroups() throws MitroServletException, IOException, SQLException, CyclicGroupError {
// push a sync request that has no groups. This should work: a domain can
// have no groups, or could have a group then have them all removed
// TODO: Test that removing all groups causes them to get deleted
setAdminInfo();
rqst.pendingGroups = new ArrayList<>();
getAddPendingResponse(testIdentity);
}
}