diff --git a/src/Console/Program.cs b/src/Console/Program.cs index 7606b0db..197c3ec8 100644 --- a/src/Console/Program.cs +++ b/src/Console/Program.cs @@ -313,8 +313,8 @@ namespace Bit.Console break; } Con.Write("Type [{0}]: ", currentType); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { switch(input) { @@ -330,32 +330,32 @@ namespace Bit.Console } Con.Write("Address [{0}]: ", config.Address); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.Address = input; } Con.Write("Port [{0}]: ", config.Port); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.Port = input; } Con.Write("Path [{0}]: ", config.Path); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.Path = input; } Con.Write("Username [{0}]: ", config.Username); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.Username = input; } Con.Write("Password: "); input = ReadSecureLine(); - if(!string.IsNullOrWhiteSpace(input)) + if(!string.IsNullOrEmpty(input)) { config.Password = new EncryptedData(input); input = null; @@ -385,7 +385,8 @@ namespace Bit.Console private static Task ConfigSyncAsync() { - var config = Core.Services.SettingsService.Instance.Sync ?? new SyncConfiguration(); + var config = Core.Services.SettingsService.Instance.Sync ?? + new SyncConfiguration(Core.Services.SettingsService.Instance.Server.Type); if(_usingArgs) { @@ -443,57 +444,63 @@ namespace Bit.Console string input; Con.Write("Sync groups? [{0}]: ", config.SyncGroups ? "y" : "n"); - input = Con.ReadLine().Trim().ToLower(); - config.SyncGroups = input == "y" || input == "yes" || string.IsNullOrWhiteSpace(input); + input = Con.ReadLine().ToLower(); + if(!string.IsNullOrEmpty(input)) + { + config.SyncGroups = input == "y" || input == "yes"; + } if(config.SyncGroups) { Con.Write("Group filter [{0}]: ", config.GroupFilter); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.GroupFilter = input; } Con.Write("Group name attribute [{0}]: ", config.GroupNameAttribute); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.GroupNameAttribute = input; } } Con.Write("Sync users? [{0}]: ", config.SyncUsers ? "y" : "n"); - input = Con.ReadLine().Trim().ToLower(); - config.SyncUsers = input == "y" || input == "yes" || string.IsNullOrWhiteSpace(input); + input = Con.ReadLine().ToLower(); + if(!string.IsNullOrEmpty(input)) + { + config.SyncUsers = input == "y" || input == "yes"; + } if(config.SyncUsers) { Con.Write("User filter [{0}]: ", config.UserFilter); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.UserFilter = input; } Con.Write("User email attribute [{0}]: ", config.UserEmailAttribute); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.GroupNameAttribute = input; } } Con.Write("Member Of Attribute [{0}]: ", config.MemberAttribute); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.MemberAttribute = input; } Con.Write("Creation Attribute [{0}]: ", config.CreationDateAttribute); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.CreationDateAttribute = input; } Con.Write("Changed Attribute [{0}]: ", config.RevisionDateAttribute); - input = Con.ReadLine().Trim(); - if(!string.IsNullOrWhiteSpace(input)) + input = Con.ReadLine(); + if(!string.IsNullOrEmpty(input)) { config.RevisionDateAttribute = input; } diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 884c6f8b..4885741b 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -73,6 +73,7 @@ + diff --git a/src/Core/Enums/DirectoryType.cs b/src/Core/Enums/DirectoryType.cs index ee064a19..b25222a9 100644 --- a/src/Core/Enums/DirectoryType.cs +++ b/src/Core/Enums/DirectoryType.cs @@ -9,7 +9,7 @@ namespace Bit.Core.Enums public enum DirectoryType : byte { ActiveDirectory = 0, - AzureActiveCirectory = 1, + AzureActiveDirectory = 1, Other = 2 } } diff --git a/src/Core/Models/SyncConfiguration.cs b/src/Core/Models/SyncConfiguration.cs index 92e7e3f5..f955e13a 100644 --- a/src/Core/Models/SyncConfiguration.cs +++ b/src/Core/Models/SyncConfiguration.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Bit.Core.Enums; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.DirectoryServices; @@ -10,17 +11,38 @@ namespace Bit.Core.Models { public class SyncConfiguration { + public SyncConfiguration() { } + + public SyncConfiguration(DirectoryType type) + { + switch(type) + { + case DirectoryType.ActiveDirectory: + MemberAttribute = "memberOf"; + CreationDateAttribute = "whenCreated"; + RevisionDateAttribute = "whenChanged"; + UserEmailPrefixAttribute = "sAMAccountName"; + break; + case DirectoryType.AzureActiveDirectory: + break; + case DirectoryType.Other: + break; + default: + break; + } + } + public string GroupFilter { get; set; } = "(&(objectClass=group))"; public string UserFilter { get; set; } = "(&(objectClass=person))"; public bool SyncGroups { get; set; } = true; public bool SyncUsers { get; set; } = true; - public string MemberAttribute { get; set; } = "memberOf"; + public string MemberAttribute { get; set; } = "member"; public string GroupNameAttribute { get; set; } = "name"; public string UserEmailAttribute { get; set; } = "mail"; public bool EmailPrefixSuffix { get; set; } = false; - public string UserEmailPrefixAttribute { get; set; } = "sAMAccountName"; + public string UserEmailPrefixAttribute { get; set; } = "cn"; public string UserEmailSuffix { get; set; } = "@companyname.com"; - public string CreationDateAttribute { get; set; } = "whenCreated"; - public string RevisionDateAttribute { get; set; } = "whenChanged"; + public string CreationDateAttribute { get; set; } + public string RevisionDateAttribute { get; set; } } } diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index bea3c8e3..91b0ada3 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -166,6 +166,32 @@ namespace Bit.Core.Services } } + public DateTime? LastGroupSyncDate + { + get + { + return Settings.LastGroupSyncDate; + } + set + { + Settings.LastGroupSyncDate = value; + SaveSettings(); + } + } + + public DateTime? LastUserSyncDate + { + get + { + return Settings.LastUserSyncDate; + } + set + { + Settings.LastUserSyncDate = value; + SaveSettings(); + } + } + public class SettingsModel { public string ApiBaseUrl { get; set; } @@ -175,6 +201,8 @@ namespace Bit.Core.Services public ServerConfiguration Server { get; set; } public SyncConfiguration Sync { get; set; } public Organization Organization { get; set; } + public DateTime? LastGroupSyncDate { get; set; } + public DateTime? LastUserSyncDate { get; set; } } } } diff --git a/src/Core/Utilities/Extensions.cs b/src/Core/Utilities/Extensions.cs new file mode 100644 index 00000000..5137c552 --- /dev/null +++ b/src/Core/Utilities/Extensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bit.Core.Utilities +{ + public static class Extensions + { + private const string GeneralizedTimeFormat = "yyyyMMddHHmmss.f'Z'"; + + public static DateTime ToDateTime(this string generalizedTimeString) + { + return DateTime.ParseExact(generalizedTimeString, GeneralizedTimeFormat, CultureInfo.InvariantCulture); + } + + public static string ToGeneralizedTimeUTC(this DateTime date) + { + return date.ToString("yyyyMMddHHmmss.f'Z'"); + } + } +} diff --git a/src/Core/Utilities/Sync.cs b/src/Core/Utilities/Sync.cs index 2d1229b4..403de437 100644 --- a/src/Core/Utilities/Sync.cs +++ b/src/Core/Utilities/Sync.cs @@ -40,6 +40,8 @@ namespace Bit.Core.Utilities }; } + var now = DateTime.UtcNow; + List groups = null; if(SettingsService.Instance.Sync.SyncGroups) { @@ -58,6 +60,16 @@ namespace Bit.Core.Utilities var response = await ApiService.Instance.PostImportAsync(request); if(response.Succeeded) { + if(SettingsService.Instance.Sync.SyncGroups) + { + SettingsService.Instance.LastGroupSyncDate = now; + } + + if(SettingsService.Instance.Sync.SyncUsers) + { + SettingsService.Instance.LastUserSyncDate = now; + } + return new SyncResult { Success = true, @@ -100,6 +112,16 @@ namespace Bit.Core.Utilities var entry = SettingsService.Instance.Server.GetDirectoryEntry(); var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.GroupFilter) ? null : SettingsService.Instance.Sync.GroupFilter; + + if(!string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.RevisionDateAttribute) && + SettingsService.Instance.LastGroupSyncDate.HasValue) + { + filter = string.Format("(&{0}({1}>{2}))", + filter != null ? string.Format("({0})", filter) : string.Empty, + SettingsService.Instance.Sync.RevisionDateAttribute, + SettingsService.Instance.LastGroupSyncDate.Value.ToGeneralizedTimeUTC()); + } + var searcher = new DirectorySearcher(entry, filter); var result = searcher.FindAll(); @@ -180,6 +202,16 @@ namespace Bit.Core.Utilities var entry = SettingsService.Instance.Server.GetDirectoryEntry(); var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserFilter) ? null : SettingsService.Instance.Sync.UserFilter; + + if(!string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.RevisionDateAttribute) && + SettingsService.Instance.LastUserSyncDate.HasValue) + { + filter = string.Format("(&{0}({1}>{2}))", + filter != null ? string.Format("({0})", filter) : string.Empty, + SettingsService.Instance.Sync.RevisionDateAttribute, + SettingsService.Instance.LastUserSyncDate.Value.ToGeneralizedTimeUTC()); + } + var searcher = new DirectorySearcher(entry, filter); var result = searcher.FindAll();