Skip to content

Commit a2f62cf

Browse files
committed
Adds validation of internal vs external addresses when exporting member changes
Fixes an issue where a missing member on a group is reported as a missing csentry in the FIM UI
1 parent 3185e3f commit a2f62cf

File tree

9 files changed

+168
-35
lines changed

9 files changed

+168
-35
lines changed

src/Lithnet.GoogleApps.MA.Setup/Product.wxs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Product Id="*"
44
Name="Lithnet GoogleApps Management Agent"
55
Language="1033"
6-
Version="1.1.6145"
6+
Version="1.1.6148"
77
Manufacturer="Lithnet"
88
UpgradeCode="3410d571b358426281edb2990ae57cae" >
99

src/Lithnet.GoogleApps.MA.UnitTests/GroupMemberInheritedRoleTests.cs

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public void RemoveExternalMembers()
2929
string member1 = "user1@lithnet.io";
3030
string member2 = "user2@lithnet.io";
3131

32-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member1});
33-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member2});
32+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member1 });
33+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member2 });
3434

3535
Thread.Sleep(1000);
3636

@@ -75,7 +75,7 @@ public void TestExternalManagerIsExternalMember()
7575
Thread.Sleep(1000);
7676

7777
string member2 = "test@test.com";
78-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member2, Role = "MANAGER"});
78+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member2, Role = "MANAGER" });
7979

8080
Thread.Sleep(1000);
8181

@@ -108,7 +108,7 @@ public void TestManagerIsMember()
108108

109109
user = UserTests.CreateUser();
110110

111-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = user.PrimaryEmail, Role = "MANAGER"});
111+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = user.PrimaryEmail, Role = "MANAGER" });
112112

113113
Thread.Sleep(1000);
114114

@@ -139,7 +139,7 @@ public void TestExternalOwnerIsExternalManagerAndExternalMember()
139139
Thread.Sleep(1000);
140140

141141
string member2 = "test@test.com";
142-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member2, Role = "OWNER"});
142+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member2, Role = "OWNER" });
143143

144144
Thread.Sleep(1000);
145145

@@ -174,7 +174,7 @@ public void TestOwnerIsManagerAndMember()
174174

175175
user = UserTests.CreateUser();
176176

177-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = user.PrimaryEmail, Role = "OWNER"});
177+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = user.PrimaryEmail, Role = "OWNER" });
178178

179179
Thread.Sleep(1000);
180180

@@ -218,7 +218,7 @@ public void DowngradeManagerToMember()
218218
cs.ObjectType = SchemaConstants.Group;
219219
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", e.Email));
220220

221-
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalManager", new List<ValueChange>() {new ValueChange(member2, ValueModificationType.Delete)}));
221+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalManager", new List<ValueChange>() { new ValueChange(member2, ValueModificationType.Delete) }));
222222

223223
CSEntryChangeResult result =
224224
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
@@ -234,7 +234,7 @@ public void DowngradeManagerToMember()
234234
Assert.AreEqual(0, membership.ExternalManagers.Count);
235235
Assert.AreEqual(0, membership.Managers.Count);
236236
Assert.AreEqual(0, membership.Members.Count);
237-
CollectionAssert.AreEquivalent(new string[] {member2}, membership.ExternalMembers.ToArray());
237+
CollectionAssert.AreEquivalent(new string[] { member2 }, membership.ExternalMembers.ToArray());
238238
}
239239
finally
240240
{
@@ -243,6 +243,60 @@ public void DowngradeManagerToMember()
243243

244244
}
245245

