1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-14 23:33:19 +00:00

server configs and building group/user entries

This commit is contained in:
Kyle Spearrin
2017-05-12 17:57:42 -04:00
parent 7f4bda6820
commit 83d93bbf51
5 changed files with 233 additions and 38 deletions

View File

@@ -242,30 +242,66 @@ namespace Bit.Console
} }
else else
{ {
string input;
Con.Write("Address: "); Con.Write("Address: ");
config.Address = Con.ReadLine().Trim(); config.Address = Con.ReadLine().Trim();
Con.Write("Port (389): "); Con.Write("Port [{0}]: ", config.Port);
var portInput = Con.ReadLine().Trim(); input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(portInput)) if(!string.IsNullOrWhiteSpace(input))
{ {
config.Port = portInput; config.Port = input;
} }
Con.Write("Path: "); Con.Write("Path: ");
config.Path = Con.ReadLine().Trim(); config.Path = Con.ReadLine().Trim();
Con.Write("Username: "); Con.Write("Username: ");
config.Username = Con.ReadLine().Trim(); config.Username = Con.ReadLine().Trim();
Con.Write("Password: "); Con.Write("Password: ");
var passwordInput = ReadSecureLine(); input = ReadSecureLine();
if(!string.IsNullOrWhiteSpace(passwordInput)) if(!string.IsNullOrWhiteSpace(input))
{ {
config.Password = new EncryptedData(passwordInput); config.Password = new EncryptedData(input);
passwordInput = null; input = null;
} }
Con.WriteLine(); Con.WriteLine();
Con.Write("Group filter: "); Con.Write("Sync groups? [y]: ");
config.GroupFilter = Con.ReadLine().Trim(); input = Con.ReadLine().Trim().ToLower();
Con.Write("User filter: "); config.SyncGroups = input == "y" || input == "yes" || string.IsNullOrWhiteSpace(input);
config.UserFilter = Con.ReadLine().Trim(); if(config.SyncGroups)
{
Con.Write("Group filter [{0}]: ", config.GroupFilter);
input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(input))
{
config.GroupFilter = input;
}
Con.Write("Group name attribute [{0}]: ", config.GroupNameAttribute);
input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(input))
{
config.GroupNameAttribute = input;
}
}
Con.Write("Sync users? [y]: ");
input = Con.ReadLine().Trim().ToLower();
config.SyncUsers = input == "y" || input == "yes" || string.IsNullOrWhiteSpace(input);
if(config.SyncUsers)
{
Con.Write("User filter [{0}]: ", config.UserFilter);
input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(input))
{
config.UserFilter = input;
}
Con.Write("User email attribute [{0}]: ", config.UserEmailAttribute);
input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(input))
{
config.GroupNameAttribute = input;
}
}
input = null;
} }
Con.WriteLine(); Con.WriteLine();

View File

@@ -51,6 +51,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Models\ApiError.cs" /> <Compile Include="Models\ApiError.cs" />
<Compile Include="Models\ApiResult.cs" /> <Compile Include="Models\ApiResult.cs" />
<Compile Include="Models\Entry.cs" />
<Compile Include="Models\ServerConfiguration.cs" /> <Compile Include="Models\ServerConfiguration.cs" />
<Compile Include="Models\LoginResult.cs" /> <Compile Include="Models\LoginResult.cs" />
<Compile Include="Models\ErrorResponse.cs" /> <Compile Include="Models\ErrorResponse.cs" />

