View Javadoc
1   /*******************************************************************************
2    * Copyright 2015 Internet2
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   ******************************************************************************/
16  package edu.internet2.middleware.changelogconsumer.googleapps;
17  
18  import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
19  import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
20  import com.google.api.client.http.HttpTransport;
21  import com.google.api.client.json.JsonFactory;
22  import com.google.api.client.json.jackson2.JacksonFactory;
23  import com.google.api.services.admin.directory.Directory;
24  import com.google.api.services.admin.directory.model.Group;
25  import com.google.api.services.admin.directory.model.Member;
26  import com.google.api.services.admin.directory.model.User;
27  import com.google.api.services.admin.directory.model.UserName;
28  import edu.internet2.middleware.changelogconsumer.googleapps.cache.GoogleCacheManager;
29  import edu.internet2.middleware.changelogconsumer.googleapps.utils.AddressFormatter;
30  import edu.internet2.middleware.grouper.changeLog.ChangeLogEntry;
31  import edu.internet2.middleware.grouper.changeLog.ChangeLogLabels;
32  import edu.internet2.middleware.grouper.changeLog.ChangeLogProcessorMetadata;
33  import edu.internet2.middleware.grouper.changeLog.ChangeLogType;
34  import edu.internet2.middleware.subject.Subject;
35  import edu.internet2.middleware.subject.provider.SubjectTypeEnum;
36  import org.junit.After;
37  import org.junit.Before;
38  import org.junit.BeforeClass;
39  import org.junit.Test;
40  import org.junit.runner.RunWith;
41  import org.powermock.core.classloader.annotations.PowerMockIgnore;
42  import org.powermock.core.classloader.annotations.PrepareForTest;
43  import org.powermock.modules.junit4.PowerMockRunner;
44  
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.math.BigInteger;
48  import java.security.GeneralSecurityException;
49  import java.security.SecureRandom;
50  import java.util.ArrayList;
51  import java.util.Arrays;
52  import java.util.List;
53  import java.util.Properties;
54  
55  import static junit.framework.Assert.assertNotNull;
56  import static junit.framework.Assert.assertTrue;
57  import static org.junit.Assert.assertEquals;
58  import static org.mockito.Mockito.when;
59  import static org.powermock.api.mockito.PowerMockito.mock;
60  
61  @RunWith(PowerMockRunner.class)
62  @PowerMockIgnore({"javax.net.ssl.*", "org.apache.log4j.*", " org.apache.logging.log4j.*"})
63  @PrepareForTest(value = { })
64  public class GoogleAppsChangeLogConsumerTest {
65  
66      private static final String groupName = "qsuob:testStem:test";
67      private String groupDisplayName = "test";
68  
69      private static final String subjectId = "fiwi";
70      private static final String sourceId = "jdbc";
71  
72      private ChangeLogProcessorMetadata metadata;
73      private static AddressFormatter addressFormatter = new AddressFormatter();
74  
75      private static GoogleAppsChangeLogConsumer consumer;
76      private static String googleDomain;
77  
78      /** Global instance of the HTTP transport. */
79      private static HttpTransport httpTransport;
80  
81      /** Global instance of the JSON factory. */
82      private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
83  
84      private static Directory directory = null;
85  
86      @BeforeClass
87      public static void setupClass() {
88  
89          consumer = new GoogleAppsChangeLogConsumer();
90  
91          Properties props = new Properties();
92  
93          InputStream is = ClassLoader.getSystemResourceAsStream("unit-test.properties");
94          try {
95              props.load(is);
96  
97              googleDomain = props.getProperty("DOMAIN");
98              addressFormatter
99                      .setDomain(googleDomain)
100                     .setGroupIdentifierExpression(props.getProperty("GROUP_IDENTIFIER_EXPRESSION"))
101                     .setSubjectIdentifierExpression(props.getProperty("SUBJECT_IDENTIFIER_EXPRESSION"));
102 
103             httpTransport = GoogleNetHttpTransport.newTrustedTransport();
104 
105             GoogleCredential googleCredential = null;
106                  googleCredential = GoogleAppsSdkUtils.getGoogleDirectoryCredential(props.getProperty("SERVICE_ACCOUNT_EMAIL"),
107                          props.getProperty("SERVICE_ACCOUNT_PKCS_12_FILE_PATH"), props.getProperty("SERVICE_IMPERSONATION_USER"),
108                          httpTransport, JSON_FACTORY);
109 
110             directory = new Directory.Builder(httpTransport, JSON_FACTORY, googleCredential)
111                     .setApplicationName("Google Apps Grouper Provisioner")
112                     .build();
113             
114         } catch (Exception e) {
115             System.out.println("unit-test.properties configuration not found. Try again! Love, Grumpy Cat");
116         }
117 
118     }
119 
120     @Before
121     public void setup() throws GeneralSecurityException, IOException {
122         metadata = mock(ChangeLogProcessorMetadata.class);
123         when(metadata.getConsumerName()).thenReturn("google");
124     }
125 
126     @After
127     public void tearDown() throws GeneralSecurityException, IOException {
128         try {
129             GoogleAppsSdkUtils.removeGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
130         } catch (IOException e) {
131 
132         }
133 
134         try {
135             GoogleAppsSdkUtils.removeUser(directory, addressFormatter.qualifyGroupAddress(subjectId));
136         } catch (IOException e) {
137 
138         }
139 
140         GoogleCacheManager.googleGroups().clear();
141         GoogleCacheManager.googleUsers().clear();
142 
143         //Give Google a second to catch up since we create and destroy the same users and groups over and over
144         pause(1000L);
145     }
146 
147     @Test
148     public void testProcessGroupAdd() throws GeneralSecurityException, IOException {
149         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
150         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "addGroup", ""));
151         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_ADD.name)).thenReturn(groupName);
152         when(addEntry.getContextId()).thenReturn("123456789");
153 
154         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
155 
156         consumer.processChangeLogEntries(changeLogEntryList, metadata);
157         Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
158         assertNotNull(group);
159         assertTrue(group.getName().equalsIgnoreCase(groupDisplayName));
160     }
161 
162     @Test
163     public void testProcessGroupUpdate() throws GeneralSecurityException, IOException {
164         final String NEW_TEST = "newTest";
165 
166         createTestGroup(groupDisplayName, groupName);
167 
168         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
169         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "updateGroup", ""));
170         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.name)).thenReturn(groupName);
171         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyChanged)).thenReturn("displayExtension");
172         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyOldValue)).thenReturn(groupDisplayName);
173         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyNewValue)).thenReturn(NEW_TEST);
174         when(addEntry.getContextId()).thenReturn("123456789");
175 
176         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
177 
178         consumer.processChangeLogEntries(changeLogEntryList, metadata);
179         pause(1000L);
180         Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
181 
182         assertNotNull(group);
183         assertEquals(NEW_TEST, group.getName());
184 
185         //TODO: ID Change
186         //TODO: Description Change
187         //TODO: Privilege Change
188     }
189 
190 
191     @Test
192     public void testProcessGroupMemberAddExistingUser() throws GeneralSecurityException, IOException {
193         //User already exists in Google
194         createTestGroup(groupDisplayName, groupName);
195         createTestUser(buildSubjectAddress(subjectId), "Fiona", "Windsor");
196 
197         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
198         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
199         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
200         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
201         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
202         when(addEntry.getContextId()).thenReturn("123456789");
203 
204         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
205 
206         consumer.processChangeLogEntries(changeLogEntryList, metadata);
207 
208         List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
209         assertNotNull(members);
210         assertTrue(members.size() == 1);
211         assertTrue(members.get(0).getEmail().equalsIgnoreCase(buildSubjectAddress(subjectId)));
212     }
213 
214 /* This only works if the grouper-load.properties is marked with .provisionUsers=false
215     @Test
216     public void testProcessGroupMemberAddNewUserNoProvisioning() throws GeneralSecurityException, IOException {
217         //User doesn't exists in Google
218         createTestGroup(groupDisplayName, groupName);
219 
220         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
221         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
222         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
223         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
224         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
225         when(addEntry.getContextId()).thenReturn("123456789");
226 
227         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
228 
229         consumer.processChangeLogEntries(changeLogEntryList, metadata);
230 
231         List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
232         assertNotNull(members);
233         assertTrue(members.size() == 0);
234     }
235 */
236 
237     //This only works if the grouper-load.properties is marked with .provisionUsers=true
238     @Test
239     public void testProcessGroupMemberAddNewUserWithProvisioning() throws GeneralSecurityException, IOException {
240         //User doesn't exists in Google
241         createTestGroup(groupDisplayName, groupName);
242 
243         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
244         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "addMembership", ""));
245         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName)).thenReturn(groupName);
246         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId)).thenReturn(subjectId);
247         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId)).thenReturn(sourceId);
248         when(addEntry.getContextId()).thenReturn("123456789");
249 
250         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
251 
252         consumer.processChangeLogEntries(changeLogEntryList, metadata);
253 
254         List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
255         assertNotNull(members);
256         assertTrue(members.size() == 1);
257         assertTrue(members.get(0).getEmail().equalsIgnoreCase(buildSubjectAddress(subjectId)));
258     }
259 
260     @Test
261     public void testProcessGroupMemberRemove() throws GeneralSecurityException, IOException {
262         //User already exists in Google
263         Group group = createTestGroup(groupDisplayName, groupName);
264         createTestUser(buildSubjectAddress(subjectId), "Fiona", "Windsor");
265 
266         Member member = new Member()
267                 .setEmail(buildSubjectAddress(subjectId))
268                 .setRole("MEMBER");
269         GoogleAppsSdkUtils.addGroupMember(directory, group.getEmail(), member);
270 
271         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
272         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("membership", "deleteMembership", ""));
273         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.groupName)).thenReturn(groupName);
274         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.subjectId)).thenReturn(subjectId);
275         when(addEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_DELETE.sourceId)).thenReturn(sourceId);
276         when(addEntry.getContextId()).thenReturn("123456789");
277 
278         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
279 
280         consumer.processChangeLogEntries(changeLogEntryList, metadata);
281 
282         List<Member> members = GoogleAppsSdkUtils.retrieveGroupMembers(directory, addressFormatter.qualifyGroupAddress(groupName));
283         assertNotNull(members);
284         assertTrue(members.size() == 0);
285     }
286 
287     @Test
288     public void testProcessGroupsStemChange() throws GeneralSecurityException, IOException {
289         try {
290             createTestGroup(groupDisplayName, groupName + "Change");
291         } catch (Exception ex) {}
292 
293         ChangeLogEntry addEntry = mock(ChangeLogEntry.class);
294         when(addEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "updateGroup", ""));
295         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.name)).thenReturn(groupName);
296         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyChanged)).thenReturn("name");
297         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyOldValue)).thenReturn(groupName+"Change");
298         when(addEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.propertyNewValue)).thenReturn(groupName);
299         when(addEntry.getContextId()).thenReturn("123456789");
300 
301         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(addEntry));
302 
303         consumer.processChangeLogEntries(changeLogEntryList, metadata);
304         pause(2000L);
305         Group group = GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName));
306 
307         assertNotNull(group);
308         assertEquals(addressFormatter.qualifyGroupAddress(groupName).toLowerCase(), group.getEmail());
309         assertTrue(group.getAliases().contains(addressFormatter.qualifyGroupAddress(groupName+"Change")));
310     }
311 
312     @Test
313     public void testProcessGroupDelete() throws GeneralSecurityException, IOException {
314         createTestGroup(groupDisplayName, groupName);
315 
316         ChangeLogEntry deleteEntry = mock(ChangeLogEntry.class);
317         when(deleteEntry.getChangeLogType()).thenReturn(new ChangeLogType("group", "deleteGroup", ""));
318         when(deleteEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_DELETE.name)).thenReturn(groupName);
319         when(deleteEntry.getContextId()).thenReturn("123456789");
320 
321         ArrayList<ChangeLogEntry> changeLogEntryList = new ArrayList<ChangeLogEntry>(Arrays.asList(deleteEntry));
322 
323         consumer.processChangeLogEntries(changeLogEntryList, metadata);
324         assertTrue(GoogleAppsSdkUtils.retrieveGroup(directory, addressFormatter.qualifyGroupAddress(groupName)) == null);
325     }
326 
327 /*
328     @Test
329     public void testProcessSyncAttributeAddedDirectly() throws GeneralSecurityException, IOException {
330         fail("Not Implemented");
331     }
332 
333     @Test
334     public void testProcessSyncAttributeAddedToParent() throws GeneralSecurityException, IOException {
335         fail("Not Implemented");
336     }
337 
338     @Test
339     public void testProcessSyncAttributeRemovedDirectly() throws GeneralSecurityException, IOException {
340         fail("Not Implemented");
341     }
342 
343     @Test
344     public void testProcessSyncAttributeRemovedFromParent() throws GeneralSecurityException, IOException {
345         fail("Not Implemented");
346     }
347 */
348 /*
349     @Test
350     public void testProcessPrivilegeAdded() throws GeneralSecurityException, IOException {
351         fail("Not Implemented");
352     }
353 
354     @Test
355     public void testProcessPrivilegeRemoved() throws GeneralSecurityException, IOException {
356         fail("Not Implemented");
357     }
358 
359     @Test
360     public void testProcessPrivilegeChange() throws GeneralSecurityException, IOException {
361         fail("Not Implemented");
362     }
363 */
364 
365 
366     private Group createTestGroup(String name, String mailbox) throws IOException {
367         Group group = new Group();
368         group.setName(name);
369         group.setEmail(addressFormatter.qualifyGroupAddress(mailbox));
370         return GoogleAppsSdkUtils.addGroup(directory, group);
371     }
372 
373     private User createTestUser(String email, String givenName, String surname) throws IOException {
374         User user = new User();
375         user.setPrimaryEmail(email);
376         user.setName(new UserName());
377         user.getName().setFamilyName(surname);
378         user.getName().setGivenName(givenName);
379         user.setPassword(new BigInteger(130, new SecureRandom()).toString(32));
380         return GoogleAppsSdkUtils.addUser(directory, user);
381     }
382 
383     private Subject getTestUserSubject() {
384         Subject subject = mock(Subject.class);
385         when(subject.getAttributeValue("givenName")).thenReturn("testgn2");
386         when(subject.getAttributeValue("sn")).thenReturn("testfn2");
387         when(subject.getAttributeValue("displayName")).thenReturn("testgn2, testfn2");
388         when(subject.getAttributeValue("mail")).thenReturn(buildSubjectAddress(subjectId));
389         when(subject.getType()).thenReturn(SubjectTypeEnum.PERSON);
390         return subject;
391     }
392 
393     private Subject getTestGroupSubject() {
394         Subject subject = mock(Subject.class);
395         when(subject.getAttributeValue("givenName")).thenReturn("testgn2");
396         when(subject.getAttributeValue("sn")).thenReturn("testfn2");
397         when(subject.getAttributeValue("displayName")).thenReturn("testgn2, testfn2");
398         when(subject.getAttributeValue("mail")).thenReturn(buildSubjectAddress(subjectId));
399         when(subject.getType()).thenReturn(SubjectTypeEnum.GROUP);
400         return subject;
401     }
402 
403     private void pause(long milliseconds) {
404         try {
405             Thread.sleep(milliseconds);
406         } catch (InterruptedException e) {
407             e.printStackTrace();
408         }
409     }
410 
411     private String buildSubjectAddress(String subjectId) {
412         return String.format("%s@%s", subjectId, googleDomain);
413     }
414 
415 }