public async Task <Group> AddChildGroups(Group group, IEnumerable <Group> childGroups) { // do not allow non-custom group to be parent if (!string.Equals(group.Source, GroupConstants.CustomSource, StringComparison.OrdinalIgnoreCase)) { throw new BadRequestException <Group>($"{group.Name} is not a custom group. Only custom groups can serve as parent groups."); } // first make sure all the child groups are created var childGroupList = childGroups.ToList(); var customGroupChildGroupWithSameName = (await _groupStore.Get( childGroupList.Select(g => new GroupIdentifier { GroupName = g.GroupIdentifier.GroupName }), true)).ToList(); if (customGroupChildGroupWithSameName.Any()) { throw new AlreadyExistsException <Group>( $"The associated user or group name should not be the same as an existing custom group: {string.Join(", ", customGroupChildGroupWithSameName.Select(g => g.Name))}"); } try { await _groupStore.Get( childGroupList.Select(g => g.GroupIdentifier), false); } // filter out groups that already exist so we don't attempt to create an existent group catch (NotFoundException <Group> ex) { var missingChildGroups = childGroupList.Where(g => ex.ExceptionDetails.Select(e => e.Identifier).Contains(g.GroupIdentifier.ToString())).ToList(); var invalidMissingGroups = missingChildGroups.Where(g => string.IsNullOrWhiteSpace(g.Name) || string.IsNullOrWhiteSpace(g.Source) || string.IsNullOrWhiteSpace(g.IdentityProvider) || !IdentityConstants.ValidIdentityProviders.Contains(g.IdentityProvider, StringComparer.OrdinalIgnoreCase) || string.Equals(g.Source, GroupConstants.CustomSource, StringComparison.OrdinalIgnoreCase)).ToList(); if (invalidMissingGroups.Any()) { throw new BadRequestException <Group>( "The following child groups do not exist in our database and cannot be created due to 1 or more of the following reasons: " + "1) missing GroupName, 2) missing GroupSource, 3) the GroupSource is incorrectly specified as Custom, or 4) The IdentityProvider field is missing or invalid: " + $"{string.Join(", ", invalidMissingGroups.Select(g => g.Name))}"); } await _groupStore.Add(missingChildGroups); } var childGroupIdentifierList = childGroupList.Select(g => g.GroupIdentifier).ToList(); // do not allow custom groups to be children // can't find where this code is hit, since the catch above looks for g.Source equal to CustomSource childGroups = (await _groupStore.Get(childGroupIdentifierList, false)).ToList(); var customGroups = childGroups.Where(g => g.Source == GroupConstants.CustomSource).ToList(); if (customGroups.Any()) { throw new BadRequestException <Group>($"Only directory groups can be child groups. The following groups are Custom groups: {string.Join(", ", customGroups)} "); } // if association already exists, return 409 var existingAssociations = group.Children.Where(c => childGroupIdentifierList.Contains(c.GroupIdentifier, new GroupIdentifierComparer())).ToList(); if (existingAssociations.Any()) { throw new AlreadyExistsException <Group>( $"The following groups are already children of group {group.Name}: {string.Join(", ", existingAssociations.Select(g => $"{g.Name} (IdP={g.IdentityProvider}, Tenant={g.TenantId})"))}"); } return(await _groupStore.AddChildGroups(group, childGroups)); }
public async Task <GroupMigrationResult> MigrateDuplicateGroups() { var groups = (await _groupStore.GetAll()).ToList(); var groupKeys = groups .GroupBy(g => g.GroupIdentifier, new GroupIdentifierComparer()) .Where(g => g.Count() > 1) .Select(g => g.Key) .ToList(); var groupMigrationResult = new GroupMigrationResult(); foreach (var key in groupKeys) { var duplicateGroups = groups.Where(g => new GroupIdentifierComparer().Equals(g.GroupIdentifier, key)).ToList(); var originalGroup = duplicateGroups.First(); duplicateGroups.RemoveAt(0); var groupMigrationRecord = new GroupMigrationRecord { DuplicateCount = duplicateGroups.Count }; var deletedDuplicateGroups = new List <Group>(); foreach (var duplicateGroup in duplicateGroups) { try { await _groupStore.Delete(duplicateGroup); deletedDuplicateGroups.Add(duplicateGroup); } catch (Exception e) { var msg = $"Exception thrown while deleting DUPLICATE group {duplicateGroup}"; _logger.Error(e, msg); groupMigrationRecord.Errors.Add($"{msg} ({e.Message})"); } } // only migrate the groups that were successfully deleted foreach (var duplicateGroup in deletedDuplicateGroups) { // migrate roles foreach (var role in duplicateGroup.Roles) { var roleExists = originalGroup.Roles.Any(r => r.Equals(role)); if (!roleExists) { _logger.Information($"Migrating Role {role} from {duplicateGroup} to group {originalGroup}"); try { await _groupStore.AddRolesToGroup(originalGroup, new List <Role> { role }); } catch (Exception e) { var msg = $"Error migrating Role {role} to Group {originalGroup}"; _logger.Error(e, msg); groupMigrationRecord.Errors.Add($"{msg} ({e.Message})"); } } } // migrate users (applies custom groups only) foreach (var user in duplicateGroup.Users) { var userExists = originalGroup.Users.Any(u => new UserComparer().Equals(u, user)); if (!userExists) { _logger.Information($"Migrating User {user} from {duplicateGroup} to group {originalGroup}"); try { await _groupStore.AddUsersToGroup(originalGroup, new List <User> { user }); } catch (Exception e) { var msg = $"Error migrating User {user} to Group {originalGroup}"; _logger.Error(e, msg); groupMigrationRecord.Errors.Add($"{msg} ({e.Message})"); } } } // migrate directory groups to new parents (this will not execute if the duplicate group is a custom group) foreach (var parent in duplicateGroup.Parents) { var parentExists = originalGroup.Parents.Any(p => p.Id == parent.Id); if (!parentExists) { _logger.Information($"Migrating Group {originalGroup} to Parent Group {parent}"); try { await _groupStore.AddChildGroups(parent, new List <Group> { originalGroup }); } catch (Exception e) { var msg = $"Error migrating Directory Child Group {originalGroup} to Parent Group {parent}"; _logger.Error(e, msg); groupMigrationRecord.Errors.Add($"{msg} ({e.Message})"); } } } // migrate custom parent groups to new child groups (this will not execute if the duplicate group is a directory group) foreach (var child in duplicateGroup.Children) { var childExists = originalGroup.Children.Any(c => c.Id == child.Id); if (!childExists) { _logger.Information($"Error migrating Custom Parent Group {originalGroup} to Child Group {child}"); try { await _groupStore.AddChildGroups(originalGroup, new List <Group> { child }); } catch (Exception e) { var msg = $"Error migrating Custom Parent Group {originalGroup} to Child Group {child}"; _logger.Error(e, msg); groupMigrationRecord.Errors.Add($"{msg} ({e.Message})"); } } } } groupMigrationRecord.MigratedGroup = originalGroup; groupMigrationResult.GroupMigrationRecords.Add(groupMigrationRecord); } return(groupMigrationResult); }