246+
[TestMethod]
247+
public void DowngradeManagersToMembers()
248+
{
249+
Group e = null;
250+
User member1 = null;
251+
User member2 = null;
252+
User member3 = null;
253+
User member4 = null;
254+
255+
try
256+
{
257+
e = UnitTestControl.CreateGroup();
258+
259+
member1 = UserTests.CreateUser();
260+
member2 = UserTests.CreateUser();
261+
member3 = UserTests.CreateUser();
262+
member4 = UserTests.CreateUser();
263+
264+
GroupMemberRequestFactory.AddMember(e.Email, member1.PrimaryEmail, "MANAGER");
265+
GroupMemberRequestFactory.AddMember(e.Email, member2.PrimaryEmail, "MANAGER");
266+
GroupMemberRequestFactory.AddMember(e.Email, member3.PrimaryEmail, "MEMBER");
267+
GroupMemberRequestFactory.AddMember(e.Email, member4.PrimaryEmail, "MEMBER");
268+
Thread.Sleep(1000);
269+
270+
CSEntryChange cs = CSEntryChange.Create();
271+
cs.ObjectModificationType = ObjectModificationType.Update;
272+
cs.DN = e.Email;
273+
cs.ObjectType = SchemaConstants.Group;
274+
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", e.Email));
275+
276+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeDelete("manager"));
277+
278+
CSEntryChangeResult result =
279+
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
280+
281+
if (result.ErrorCode != MAExportError.Success)
282+
{
283+
Assert.Fail(result.ErrorName);
284+
}
285+
286+
Thread.Sleep(1000);
287+
GroupMembership membership = GroupMemberRequestFactory.GetMembership(cs.DN);
288+
289+
Assert.AreEqual(0, membership.ExternalManagers.Count);
290+
Assert.AreEqual(0, membership.Managers.Count);
291+
Assert.AreEqual(0, membership.ExternalMembers.Count);
292+
CollectionAssert.AreEquivalent(new string[] { member1.PrimaryEmail, member2.PrimaryEmail, member3.PrimaryEmail, member4.PrimaryEmail }, membership.Members.ToArray());
293+
}
294+
finally
295+
{
296+
UnitTestControl.Cleanup(e, member1, member2, member3, member4);
297+
}
298+
}
299+
246300
[TestMethod]
247301
public void DowngradeOwnerToManager()
248302
{
@@ -265,7 +319,7 @@ public void DowngradeOwnerToManager()
265319
cs.ObjectType = SchemaConstants.Group;
266320
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", e.Id));
267321

268-
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalOwner", new List<ValueChange>() {new ValueChange(member2, ValueModificationType.Delete)}));
322+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalOwner", new List<ValueChange>() { new ValueChange(member2, ValueModificationType.Delete) }));
269323

270324
CSEntryChangeResult result =
271325
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
@@ -282,7 +336,7 @@ public void DowngradeOwnerToManager()
282336
Assert.AreEqual(0, membership.Owners.Count);
283337
Assert.AreEqual(0, membership.Managers.Count);
284338
Assert.AreEqual(0, membership.Members.Count);
285-
CollectionAssert.AreEquivalent(new string[] {member2}, membership.ExternalManagers.ToArray());
339+
CollectionAssert.AreEquivalent(new string[] { member2 }, membership.ExternalManagers.ToArray());
286340
}
287341
finally
288342
{
@@ -301,7 +355,7 @@ public void UpgradeMemberToManager()
301355
Thread.Sleep(1000);
302356

303357
string member2 = "test@test.com";
304-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member2});
358+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member2 });
305359

306360
Thread.Sleep(1000);
307361

@@ -311,7 +365,7 @@ public void UpgradeMemberToManager()
311365
cs.ObjectType = SchemaConstants.Group;
312366
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", e.Id));
313367

314-
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalManager", new List<ValueChange>() {new ValueChange(member2, ValueModificationType.Add)}));
368+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalManager", new List<ValueChange>() { new ValueChange(member2, ValueModificationType.Add) }));
315369

316370
CSEntryChangeResult result =
317371
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
@@ -323,7 +377,7 @@ public void UpgradeMemberToManager()
323377

324378
Thread.Sleep(1000);
325379

326-
CollectionAssert.AreEquivalent(new string[] {member2}, GroupMemberRequestFactory.GetMembership(cs.DN).ExternalManagers.ToArray());
380+
CollectionAssert.AreEquivalent(new string[] { member2 }, GroupMemberRequestFactory.GetMembership(cs.DN).ExternalManagers.ToArray());
327381
}
328382
finally
329383
{
@@ -342,7 +396,7 @@ public void UpgradeManagerToOwner()
342396
Thread.Sleep(1000);
343397

344398
string member2 = "test@test.com";
345-
GroupMemberRequestFactory.AddMember(e.Email, new Member() {Email = member2, Role = "MANAGER"});
399+
GroupMemberRequestFactory.AddMember(e.Email, new Member() { Email = member2, Role = "MANAGER" });
346400

347401
Thread.Sleep(1000);
348402

@@ -352,7 +406,7 @@ public void UpgradeManagerToOwner()
352406
cs.ObjectType = SchemaConstants.Group;
353407
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", e.Id));
354408

355-
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalOwner", new List<ValueChange>() {new ValueChange(member2, ValueModificationType.Add)}));
409+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeUpdate("externalOwner", new List<ValueChange>() { new ValueChange(member2, ValueModificationType.Add) }));
356410

357411
CSEntryChangeResult result =
358412
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
@@ -364,7 +418,7 @@ public void UpgradeManagerToOwner()
364418

365419
Thread.Sleep(1000);
366420

