1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-21 10:43:16 +00:00

Interface out directory service for Azure implem.

This commit is contained in:
Kyle Spearrin
2017-05-15 09:22:51 -04:00
parent 90cd60e62d
commit 6ede5550b8
6 changed files with 342 additions and 252 deletions

View File

@@ -555,7 +555,7 @@ namespace Bit.Console
} }
Con.WriteLine("Syncing..."); Con.WriteLine("Syncing...");
var result = await Sync.SyncAllAsync(force); var result = await Sync.SyncAllAsync(force, true);
if(result.Success) if(result.Success)
{ {
@@ -595,7 +595,7 @@ namespace Bit.Console
Con.WriteLine("Querying..."); Con.WriteLine("Querying...");
Con.WriteLine(); Con.WriteLine();
var result = await Sync.GatherAsync(force); var result = await Sync.SyncAllAsync(force, false);
if(result.Success) if(result.Success)
{ {
Con.WriteLine("Groups:"); Con.WriteLine("Groups:");

View File

@@ -68,6 +68,9 @@
<Compile Include="Models\ProfileResponse.cs" /> <Compile Include="Models\ProfileResponse.cs" />
<Compile Include="Models\TokenResponse.cs" /> <Compile Include="Models\TokenResponse.cs" />
<Compile Include="Services\ApiService.cs" /> <Compile Include="Services\ApiService.cs" />
<Compile Include="Services\AzureDirectoryService.cs" />
<Compile Include="Services\LdapDirectoryService.cs" />
<Compile Include="Services\IDirectoryService.cs" />
<Compile Include="Services\SettingsService.cs" /> <Compile Include="Services\SettingsService.cs" />
<Compile Include="Utilities\Crypto.cs" /> <Compile Include="Utilities\Crypto.cs" />
<Compile Include="Services\TokenService.cs" /> <Compile Include="Services\TokenService.cs" />

View File

@@ -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<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
{
throw new NotImplementedException();
}
}
}

View File

@@ -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<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false);
}
}

View File

@@ -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<Tuple<List<GroupEntry>, List<UserEntry>>> 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<GroupEntry> groups = null;
if(SettingsService.Instance.Sync.SyncGroups)
{
groups = await GetGroupsAsync(force);
}
List<UserEntry> users = null;
if(SettingsService.Instance.Sync.SyncUsers)
{
users = await GetUsersAsync(force);
}
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
}
private static Task<List<GroupEntry>> 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<GroupEntry>();
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<List<UserEntry>> 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<UserEntry>();
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);
}
}
}

View File

@@ -1,9 +1,7 @@
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Services; using Bit.Core.Services;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,268 +9,76 @@ namespace Bit.Core.Utilities
{ {
public static class Sync public static class Sync
{ {
public static async Task<SyncResult> SyncAllAsync(bool force = false) public static async Task<SyncResult> SyncAllAsync(bool force = false, bool sendToServer = true)
{ {
var now = DateTime.UtcNow; try
var gatherResult = await GatherAsync(force);
if(!gatherResult.Success)
{ {
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); FlattenGroupsToUsers(groups, null, groups, users);
var response = await ApiService.Instance.PostImportAsync(request);
if(response.Succeeded) if(!sendToServer)
{
if(SettingsService.Instance.Sync.SyncGroups)
{ {
SettingsService.Instance.LastGroupSyncDate = now; return new SyncResult
}
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<SyncResult> 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<GroupEntry> groups = null;
if(SettingsService.Instance.Sync.SyncGroups)
{
groups = await GetGroupsAsync(force);
}
List<UserEntry> 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<List<GroupEntry>> 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<GroupEntry>();
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(); Success = true,
if(!group.Members.Contains(memberDn)) Groups = groups,
{ Users = users
group.Members.Add(memberDn); };
} }
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); return new SyncResult
} {
Success = true,
private static Task<List<UserEntry>> GetUsersAsync(bool force = false) Groups = groups,
{ Users = users
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<UserEntry>();
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 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<GroupEntry> currentGroups, List<UserEntry> currentGroupsUsers, private static void FlattenGroupsToUsers(List<GroupEntry> currentGroups, List<UserEntry> currentGroupsUsers,