1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-15 07:43:27 +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
{
string input;
Con.Write("Address: ");
config.Address = Con.ReadLine().Trim();
Con.Write("Port (389): ");
var portInput = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(portInput))
Con.Write("Port [{0}]: ", config.Port);
input = Con.ReadLine().Trim();
if(!string.IsNullOrWhiteSpace(input))
{
config.Port = portInput;
config.Port = input;
}
Con.Write("Path: ");
config.Path = Con.ReadLine().Trim();
Con.Write("Username: ");
config.Username = Con.ReadLine().Trim();
Con.Write("Password: ");
var passwordInput = ReadSecureLine();
if(!string.IsNullOrWhiteSpace(passwordInput))
input = ReadSecureLine();
if(!string.IsNullOrWhiteSpace(input))
{
config.Password = new EncryptedData(passwordInput);
passwordInput = null;
config.Password = new EncryptedData(input);
input = null;
}
Con.WriteLine();
Con.Write("Group filter: ");
config.GroupFilter = Con.ReadLine().Trim();
Con.Write("User filter: ");
config.UserFilter = Con.ReadLine().Trim();
Con.Write("Sync groups? [y]: ");
input = Con.ReadLine().Trim().ToLower();
config.SyncGroups = input == "y" || input == "yes" || string.IsNullOrWhiteSpace(input);
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();

View File

@@ -51,6 +51,7 @@
<ItemGroup>
<Compile Include="Models\ApiError.cs" />
<Compile Include="Models\ApiResult.cs" />
<Compile Include="Models\Entry.cs" />
<Compile Include="Models\ServerConfiguration.cs" />
<Compile Include="Models\LoginResult.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; }
[JsonIgnore]
public string ServerPath => $"LDAP://{Address}:{Port}/{Path}";
public string GroupFilter { get; set; }
public string UserFilter { get; set; }
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 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()
{

View File

@@ -1,4 +1,5 @@
using System;
using Bit.Core.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
@@ -10,8 +11,13 @@ namespace Bit.Core.Utilities
{
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)
{
throw new ApplicationException("No configuration for directory server.");
@@ -23,18 +29,70 @@ namespace Bit.Core.Utilities
}
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;
var searcher = new DirectorySearcher(entry, filter);
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)
{
throw new ApplicationException("No configuration for directory server.");
@@ -51,40 +109,101 @@ namespace Bit.Core.Utilities
var searcher = new DirectorySearcher(entry, filter);
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()
{
await SyncGroupsAsync();
await SyncUsersAsync();
List<GroupEntry> groups = null;
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;
for(int i = 0; i < vals.Count; i++)
if(users != null)
{
Console.Write(vals[i]);
if(i != vals.Count - 1)
{
Console.Write(" | ");
}
group.UserMembers = users.Where(u => group.Members.Contains(u.DistinguishedName)).ToList();
}
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;
}
}
}