367-
CollectionAssert.AreEquivalent(new string[] {member2}, GroupMemberRequestFactory.GetMembership(cs.DN).ExternalOwners.ToArray());
421+
CollectionAssert.AreEquivalent(new string[] { member2 }, GroupMemberRequestFactory.GetMembership(cs.DN).ExternalOwners.ToArray());
368422
}
369423
finally
370424
{

src/Lithnet.GoogleApps.MA.UnitTests/GroupTests.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,55 @@ public void Update()
236236
}
237237
}
238238

239+
[TestMethod]
240+
public void UpdateDescription()
241+
{
242+
string id = null;
243+
string dn = $"{Guid.NewGuid()}@{UnitTestControl.TestParameters.Domain}";
244+
Group e = new Group
245+
{
246+
Email = dn,
247+
Name = "name",
248+
Description = "description"
249+
};
250+
251+
e = GroupRequestFactory.Add(e);
252+
id = e.Id;
253+
254+
CSEntryChange cs = CSEntryChange.Create();
255+
cs.ObjectModificationType = ObjectModificationType.Update;
256+
cs.DN = dn;
257+
cs.ObjectType = SchemaConstants.Group;
258+
cs.AnchorAttributes.Add(AnchorAttribute.Create("id", id));
259+
260+
cs.AttributeChanges.Add(AttributeChange.CreateAttributeDelete("description"));
261+
262+
try
263+
{
264+
CSEntryChangeResult result =
265+
ExportProcessor.PutCSEntryChange(cs, UnitTestControl.Schema.GetSchema().Types[SchemaConstants.Group], UnitTestControl.TestParameters);
266+
267+
if (result.ErrorCode != MAExportError.Success)
268+
{
269+
Assert.Fail($"{result.ErrorName}\n{result.ErrorDetail}");
270+
}
271+
272+
e = GroupRequestFactory.Get(id);
273+
Assert.AreEqual(cs.DN, e.Email);
274+
275+
Assert.AreEqual(true, e.AdminCreated);
276+
Assert.AreEqual(null, e.Description);
277+
Assert.AreEqual("name", e.Name);
278+
}
279+
finally
280+
{
281+
if (id != null)
282+
{
283+
// GroupRequestFactory.Delete(id);
284+
CSEntryChangeQueue.SaveQueue("D:\\temp\\group-update.xml", UnitTestControl.MmsSchema);
285+
}
286+
}
287+
}
239288
[TestMethod]
240289
public void UpdateNoneCanPostOn()
241290
{
@@ -706,7 +755,7 @@ public void ReplaceAliases()
706755
}
707756