28
src/Core/Models/Entry.cs Normal file
View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bit.Core.Models
{
public abstract class Entry
{
public string DistinguishedName { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? RevisionDate { get; set; }
}
public class GroupEntry : Entry
{
public string Name { get; set; }
public HashSet<string> Members { get; set; } = new HashSet<string>();
public List<GroupEntry> GroupMembers { get; set; } = new List<GroupEntry>();
public List<UserEntry> UserMembers { get; set; } = new List<UserEntry>();
}
public class UserEntry : Entry
{
public string Email { get; set; }
}
}

View File

@@ -17,8 +17,19 @@ namespace Bit.Core.Models
public EncryptedData Password { get; set; } public EncryptedData Password { get; set; }
[JsonIgnore] [JsonIgnore]
public string ServerPath => $"LDAP://{Address}:{Port}/{Path}"; public string ServerPath => $"LDAP://{Address}:{Port}/{Path}";
public string GroupFilter { get; set; } public string GroupFilter { get; set; } = "(&(objectClass=group))";
public string UserFilter { get; set; } public string UserFilter { get; set; } = "(&(objectClass=person))";
public bool SyncGroups { get; set; } = true;
public bool SyncUsers { get; set; } = true;
public string Type { get; set; } = "Active Directory";
public string MemberAttribute { get; set; } = "memberOf";
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 UserEmailSuffix { get; set; } = "@companyname.com";
public string CreationDateAttribute { get; set; } = "whenCreated";
public string RevisionDateAttribute { get; set; } = "whenChanged";
public DirectoryEntry GetDirectoryEntry() public DirectoryEntry GetDirectoryEntry()
{ {

View File

@@ -1,4 +1,5 @@
using System; using Bit.Core.Models;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.DirectoryServices; using System.DirectoryServices;
@@ -10,8 +11,13 @@ namespace Bit.Core.Utilities
{ {
public static class Sync public static class Sync
{ {
public static Task SyncGroupsAsync() public static Task<List<GroupEntry>> SyncGroupsAsync()
{ {
if(!Services.SettingsService.Instance.Server.SyncGroups)
{
throw new ApplicationException("Not configured to sync groups.");
}
if(Services.SettingsService.Instance.Server == null) if(Services.SettingsService.Instance.Server == null)
{ {
throw new ApplicationException("No configuration for directory server."); throw new ApplicationException("No configuration for directory server.");
@@ -23,18 +29,70 @@ namespace Bit.Core.Utilities
} }
var entry = Services.SettingsService.Instance.Server.GetDirectoryEntry(); var entry = Services.SettingsService.Instance.Server.GetDirectoryEntry();
var filter = string.IsNullOrWhiteSpace(Services.SettingsService.Instance.Server.GroupFilter) ? null : var filter = string.IsNullOrWhiteSpace(Services.SettingsService.Instance.Server.GroupFilter) ? null :
Services.SettingsService.Instance.Server.GroupFilter; Services.SettingsService.Instance.Server.GroupFilter;
var searcher = new DirectorySearcher(entry, filter); var searcher = new DirectorySearcher(entry, filter);
var result = searcher.FindAll(); var result = searcher.FindAll();
PrintSearchResults(result); var groups = new List<GroupEntry>();
foreach(SearchResult item in result)
{
var group = new GroupEntry
{
DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault()
};
return Task.FromResult(0); if(group.DistinguishedName == null)
{
continue;
}
// Name
if(item.Properties.Contains(Services.SettingsService.Instance.Server.GroupNameAttribute) &&
item.Properties[Services.SettingsService.Instance.Server.GroupNameAttribute].Count > 0)
{
group.Name = item.Properties[Services.SettingsService.Instance.Server.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 = ParseDate(item.Properties, Services.SettingsService.Instance.Server.CreationDateAttribute);
group.RevisionDate = ParseDate(item.Properties, Services.SettingsService.Instance.Server.RevisionDateAttribute);
// Members
if(item.Properties.Contains(Services.SettingsService.Instance.Server.MemberAttribute) &&
item.Properties[Services.SettingsService.Instance.Server.MemberAttribute].Count > 0)
{
foreach(var member in item.Properties[Services.SettingsService.Instance.Server.MemberAttribute])
{
var memberDn = member.ToString();
if(!group.Members.Contains(memberDn))
{
group.Members.Add(memberDn);
}
}
}
groups.Add(group);
}
return Task.FromResult(groups);
} }
public static Task SyncUsersAsync() public static Task<List<UserEntry>> SyncUsersAsync()
{ {
if(!Services.SettingsService.Instance.Server.SyncUsers)
{
throw new ApplicationException("Not configured to sync users.");
}
if(Services.SettingsService.Instance.Server == null) if(Services.SettingsService.Instance.Server == null)
{ {
throw new ApplicationException("No configuration for directory server."); throw new ApplicationException("No configuration for directory server.");
@@ -51,40 +109,101 @@ namespace Bit.Core.Utilities
var searcher = new DirectorySearcher(entry, filter); var searcher = new DirectorySearcher(entry, filter);
var result = searcher.FindAll(); var result = searcher.FindAll();
PrintSearchResults(result); var users = new List<UserEntry>();
foreach(SearchResult item in result)
{
var user = new UserEntry
{
DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault()
};
return Task.FromResult(0); if(user.DistinguishedName == null)
{
continue;
}
// Email
if(Services.SettingsService.Instance.Server.EmailPrefixSuffix &&
item.Properties.Contains(Services.SettingsService.Instance.Server.UserEmailPrefixAttribute) &&
item.Properties[Services.SettingsService.Instance.Server.UserEmailPrefixAttribute].Count > 0 &&
!string.IsNullOrWhiteSpace(Services.SettingsService.Instance.Server.UserEmailSuffix))
{
user.Email = string.Concat(
item.Properties[Services.SettingsService.Instance.Server.UserEmailPrefixAttribute][0].ToString(),
Services.SettingsService.Instance.Server.UserEmailSuffix).ToLowerInvariant();
}
else if(item.Properties.Contains(Services.SettingsService.Instance.Server.UserEmailAttribute) &&
item.Properties[Services.SettingsService.Instance.Server.UserEmailAttribute].Count > 0)
{
user.Email = item.Properties[Services.SettingsService.Instance.Server.UserEmailAttribute][0]
.ToString()
.ToLowerInvariant();
}
else
{
continue;
}
// Dates
user.CreationDate = ParseDate(item.Properties, Services.SettingsService.Instance.Server.CreationDateAttribute);
user.RevisionDate = ParseDate(item.Properties, Services.SettingsService.Instance.Server.RevisionDateAttribute);
users.Add(user);
}
return Task.FromResult(users);
} }
public static async Task SyncAllAsync() public static async Task SyncAllAsync()
{ {
await SyncGroupsAsync(); List<GroupEntry> groups = null;
await SyncUsersAsync(); if(Services.SettingsService.Instance.Server.SyncGroups)
{
groups = await SyncGroupsAsync();
}
List<UserEntry> users = null;
if(Services.SettingsService.Instance.Server.SyncUsers)
{
users = await SyncUsersAsync();
}
AssociateMembers(ref groups, ref users);
} }
private static void PrintSearchResults(SearchResultCollection result) private static void AssociateMembers(ref List<GroupEntry> groups, ref List<UserEntry> users)
{ {
foreach(SearchResult item in result) if(groups == null)
{ {
Console.WriteLine(item.Path); return;
}
foreach(DictionaryEntry prop in item.Properties) foreach(var group in groups)
{
if(group.Members.Any())
{ {
Console.Write(" " + prop.Key + ": "); group.GroupMembers = groups.Where(g => group.Members.Contains(g.DistinguishedName)).ToList();
var vals = prop.Value as ResultPropertyValueCollection; if(users != null)
for(int i = 0; i < vals.Count; i++)
{ {
Console.Write(vals[i]); group.UserMembers = users.Where(u => group.Members.Contains(u.DistinguishedName)).ToList();
if(i != vals.Count - 1)
{
Console.Write(" | ");
}
} }
Console.Write("\n");
} }
} }
// TODO: Handle nested group associations
}
private static DateTime? ParseDate(ResultPropertyCollection collection, string dateKey)
{
DateTime date;
if(collection.Contains(dateKey) && collection[dateKey].Count > 0 &&
DateTime.TryParse(collection[dateKey][0].ToString(), out date))
{
return date;
}
return null;
} }
} }
} }