From 37655d0a614deacc41986df7915242033e669bd4 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 15 May 2017 22:43:44 -0400 Subject: [PATCH] track deltas for groups and users in azure --- src/Core/Services/AzureDirectoryService.cs | 132 +++++++++++++++++---- src/Core/Services/SettingsService.cs | 28 +++++ src/Core/Utilities/Extensions.cs | 19 +++ 3 files changed, 155 insertions(+), 24 deletions(-) diff --git a/src/Core/Services/AzureDirectoryService.cs b/src/Core/Services/AzureDirectoryService.cs index 6a99a8dd..2be6f2c2 100644 --- a/src/Core/Services/AzureDirectoryService.cs +++ b/src/Core/Services/AzureDirectoryService.cs @@ -89,22 +89,64 @@ namespace Bit.Core.Services var entries = new List(); - var groups = await _graphClient.Groups.Request().Select("id,displayName").GetAsync(); - foreach(var group in groups) - { - var entry = new GroupEntry - { - Id = group.Id, - Name = group.DisplayName - }; + var groupRequest = _graphClient.Groups.Delta(); + IGroupDeltaCollectionPage groups = null; - var members = await _graphClient.Groups[group.Id].Members.Request().Select("id").GetAsync(); - foreach(var member in members) + if(SettingsService.Instance.GroupDeltaToken != null) + { + try { - entry.Members.Add(member.Id); + var delataRequest = groupRequest.Request(); + delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.GroupDeltaToken)); + groups = await delataRequest.GetAsync(); + } + catch + { + groups = null; + } + } + + if(groups == null) + { + groups = await groupRequest.Request().Select("id,displayName").GetAsync(); + } + + while(true) + { + foreach(var group in groups) + { + var entry = new GroupEntry + { + Id = group.Id, + Name = group.DisplayName + }; + + var members = await _graphClient.Groups[group.Id].Members.Request().Select("id").GetAsync(); + foreach(var member in members) + { + entry.Members.Add(member.Id); + } + + entries.Add(entry); } - entries.Add(entry); + if(groups.NextPageRequest == null) + { + object deltaLink; + if(groups.AdditionalData.TryGetValue("@odata.deltaLink", out deltaLink)) + { + var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString(); + if(deltaUriQuery["$deltatoken"] != null) + { + SettingsService.Instance.GroupDeltaToken = deltaUriQuery["$deltatoken"]; + } + } + break; + } + else + { + groups = await groups.NextPageRequest.GetAsync(); + } } return entries; @@ -134,22 +176,64 @@ namespace Bit.Core.Services var entries = new List(); - var users = await _graphClient.Users.Request().Select("id,mail,userPrincipalName,accountEnabled").GetAsync(); - foreach(var user in users) - { - var entry = new UserEntry - { - Id = user.Id, - Email = user.Mail ?? user.UserPrincipalName, - Disabled = !user.AccountEnabled.GetValueOrDefault(true) - }; + var userRequest = _graphClient.Users.Delta(); + IUserDeltaCollectionPage users = null; - if(entry.Email.Contains("#")) + if(SettingsService.Instance.UserDeltaToken != null) + { + try { - continue; + var delataRequest = userRequest.Request(); + delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.UserDeltaToken)); + users = await delataRequest.GetAsync(); + } + catch + { + users = null; + } + } + + if(users == null) + { + users = await userRequest.Request().Select("id,mail,userPrincipalName,accountEnabled").GetAsync(); + } + + while(true) + { + foreach(var user in users) + { + var entry = new UserEntry + { + Id = user.Id, + Email = user.Mail ?? user.UserPrincipalName, + Disabled = !user.AccountEnabled.GetValueOrDefault(true) + }; + + if(entry?.Email?.Contains("#") ?? true) + { + continue; + } + + entries.Add(entry); } - entries.Add(entry); + if(users.NextPageRequest == null) + { + object deltaLink; + if(users.AdditionalData.TryGetValue("@odata.deltaLink", out deltaLink)) + { + var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString(); + if(deltaUriQuery["$deltatoken"] != null) + { + SettingsService.Instance.UserDeltaToken = deltaUriQuery["$deltatoken"]; + } + } + break; + } + else + { + users = await users.NextPageRequest.GetAsync(); + } } return entries; diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index 91b0ada3..dfabd928 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -192,6 +192,32 @@ namespace Bit.Core.Services } } + public string GroupDeltaToken + { + get + { + return Settings.GroupDeltaToken; + } + set + { + Settings.GroupDeltaToken = value; + SaveSettings(); + } + } + + public string UserDeltaToken + { + get + { + return Settings.UserDeltaToken; + } + set + { + Settings.UserDeltaToken = value; + SaveSettings(); + } + } + public class SettingsModel { public string ApiBaseUrl { get; set; } @@ -203,6 +229,8 @@ namespace Bit.Core.Services public Organization Organization { get; set; } public DateTime? LastGroupSyncDate { get; set; } public DateTime? LastUserSyncDate { get; set; } + public string GroupDeltaToken { get; set; } + public string UserDeltaToken { get; set; } } } } diff --git a/src/Core/Utilities/Extensions.cs b/src/Core/Utilities/Extensions.cs index b289620e..b1276be1 100644 --- a/src/Core/Utilities/Extensions.cs +++ b/src/Core/Utilities/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.DirectoryServices; using System.Globalization; using System.Linq; @@ -33,5 +34,23 @@ namespace Bit.Core.Utilities return null; } + + public static NameValueCollection ParseQueryString(this Uri uri) + { + var queryParameters = new NameValueCollection(); + var querySegments = uri.Query.Split('&'); + foreach(var segment in querySegments) + { + var parts = segment.Split('='); + if(parts.Length > 0) + { + var key = parts[0].Trim(new char[] { '?', ' ' }); + var val = parts[1].Trim(); + queryParameters.Add(key, val); + } + } + + return queryParameters; + } } }