708757
}
709-
758+
710759
private string CreateGroup(out Group e)
711760
{
712761
string dn = $"{Guid.NewGuid()}@{UnitTestControl.TestParameters.Domain}";

src/Lithnet.GoogleApps.MA.UnitTests/Lithnet.GoogleApps.MA.UnitTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@
8989
<HintPath>..\packages\Google.GData.Extensions.2.2.0.0\lib\Google.GData.Extensions.dll</HintPath>
9090
<Private>True</Private>
9191
</Reference>
92-
<Reference Include="Lithnet.GoogleApps, Version=1.0.6144.23555, Culture=neutral, processorArchitecture=MSIL">
93-
<HintPath>..\packages\Lithnet.GoogleApps.1.0.6144.23555\lib\net452\Lithnet.GoogleApps.dll</HintPath>
92+
<Reference Include="Lithnet.GoogleApps, Version=1.0.6146.35517, Culture=neutral, processorArchitecture=MSIL">
93+
<HintPath>..\packages\Lithnet.GoogleApps.1.0.6146.35517\lib\net452\Lithnet.GoogleApps.dll</HintPath>
9494
<Private>True</Private>
9595
</Reference>
9696
<Reference Include="Lithnet.Logging, Version=1.0.5774.20685, Culture=neutral, processorArchitecture=MSIL">

src/Lithnet.GoogleApps.MA.UnitTests/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<package id="Google.GData.Client" version="2.2.0.0" targetFramework="net452" />
1212
<package id="Google.GData.Contacts" version="2.2.0.0" targetFramework="net452" />
1313
<package id="Google.GData.Extensions" version="2.2.0.0" targetFramework="net452" />
14-
<package id="Lithnet.GoogleApps" version="1.0.6144.23555" targetFramework="net452" />
14+
<package id="Lithnet.GoogleApps" version="1.0.6146.35517" targetFramework="net452" />
1515
<package id="Lithnet.Logging" version="1.0.5774.20685" targetFramework="net452" />
1616
<package id="Lithnet.MetadirectoryServices" version="1.0.6017.24789" targetFramework="net452" />
1717
<package id="log4net" version="2.0.5" targetFramework="net452" />

src/Lithnet.GoogleApps.MA/ApiInterfaces/ApiInterfaceGroupMembership.cs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Google;
45
using Google.Apis.Admin.Directory.directory_v1.Data;
56
using Lithnet.Logging;
67
using Lithnet.GoogleApps.ManagedObjects;
@@ -70,13 +71,22 @@ public IList<AttributeChange> ApplyChanges(CSEntryChange csentry, SchemaType typ
7071
{
7172
foreach (Member change in roleChanges)
7273
{
73-
GroupMemberRequestFactory.ChangeMemberRole(csentry.DN, change);
74-
}
75-
76-
foreach (Member member in roleChanges)
77-
{
78-
Logger.WriteLine($"Changed member role {member.Email} to {member.Role}", LogLevel.Debug);
74+
try
75+
{
76+
GroupMemberRequestFactory.ChangeMemberRole(csentry.DN, change);
77+
Logger.WriteLine($"Changed member role {change.Email} to {change.Role}", LogLevel.Debug);
78+
}
79+
catch (GoogleApiException ex)
80+
{
81+
if (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound)
82+
{
83+
throw new UnexpectedDataException($"Member {change.Email} cannot be assigned {change.Role} without being made a member", ex);
84+
}
85+
86+
throw;
87+
}
7988
}
89+
8090
}
8191
catch (AggregateGroupUpdateException ex)
8292
{
@@ -469,13 +479,15 @@ private static void GetMemberChangesFromCSEntryChange(CSEntryChange csentry, Has
469479
case AttributeModificationType.Add:
470480
foreach (string address in csentry.GetValueAdds<string>(attributeName))
471481
{
482+
ApiInterfaceGroupMembership.ValidateAddress(address, attributeName);
472483
adds.Add(address);
473484
}
474485
break;
475486

476487
case AttributeModificationType.Delete:
477488
foreach (string member in existingMembers)
478489
{
490+
ApiInterfaceGroupMembership.ValidateAddress(member, attributeName);
479491
deletes.Add(member);
480492
}
481493

@@ -485,11 +497,13 @@ private static void GetMemberChangesFromCSEntryChange(CSEntryChange csentry, Has
485497
IList<string> newMembers = csentry.GetValueAdds<string>(attributeName);
486498
foreach (string address in newMembers)
487499
{
500+
ApiInterfaceGroupMembership.ValidateAddress(address, attributeName);
488501
adds.Add(address);
489502
}
490503

491504
foreach (string member in existingMembers.Except(newMembers))
492505
{
506+
ApiInterfaceGroupMembership.ValidateAddress(member, attributeName);
493507
deletes.Add(member);
494508
}
495509

@@ -498,11 +512,13 @@ private static void GetMemberChangesFromCSEntryChange(CSEntryChange csentry, Has
498512
case AttributeModificationType.Update:
499513
foreach (string address in csentry.GetValueDeletes<string>(attributeName))
500514
{
515+
ApiInterfaceGroupMembership.ValidateAddress(address, attributeName);
501516
deletes.Add(address);
502517
}
503518

504519
foreach (string address in csentry.GetValueAdds<string>(attributeName))
505520
{
521+
ApiInterfaceGroupMembership.ValidateAddress(address, attributeName);
506522
adds.Add(address);
507523
}
508524

@@ -515,6 +531,24 @@ private static void GetMemberChangesFromCSEntryChange(CSEntryChange csentry, Has
515531
}
516532
}
517533

534+
private static void ValidateAddress(string address, string attributeName)
535+
{
536+
if (attributeName.StartsWith("external"))
537+
{
538+
if (GroupMembership.IsAddressInternal(address))
539+
{
540+
throw new UnexpectedDataException($"The value {address} cannot be exported as {attributeName} as it is a known internal domain. It should be exported as {attributeName.Replace("external", string.Empty)}");
541+
}
542+
}
543+
else
544+
{
545+
if (GroupMembership.IsAddressExternal(address))
546+
{
547+
throw new UnexpectedDataException($"The value {address} cannot be exported as {attributeName} as it is not a known internal domain. It should be exported as external{attributeName}");
548+
}
549+
}
550+
}
551+
518552
private static bool ExistingMembershipRequiredForUpdate(CSEntryChange csentry, IManagementAgentParameters config)
519553
{
520554
if (csentry.ObjectModificationType == ObjectModificationType.Add)

0 commit comments

Comments
 (0)