diff --git a/src/Console/Program.cs b/src/Console/Program.cs index b586c2a3..fe88473b 100644 --- a/src/Console/Program.cs +++ b/src/Console/Program.cs @@ -555,7 +555,7 @@ namespace Bit.Console } Con.WriteLine("Syncing..."); - var result = await Sync.SyncAllAsync(force); + var result = await Sync.SyncAllAsync(force, true); if(result.Success) { @@ -595,7 +595,7 @@ namespace Bit.Console Con.WriteLine("Querying..."); Con.WriteLine(); - var result = await Sync.GatherAsync(force); + var result = await Sync.SyncAllAsync(force, false); if(result.Success) { Con.WriteLine("Groups:"); diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 4885741b..cb41e8fc 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -68,6 +68,9 @@ + + + diff --git a/src/Core/Services/AzureDirectoryService.cs b/src/Core/Services/AzureDirectoryService.cs new file mode 100644 index 00000000..b54c57d8 --- /dev/null +++ b/src/Core/Services/AzureDirectoryService.cs @@ -0,0 +1,32 @@ +using Bit.Core.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Bit.Core.Services +{ + public class AzureDirectoryService : IDirectoryService + { + private static AzureDirectoryService _instance; + + private AzureDirectoryService() { } + + public static IDirectoryService Instance + { + get + { + if(_instance == null) + { + _instance = new AzureDirectoryService(); + } + + return _instance; + } + } + + public Task, List>> GetEntriesAsync(bool force = false) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Core/Services/IDirectoryService.cs b/src/Core/Services/IDirectoryService.cs new file mode 100644 index 00000000..02f4f93f --- /dev/null +++ b/src/Core/Services/IDirectoryService.cs @@ -0,0 +1,14 @@ +using Bit.Core.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public interface IDirectoryService + { + Task, List>> GetEntriesAsync(bool force = false); + } +} diff --git a/src/Core/Services/LdapDirectoryService.cs b/src/Core/Services/LdapDirectoryService.cs new file mode 100644 index 00000000..27795cb3 --- /dev/null +++ b/src/Core/Services/LdapDirectoryService.cs @@ -0,0 +1,235 @@ +using Bit.Core.Models; +using Bit.Core.Utilities; +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Linq; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public class LdapDirectoryService : IDirectoryService + { + private static LdapDirectoryService _instance; + + private LdapDirectoryService() { } + + public static IDirectoryService Instance + { + get + { + if(_instance == null) + { + _instance = new LdapDirectoryService(); + } + + return _instance; + } + } + + public async Task, List>> GetEntriesAsync(bool force = false) + { + if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet) + { + throw new ApplicationException("Not logged in or have an org set."); + } + + if(SettingsService.Instance.Server == null) + { + throw new ApplicationException("No configuration for directory server."); + } + + if(SettingsService.Instance.Sync == null) + { + throw new ApplicationException("No configuration for sync."); + } + + List groups = null; + if(SettingsService.Instance.Sync.SyncGroups) + { + groups = await GetGroupsAsync(force); + } + + List users = null; + if(SettingsService.Instance.Sync.SyncUsers) + { + users = await GetUsersAsync(force); + } + + return new Tuple, List>(groups, users); + } + + private static Task> GetGroupsAsync(bool force = false) + { + if(!SettingsService.Instance.Sync.SyncGroups) + { + throw new ApplicationException("Not configured to sync groups."); + } + + if(SettingsService.Instance.Server == null) + { + throw new ApplicationException("No configuration for directory server."); + } + + if(SettingsService.Instance.Sync == null) + { + throw new ApplicationException("No configuration for sync."); + } + + if(!AuthService.Instance.Authenticated) + { + throw new ApplicationException("Not authenticated."); + } + + var entry = SettingsService.Instance.Server.GetDirectoryEntry(); + var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.GroupFilter) ? null : + SettingsService.Instance.Sync.GroupFilter; + + if(!force && !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(); + + var groups = new List(); + foreach(SearchResult item in result) + { + var group = new GroupEntry + { + DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault() + }; + + if(group.DistinguishedName == null) + { + continue; + } + + // Name + if(item.Properties.Contains(SettingsService.Instance.Sync.GroupNameAttribute) && + item.Properties[SettingsService.Instance.Sync.GroupNameAttribute].Count > 0) + { + group.Name = item.Properties[SettingsService.Instance.Sync.GroupNameAttribute][0].ToString(); + } + else if(item.Properties.Contains("cn") && item.Properties["cn"].Count > 0) + { + group.Name = item.Properties["cn"][0].ToString(); + } + else + { + continue; + } + + // Dates + group.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.CreationDateAttribute); + group.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.RevisionDateAttribute); + + // Members + if(item.Properties.Contains(SettingsService.Instance.Sync.MemberAttribute) && + item.Properties[SettingsService.Instance.Sync.MemberAttribute].Count > 0) + { + foreach(var member in item.Properties[SettingsService.Instance.Sync.MemberAttribute]) + { + var memberDn = member.ToString(); + if(!group.Members.Contains(memberDn)) + { + group.Members.Add(memberDn); + } + } + } + + groups.Add(group); + } + + return Task.FromResult(groups); + } + + private static Task> GetUsersAsync(bool force = false) + { + if(!SettingsService.Instance.Sync.SyncUsers) + { + throw new ApplicationException("Not configured to sync users."); + } + + if(SettingsService.Instance.Server == null) + { + throw new ApplicationException("No configuration for directory server."); + } + + if(SettingsService.Instance.Sync == null) + { + throw new ApplicationException("No configuration for sync."); + } + + if(!AuthService.Instance.Authenticated) + { + throw new ApplicationException("Not authenticated."); + } + + var entry = SettingsService.Instance.Server.GetDirectoryEntry(); + var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserFilter) ? null : + SettingsService.Instance.Sync.UserFilter; + + if(!force && !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(); + + var users = new List(); + foreach(SearchResult item in result) + { + var user = new UserEntry + { + DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault() + }; + + if(user.DistinguishedName == null) + { + continue; + } + + // Email + if(SettingsService.Instance.Sync.EmailPrefixSuffix && + item.Properties.Contains(SettingsService.Instance.Sync.UserEmailPrefixAttribute) && + item.Properties[SettingsService.Instance.Sync.UserEmailPrefixAttribute].Count > 0 && + !string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserEmailSuffix)) + { + user.Email = string.Concat( + item.Properties[SettingsService.Instance.Sync.UserEmailPrefixAttribute][0].ToString(), + SettingsService.Instance.Sync.UserEmailSuffix).ToLowerInvariant(); + } + else if(item.Properties.Contains(SettingsService.Instance.Sync.UserEmailAttribute) && + item.Properties[SettingsService.Instance.Sync.UserEmailAttribute].Count > 0) + { + user.Email = item.Properties[SettingsService.Instance.Sync.UserEmailAttribute][0] + .ToString() + .ToLowerInvariant(); + } + else + { + continue; + } + + // Dates + user.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.CreationDateAttribute); + user.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.RevisionDateAttribute); + + users.Add(user); + } + + return Task.FromResult(users); + } + } +} diff --git a/src/Core/Utilities/Sync.cs b/src/Core/Utilities/Sync.cs index 2aa48b7f..2f2eea43 100644 --- a/src/Core/Utilities/Sync.cs +++ b/src/Core/Utilities/Sync.cs @@ -1,9 +1,7 @@ using Bit.Core.Models; using Bit.Core.Services; using System; -using System.Collections; using System.Collections.Generic; -using System.DirectoryServices; using System.Linq; using System.Threading.Tasks; @@ -11,268 +9,76 @@ namespace Bit.Core.Utilities { public static class Sync { - public static async Task SyncAllAsync(bool force = false) + public static async Task SyncAllAsync(bool force = false, bool sendToServer = true) { - var now = DateTime.UtcNow; - var gatherResult = await GatherAsync(force); - if(!gatherResult.Success) + try { - return gatherResult; - } + var now = DateTime.UtcNow; + var entriesResult = await GetDirectoryService().GetEntriesAsync(force); + var groups = entriesResult.Item1; + var users = entriesResult.Item2; - var request = new ImportRequest(gatherResult.Groups, gatherResult.Users); - var response = await ApiService.Instance.PostImportAsync(request); - if(response.Succeeded) - { - if(SettingsService.Instance.Sync.SyncGroups) + FlattenGroupsToUsers(groups, null, groups, users); + + if(!sendToServer) { - SettingsService.Instance.LastGroupSyncDate = now; - } - - if(SettingsService.Instance.Sync.SyncUsers) - { - SettingsService.Instance.LastUserSyncDate = now; - } - - return new SyncResult - { - Success = true, - Groups = gatherResult.Groups, - Users = gatherResult.Users - }; - } - else - { - return new SyncResult - { - Success = false, - ErrorMessage = response.Errors.FirstOrDefault()?.Message - }; - } - } - - public static async Task GatherAsync(bool force = false) - { - if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet) - { - return new SyncResult - { - Success = false, - ErrorMessage = "Not logged in or have an org set." - }; - } - - if(SettingsService.Instance.Server == null) - { - return new SyncResult - { - Success = false, - ErrorMessage = "No configuration for directory server." - }; - } - - if(SettingsService.Instance.Sync == null) - { - return new SyncResult - { - Success = false, - ErrorMessage = "No configuration for sync." - }; - } - - List groups = null; - if(SettingsService.Instance.Sync.SyncGroups) - { - groups = await GetGroupsAsync(force); - } - - List users = null; - if(SettingsService.Instance.Sync.SyncUsers) - { - users = await GetUsersAsync(force); - } - - FlattenGroupsToUsers(groups, null, groups, users); - - return new SyncResult - { - Success = true, - Groups = groups, - Users = users - }; - } - - private static Task> GetGroupsAsync(bool force = false) - { - if(!SettingsService.Instance.Sync.SyncGroups) - { - throw new ApplicationException("Not configured to sync groups."); - } - - if(SettingsService.Instance.Server == null) - { - throw new ApplicationException("No configuration for directory server."); - } - - if(SettingsService.Instance.Sync == null) - { - throw new ApplicationException("No configuration for sync."); - } - - if(!AuthService.Instance.Authenticated) - { - throw new ApplicationException("Not authenticated."); - } - - var entry = SettingsService.Instance.Server.GetDirectoryEntry(); - var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.GroupFilter) ? null : - SettingsService.Instance.Sync.GroupFilter; - - if(!force && !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(); - - var groups = new List(); - foreach(SearchResult item in result) - { - var group = new GroupEntry - { - DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault() - }; - - if(group.DistinguishedName == null) - { - continue; - } - - // Name - if(item.Properties.Contains(SettingsService.Instance.Sync.GroupNameAttribute) && - item.Properties[SettingsService.Instance.Sync.GroupNameAttribute].Count > 0) - { - group.Name = item.Properties[SettingsService.Instance.Sync.GroupNameAttribute][0].ToString(); - } - else if(item.Properties.Contains("cn") && item.Properties["cn"].Count > 0) - { - group.Name = item.Properties["cn"][0].ToString(); - } - else - { - continue; - } - - // Dates - group.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.CreationDateAttribute); - group.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.RevisionDateAttribute); - - // Members - if(item.Properties.Contains(SettingsService.Instance.Sync.MemberAttribute) && - item.Properties[SettingsService.Instance.Sync.MemberAttribute].Count > 0) - { - foreach(var member in item.Properties[SettingsService.Instance.Sync.MemberAttribute]) + return new SyncResult { - var memberDn = member.ToString(); - if(!group.Members.Contains(memberDn)) - { - group.Members.Add(memberDn); - } + Success = true, + Groups = groups, + Users = users + }; + } + + var request = new ImportRequest(groups, users); + var response = await ApiService.Instance.PostImportAsync(request); + if(response.Succeeded) + { + if(SettingsService.Instance.Sync.SyncGroups) + { + SettingsService.Instance.LastGroupSyncDate = now; } - } - groups.Add(group); - } + if(SettingsService.Instance.Sync.SyncUsers) + { + SettingsService.Instance.LastUserSyncDate = now; + } - return Task.FromResult(groups); - } - - private static Task> GetUsersAsync(bool force = false) - { - if(!SettingsService.Instance.Sync.SyncUsers) - { - throw new ApplicationException("Not configured to sync users."); - } - - if(SettingsService.Instance.Server == null) - { - throw new ApplicationException("No configuration for directory server."); - } - - if(SettingsService.Instance.Sync == null) - { - throw new ApplicationException("No configuration for sync."); - } - - if(!AuthService.Instance.Authenticated) - { - throw new ApplicationException("Not authenticated."); - } - - var entry = SettingsService.Instance.Server.GetDirectoryEntry(); - var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserFilter) ? null : - SettingsService.Instance.Sync.UserFilter; - - if(!force && !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(); - - var users = new List(); - foreach(SearchResult item in result) - { - var user = new UserEntry - { - DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault() - }; - - if(user.DistinguishedName == null) - { - continue; - } - - // Email - if(SettingsService.Instance.Sync.EmailPrefixSuffix && - item.Properties.Contains(SettingsService.Instance.Sync.UserEmailPrefixAttribute) && - item.Properties[SettingsService.Instance.Sync.UserEmailPrefixAttribute].Count > 0 && - !string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserEmailSuffix)) - { - user.Email = string.Concat( - item.Properties[SettingsService.Instance.Sync.UserEmailPrefixAttribute][0].ToString(), - SettingsService.Instance.Sync.UserEmailSuffix).ToLowerInvariant(); - } - else if(item.Properties.Contains(SettingsService.Instance.Sync.UserEmailAttribute) && - item.Properties[SettingsService.Instance.Sync.UserEmailAttribute].Count > 0) - { - user.Email = item.Properties[SettingsService.Instance.Sync.UserEmailAttribute][0] - .ToString() - .ToLowerInvariant(); + return new SyncResult + { + Success = true, + Groups = groups, + Users = users + }; } else { - continue; + return new SyncResult + { + Success = false, + ErrorMessage = response.Errors.FirstOrDefault()?.Message + }; } - - // Dates - user.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.CreationDateAttribute); - user.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.RevisionDateAttribute); - - users.Add(user); } + catch (ApplicationException e) + { + return new SyncResult + { + Success = false, + ErrorMessage = e.Message + }; + } + } - return Task.FromResult(users); + private static IDirectoryService GetDirectoryService() + { + switch(SettingsService.Instance.Server.Type) + { + case Enums.DirectoryType.AzureActiveDirectory: + return AzureDirectoryService.Instance; + default: + return LdapDirectoryService.Instance; + } } private static void FlattenGroupsToUsers(List currentGroups, List currentGroupsUsers,