/*
* Licensed to the University Corporation for Advanced Internet Development,
* Inc. (UCAID) under one or more contributor license agreements. See the
* NOTICE file distributed with this work for additional information regarding
* copyright ownership. The UCAID licenses this file to You under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.internet2.middleware.changelogconsumer.googleapps;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mock;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.admin.directory.Directory;
import com.google.api.services.admin.directory.model.Group;
import com.google.api.services.admin.directory.model.Member;
import com.google.api.services.admin.directory.model.User;
import com.google.api.services.admin.directory.model.UserName;
import edu.internet2.middleware.changelogconsumer.googleapps.cache.GoogleCacheManager;
import edu.internet2.middleware.changelogconsumer.googleapps.utils.AddressFormatter;
import edu.internet2.middleware.grouper.changeLog.*;
import edu.internet2.middleware.subject.Subject;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.*;
import edu.internet2.middleware.subject.provider.SubjectTypeEnum;
import org.junit.*;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.net.ssl.*", "org.apache.log4j.*"})
@PrepareForTest(value = { })
public class GoogleAppsChangeLogConsumerTest {
private static final String groupName = "qsuob:testStem:test";
private String groupDisplayName = "test";
private static final String subjectId = "fiwi";
private static final String sourceId = "jdbc";
private ChangeLogProcessorMetadata metadata;
private static AddressFormatter addressFormatter = new AddressFormatter();
private static GoogleAppsChangeLogConsumer consumer;
private static String googleDomain;
/** Global instance of the HTTP transport. */
private static HttpTransport httpTransport;
/** Global instance of the JSON factory. */
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static Directory directory = null;
@BeforeClass
public static void setupClass() {
consumer = new GoogleAppsChangeLogConsumer();
Properties props = new Properties();
InputStream is = ClassLoader.getSystemResourceAsStream("unit-test.properties");
try {
props.load(is);
addressFormatter
.setDomain(props.getProperty("DOMAIN"))
.setGroupIdentifierExpression(props.getProperty("GROUP_IDENTIFIER_EXPRESSION"))
.setSubjectIdentifierExpression(props.getProperty("SUBJECT_IDENTIFIER_EXPRESSION"));
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential googleCredential = null;
googleCredential = GoogleAppsSdkUtils.getGoogleDirectoryCredential(props.getProperty("SERVICE_ACCOUNT_EMAIL"),
props.getProperty("SERVICE_ACCOUNT_PKCS_12_FILE_PATH"), props.getProperty("SERVICE_IMPERSONATION_USER"),
httpTransport, JSON_FACTORY);
directory = new Directory.Builder(httpTransport, JSON_FACTORY, googleCredential)
.setApplicationName("Google Apps Grouper Provisioner")
.build();
} catch (Exception e) {
System.out.println("unit-test.properties configuration not found. Try again! Love, Grumpy Cat");
}
}
@Before
public void setup() throws GeneralSecurityException, IOException {
metadata = mock(ChangeLogProcessorMetadata.class);
when(metadata.getConsumerName()).thenReturn("google");
}
@After
public void tearDown() throws GeneralSecurityException, IOException {
try {
GoogleAppsSdkUtils.removeGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
} catch (IOException e) {
}
try {
GoogleAppsSdkUtils.removeUser(directory, addressFormatter.qualifyGroupAddress(subjectId));
} catch (IOException e) {
}
GoogleCacheManager.googleGroups().clear();
GoogleCacheManager.googleUsers().clear();
//Give Google a second to catch up since we create and destroy the same users and groups over and over
pause(1000L);
}
@Test
public void testProcessGroupAdd() throws GeneralSecurityException, IOException {
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "addGroup", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_ADD.name)).thenReturn(groupName);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(group);
assertTrue(group.getName().equalsIgnoreCase(groupDisplayName));
}
@Test
public void testProcessGroupUpdate() throws GeneralSecurityException, IOException {
final String NEW_TEST = "newTest";
createTestGroup(groupDisplayName, groupName);
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "updateGroup", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.name)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyChanged)).thenReturn("displayExtension");
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyOldValue)).thenReturn(groupDisplayName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyNewValue)).thenReturn(NEW_TEST);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
pause(1000L);
Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(group);
assertEquals(NEW_TEST, group.getName());
//TODO: ID Change
//TODO: Description Change
//TODO: Privilege Change
}
@Test
public void testProcessGroupMemberAddExistingUser() throws GeneralSecurityException, IOException {
//User already exists in Google
createTestGroup(groupDisplayName, groupName);
createTestUser(addressFormatter.qualifySubjectAddress(subjectId), "Fiona", "Windsor");
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(members);
assertTrue(members.size() == 1);
assertTrue(members.get(0).getEmail().equalsIgnoreCase(addressFormatter.qualifySubjectAddress(subjectId)));
}
/* This only works if the grouper-load.properties is marked with .provisionUsers=false
@Test
public void testProcessGroupMemberAddNewUserNoProvisioning() throws GeneralSecurityException, IOException {
//User doesn't exists in Google
createTestGroup(groupDisplayName, groupName);
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(members);
assertTrue(members.size() == 0);
}
*/
//This only works if the grouper-load.properties is marked with .provisionUsers=true
@Test
public void testProcessGroupMemberAddNewUserWithProvisioning() throws GeneralSecurityException, IOException {
//User doesn't exists in Google
createTestGroup(groupDisplayName, groupName);
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(members);
assertTrue(members.size() == 1);
assertTrue(members.get(0).getEmail().equalsIgnoreCase(addressFormatter.qualifySubjectAddress(subjectId)));
}
@Test
public void testProcessGroupMemberRemove() throws GeneralSecurityException, IOException {
//User already exists in Google
Group group = createTestGroup(groupDisplayName, groupName);
createTestUser(addressFormatter.qualifySubjectAddress(subjectId), "Fiona", "Windsor");
Member member = new Member()
.setEmail(addressFormatter.qualifySubjectAddress(subjectId))
.setRole("MEMBER");
GoogleAppsSdkUtils.addGroupMember(directory, group.getEmail(), member);
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "deleteMembership", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.groupName)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.subjectId)).thenReturn(subjectId);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.sourceId)).thenReturn(sourceId);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(members);
assertTrue(members.size() == 0);
}
@Test
public void testProcessGroupsStemChange() throws GeneralSecurityException, IOException {
try {
createTestGroup(groupDisplayName, groupName + "Change");
} catch (Exception ex) {}
ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "updateGroup", ""));
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.name)).thenReturn(groupName);
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyChanged)).thenReturn("name");
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyOldValue)).thenReturn(groupName+"Change");
when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyNewValue)).thenReturn(groupName);
when(addEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
pause(2000L);
Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
assertNotNull(group);
assertEquals(addressFormatter.qualifyGroupAddress(groupName).toLowerCase(), group.getEmail());
assertTrue(group.getAliases().contains(addressFormatter.qualifyGroupAddress(groupName+"Change")));
}
@Test
public void testProcessGroupDelete() throws GeneralSecurityException, IOException {
createTestGroup(groupDisplayName, groupName);
ChangeLogEntry deleteEntry = mock(ChangeLogEntry.class);
when(deleteEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "deleteGroup", ""));
when(deleteEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_DELETE.name)).thenReturn(groupName);
when(deleteEntry.getContextId()).thenReturn("123456789");
ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(deleteEntry));
consumer.processChangeLogEntries(changeLogEntryList, metadata);
assertTrue(GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName)) == null);
}
/*
@Test
public void testProcessSyncAttributeAddedDirectly() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
@Test
public void testProcessSyncAttributeAddedToParent() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
@Test
public void testProcessSyncAttributeRemovedDirectly() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
@Test
public void testProcessSyncAttributeRemovedFromParent() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
*/
/*
@Test
public void testProcessPrivilegeAdded() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
@Test
public void testProcessPrivilegeRemoved() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
@Test
public void testProcessPrivilegeChange() throws GeneralSecurityException, IOException {
fail("Not Implemented");
}
*/
private Group createTestGroup(String name, String mailbox) throws IOException {
Group group = new Group();
group.setName(name);
group.setEmail(addressFormatter.qualifyGroupAddress(mailbox));
return GoogleAppsSdkUtils.addGroup(directory, group);
}
private User createTestUser(String email, String givenName, String surname) throws IOException {
User user = new User();
user.setPrimaryEmail(email);
user.setName(new UserName());
user.getName().setFamilyName(surname);
user.getName().setGivenName(givenName);
user.setPassword(new BigInteger(130, new SecureRandom()).toString(32));
return GoogleAppsSdkUtils.addUser(directory, user);
}
private Subject getTestUserSubject() {
Subject subject = mock(Subject.class);
when(subject.getAttributeValue("givenName")).thenReturn("testgn2");
when(subject.getAttributeValue("sn")).thenReturn("testfn2");
when(subject.getAttributeValue("displayName")).thenReturn("testgn2, testfn2");
when(subject.getAttributeValue("mail")).thenReturn(addressFormatter.qualifySubjectAddress(subjectId));
when(subject.getType()).thenReturn(SubjectTypeEnum.PERSON);
return subject;
}
private Subject getTestGroupSubject() {
Subject subject = mock(Subject.class);
when(subject.getAttributeValue("givenName")).thenReturn("testgn2");
when(subject.getAttributeValue("sn")).thenReturn("testfn2");
when(subject.getAttributeValue("displayName")).thenReturn("testgn2, testfn2");
when(subject.getAttributeValue("mail")).thenReturn(addressFormatter.qualifySubjectAddress(subjectId));
when(subject.getType()).thenReturn(SubjectTypeEnum.GROUP);
return subject;
}
private void pause(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}