diff --git a/src/Console/Program.cs b/src/Console/Program.cs
index fe88473b..ccf88a3b 100644
--- a/src/Console/Program.cs
+++ b/src/Console/Program.cs
@@ -286,29 +286,52 @@ namespace Bit.Console
}
}
- if(parameters.ContainsKey("a"))
+ if(config.Type == Core.Enums.DirectoryType.AzureActiveDirectory)
{
- config.Address = parameters["a"];
- }
+ config.Azure = new AzureConfiguration();
- if(parameters.ContainsKey("port"))
- {
- config.Port = parameters["port"];
- }
+ if(parameters.ContainsKey("i"))
+ {
+ config.Azure.Id = parameters["i"];
+ }
- if(parameters.ContainsKey("path"))
- {
- config.Path = parameters["path"];
- }
+ if(parameters.ContainsKey("s"))
+ {
+ config.Azure.Secret = new EncryptedData(parameters["s"]);
+ }
- if(parameters.ContainsKey("u"))
- {
- config.Username = parameters["u"];
+ if(parameters.ContainsKey("t"))
+ {
+ config.Azure.Tenant = parameters["t"];
+ }
}
-
- if(parameters.ContainsKey("p"))
+ else
{
- config.Password = new EncryptedData(parameters["p"]);
+ config.Ldap = config.Ldap ?? new LdapConfiguration();
+ if(parameters.ContainsKey("a"))
+ {
+ config.Ldap.Address = parameters["a"];
+ }
+
+ if(parameters.ContainsKey("port"))
+ {
+ config.Ldap.Port = parameters["port"];
+ }
+
+ if(parameters.ContainsKey("path"))
+ {
+ config.Ldap.Path = parameters["path"];
+ }
+
+ if(parameters.ContainsKey("u"))
+ {
+ config.Ldap.Username = parameters["u"];
+ }
+
+ if(parameters.ContainsKey("p"))
+ {
+ config.Ldap.Password = new EncryptedData(parameters["p"]);
+ }
}
}
else
@@ -316,8 +339,8 @@ namespace Bit.Console
string input;
Con.WriteLine("1. Active Directory");
- //Con.WriteLine("2. Azure Active Directory ");
- Con.WriteLine("2. Other LDAP Directory");
+ Con.WriteLine("2. Azure Active Directory ");
+ Con.WriteLine("3. Other LDAP Directory");
string currentType;
switch(config.Type)
@@ -325,9 +348,12 @@ namespace Bit.Console
case Core.Enums.DirectoryType.ActiveDirectory:
currentType = "1";
break;
- default:
+ case Core.Enums.DirectoryType.AzureActiveDirectory:
currentType = "2";
break;
+ default:
+ currentType = "3";
+ break;
}
Con.Write("Type [{0}]: ", currentType);
input = Con.ReadLine();
@@ -338,44 +364,74 @@ namespace Bit.Console
case "1":
config.Type = Core.Enums.DirectoryType.ActiveDirectory;
break;
- //case "2":
- // config.Type = Core.Enums.DirectoryType.AzureActiveCirectory;
+ case "2":
+ config.Type = Core.Enums.DirectoryType.AzureActiveDirectory;
+ break;
default:
config.Type = Core.Enums.DirectoryType.Other;
break;
}
}
- Con.Write("Address [{0}]: ", config.Address);
- input = Con.ReadLine();
- if(!string.IsNullOrEmpty(input))
+ if(config.Type == Core.Enums.DirectoryType.AzureActiveDirectory)
{
- config.Address = input;
+ config.Azure = config.Azure ?? new AzureConfiguration();
+
+ Con.Write("Tenant [{0}]: ", config.Azure.Tenant);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Azure.Tenant = input;
+ }
+ Con.Write("Application Id [{0}]: ", config.Azure.Id);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Azure.Id = input;
+ }
+ Con.Write("Secret key: ");
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Azure.Secret = new EncryptedData(input);
+ input = null;
+ }
}
- Con.Write("Port [{0}]: ", config.Port);
- input = Con.ReadLine();
- if(!string.IsNullOrEmpty(input))
+ else
{
- config.Port = input;
- }
- Con.Write("Path [{0}]: ", config.Path);
- input = Con.ReadLine();
- if(!string.IsNullOrEmpty(input))
- {
- config.Path = input;
- }
- Con.Write("Username [{0}]: ", config.Username);
- input = Con.ReadLine();
- if(!string.IsNullOrEmpty(input))
- {
- config.Username = input;
- }
- Con.Write("Password: ");
- input = ReadSecureLine();
- if(!string.IsNullOrEmpty(input))
- {
- config.Password = new EncryptedData(input);
- input = null;
+ config.Ldap = new LdapConfiguration();
+
+ Con.Write("Address [{0}]: ", config.Ldap.Address);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Ldap.Address = input;
+ }
+ Con.Write("Port [{0}]: ", config.Ldap.Port);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Ldap.Port = input;
+ }
+ Con.Write("Path [{0}]: ", config.Ldap.Path);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Ldap.Path = input;
+ }
+ Con.Write("Username [{0}]: ", config.Ldap.Username);
+ input = Con.ReadLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Ldap.Username = input;
+ }
+ Con.Write("Password: ");
+ input = ReadSecureLine();
+ if(!string.IsNullOrEmpty(input))
+ {
+ config.Ldap.Password = new EncryptedData(input);
+ input = null;
+ }
}
input = null;
@@ -383,7 +439,14 @@ namespace Bit.Console
Con.WriteLine();
Con.WriteLine();
- if(string.IsNullOrWhiteSpace(config.Address))
+ if(config.Ldap != null && string.IsNullOrWhiteSpace(config.Ldap.Address))
+ {
+ Con.ForegroundColor = ConsoleColor.Red;
+ Con.WriteLine("Invalid input parameters.");
+ Con.ResetColor();
+ }
+ else if(config.Azure != null && (string.IsNullOrWhiteSpace(config.Azure.Id) ||
+ config.Azure.Secret == null || string.IsNullOrWhiteSpace(config.Azure.Tenant)))
{
Con.ForegroundColor = ConsoleColor.Red;
Con.WriteLine("Invalid input parameters.");
@@ -601,7 +664,7 @@ namespace Bit.Console
Con.WriteLine("Groups:");
foreach(var group in result.Groups)
{
- Con.WriteLine(" {0} - {1}", group.Name, group.DistinguishedName);
+ Con.WriteLine(" {0} - {1}", group.Name, group.Id);
}
Con.WriteLine();
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index cb41e8fc..034dbabd 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -33,6 +33,18 @@
..\..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll
+
+ ..\..\packages\Microsoft.Graph.1.3.0\lib\net45\Microsoft.Graph.dll
+
+
+ ..\..\packages\Microsoft.Graph.Core.1.4.0\lib\net45\Microsoft.Graph.Core.dll
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll
+
..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll
@@ -56,10 +68,12 @@
+
+
-
+
@@ -72,6 +86,7 @@
+
diff --git a/src/Core/Models/AzureConfiguration.cs b/src/Core/Models/AzureConfiguration.cs
new file mode 100644
index 00000000..cd7258a0
--- /dev/null
+++ b/src/Core/Models/AzureConfiguration.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.DirectoryServices;
+
+namespace Bit.Core.Models
+{
+ public class AzureConfiguration
+ {
+ public string Tenant { get; set; } = "yourcompany.onmicrosoft.com";
+ public string Id { get; set; }
+ public EncryptedData Secret { get; set; }
+ }
+}
diff --git a/src/Core/Models/Entry.cs b/src/Core/Models/Entry.cs
index 351e1ea0..df412cce 100644
--- a/src/Core/Models/Entry.cs
+++ b/src/Core/Models/Entry.cs
@@ -8,7 +8,7 @@ namespace Bit.Core.Models
{
public abstract class Entry
{
- public string DistinguishedName { get; set; }
+ public string Id { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? RevisionDate { get; set; }
}
diff --git a/src/Core/Models/ImportRequest.cs b/src/Core/Models/ImportRequest.cs
index 619ad841..77d91b7c 100644
--- a/src/Core/Models/ImportRequest.cs
+++ b/src/Core/Models/ImportRequest.cs
@@ -19,7 +19,7 @@ namespace Bit.Core.Models
public Group(GroupEntry entry)
{
Name = entry.Name;
- ExternalId = entry.DistinguishedName;
+ ExternalId = entry.Id;
}
public string Name { get; set; }
diff --git a/src/Core/Models/LdapConfiguration.cs b/src/Core/Models/LdapConfiguration.cs
new file mode 100644
index 00000000..763b954b
--- /dev/null
+++ b/src/Core/Models/LdapConfiguration.cs
@@ -0,0 +1,28 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.DirectoryServices;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Bit.Core.Models
+{
+ public class LdapConfiguration
+ {
+ public string Address { get; set; }
+ public string Port { get; set; } = "389";
+ public string Path { get; set; }
+ public string Username { get; set; }
+ public EncryptedData Password { get; set; }
+ [JsonIgnore]
+ public string ServerPath => $"LDAP://{Address}:{Port}/{Path}";
+ public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
+
+ public DirectoryEntry GetDirectoryEntry()
+ {
+ var entry = new DirectoryEntry(ServerPath, Username, Password.DecryptToString(), AuthenticationTypes.None);
+ return entry;
+ }
+ }
+}
diff --git a/src/Core/Models/ServerConfiguration.cs b/src/Core/Models/ServerConfiguration.cs
index fecf81d4..fbdffe92 100644
--- a/src/Core/Models/ServerConfiguration.cs
+++ b/src/Core/Models/ServerConfiguration.cs
@@ -10,19 +10,8 @@ namespace Bit.Core.Models
{
public class ServerConfiguration
{
- public string Address { get; set; }
- public string Port { get; set; } = "389";
- public string Path { get; set; }
- public string Username { get; set; }
- public EncryptedData Password { get; set; }
- [JsonIgnore]
- public string ServerPath => $"LDAP://{Address}:{Port}/{Path}";
public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
-
- public DirectoryEntry GetDirectoryEntry()
- {
- var entry = new DirectoryEntry(ServerPath, Username, Password.DecryptToString(), AuthenticationTypes.None);
- return entry;
- }
+ public LdapConfiguration Ldap { get; set; }
+ public AzureConfiguration Azure { get; set; }
}
}
diff --git a/src/Core/Services/AzureDirectoryService.cs b/src/Core/Services/AzureDirectoryService.cs
index b54c57d8..fd33d79f 100644
--- a/src/Core/Services/AzureDirectoryService.cs
+++ b/src/Core/Services/AzureDirectoryService.cs
@@ -2,14 +2,23 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
+using Microsoft.Graph;
+using System.Net.Http.Headers;
+using System.Diagnostics;
+using System.Linq;
+using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class AzureDirectoryService : IDirectoryService
{
private static AzureDirectoryService _instance;
+ private static GraphServiceClient _graphClient;
- private AzureDirectoryService() { }
+ private AzureDirectoryService()
+ {
+ _graphClient = new GraphServiceClient(new AzureAuthenticationProvider());
+ }
public static IDirectoryService Instance
{
@@ -24,9 +33,112 @@ namespace Bit.Core.Services
}
}
- public Task, List>> GetEntriesAsync(bool force = false)
+ public async Task, List>> GetEntriesAsync(bool force = false)
{
- throw new NotImplementedException();
+ if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
+ {
+ throw new ApplicationException("Not logged in or have an org set.");
+ }
+
+ if(SettingsService.Instance.Server?.Azure == 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 async static Task> GetGroupsAsync(bool force = false)
+ {
+ if(!SettingsService.Instance.Sync.SyncGroups)
+ {
+ throw new ApplicationException("Not configured to sync groups.");
+ }
+
+ if(SettingsService.Instance.Server?.Azure == 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 entries = new List();
+
+ var groups = await _graphClient.Groups.Request().GetAsync();
+ foreach(var group in groups)
+ {
+ entries.Add(new GroupEntry
+ {
+ Id = group.Id,
+ Name = group.DisplayName
+ });
+ }
+
+ return entries;
+ }
+
+ private async static Task> GetUsersAsync(bool force = false)
+ {
+ if(!SettingsService.Instance.Sync.SyncUsers)
+ {
+ throw new ApplicationException("Not configured to sync users.");
+ }
+
+ if(SettingsService.Instance.Server?.Azure == 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 entries = new List();
+
+ var users = await _graphClient.Users.Request().GetAsync();
+ foreach(var user in users)
+ {
+ var entry = new UserEntry
+ {
+ Id = user.Id,
+ Email = user.Mail
+ };
+
+ entries.Add(entry);
+ }
+
+ return entries;
}
}
}
diff --git a/src/Core/Services/LdapDirectoryService.cs b/src/Core/Services/LdapDirectoryService.cs
index 27795cb3..b3eb72c4 100644
--- a/src/Core/Services/LdapDirectoryService.cs
+++ b/src/Core/Services/LdapDirectoryService.cs
@@ -34,7 +34,7 @@ namespace Bit.Core.Services
throw new ApplicationException("Not logged in or have an org set.");
}
- if(SettingsService.Instance.Server == null)
+ if(SettingsService.Instance.Server?.Ldap == null)
{
throw new ApplicationException("No configuration for directory server.");
}
@@ -66,7 +66,7 @@ namespace Bit.Core.Services
throw new ApplicationException("Not configured to sync groups.");
}
- if(SettingsService.Instance.Server == null)
+ if(SettingsService.Instance.Server?.Ldap == null)
{
throw new ApplicationException("No configuration for directory server.");
}
@@ -81,7 +81,7 @@ namespace Bit.Core.Services
throw new ApplicationException("Not authenticated.");
}
- var entry = SettingsService.Instance.Server.GetDirectoryEntry();
+ var entry = SettingsService.Instance.Server.Ldap.GetDirectoryEntry();
var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.GroupFilter) ? null :
SettingsService.Instance.Sync.GroupFilter;
@@ -102,10 +102,10 @@ namespace Bit.Core.Services
{
var group = new GroupEntry
{
- DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault()
+ Id = new Uri(item.Path).Segments?.LastOrDefault()
};
- if(group.DistinguishedName == null)
+ if(group.Id == null)
{
continue;
}
@@ -156,7 +156,7 @@ namespace Bit.Core.Services
throw new ApplicationException("Not configured to sync users.");
}
- if(SettingsService.Instance.Server == null)
+ if(SettingsService.Instance.Server?.Ldap == null)
{
throw new ApplicationException("No configuration for directory server.");
}
@@ -171,7 +171,7 @@ namespace Bit.Core.Services
throw new ApplicationException("Not authenticated.");
}
- var entry = SettingsService.Instance.Server.GetDirectoryEntry();
+ var entry = SettingsService.Instance.Server.Ldap.GetDirectoryEntry();
var filter = string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.UserFilter) ? null :
SettingsService.Instance.Sync.UserFilter;
@@ -192,10 +192,10 @@ namespace Bit.Core.Services
{
var user = new UserEntry
{
- DistinguishedName = new Uri(item.Path).Segments?.LastOrDefault()
+ Id = new Uri(item.Path).Segments?.LastOrDefault()
};
- if(user.DistinguishedName == null)
+ if(user.Id == null)
{
continue;
}
diff --git a/src/Core/Utilities/AzureAuthenticationProvider.cs b/src/Core/Utilities/AzureAuthenticationProvider.cs
new file mode 100644
index 00000000..f1fd44a7
--- /dev/null
+++ b/src/Core/Utilities/AzureAuthenticationProvider.cs
@@ -0,0 +1,33 @@
+using Bit.Core.Services;
+using Microsoft.Graph;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Bit.Core.Utilities
+{
+ public class AzureAuthenticationProvider : IAuthenticationProvider
+ {
+ public async Task AuthenticateRequestAsync(HttpRequestMessage request)
+ {
+ if(SettingsService.Instance.Server?.Azure == null)
+ {
+ throw new ApplicationException("No server configuration.");
+ }
+
+ var authContext = new AuthenticationContext(
+ $"https://login.windows.net/{SettingsService.Instance.Server.Azure.Tenant}/oauth2/token");
+ var creds = new ClientCredential(SettingsService.Instance.Server.Azure.Id,
+ SettingsService.Instance.Server.Azure.Secret.DecryptToString());
+ var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
+ request.Headers.Add("Authorization", $"Bearer {authResult.AccessToken}");
+ }
+
+ // ref: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/511
+ private static void SomeMethodToLinkPlatform()
+ {
+ var creds = new UserPasswordCredential("user", "pass");
+ }
+ }
+}
diff --git a/src/Core/Utilities/Sync.cs b/src/Core/Utilities/Sync.cs
index 2f2eea43..ec3e5fea 100644
--- a/src/Core/Utilities/Sync.cs
+++ b/src/Core/Utilities/Sync.cs
@@ -86,14 +86,14 @@ namespace Bit.Core.Utilities
{
foreach(var group in currentGroups)
{
- var groupsInThisGroup = allGroups.Where(g => group.Members.Contains(g.DistinguishedName)).ToList();
- var usersInThisGroup = allUsers.Where(u => group.Members.Contains(u.DistinguishedName)).ToList();
+ var groupsInThisGroup = allGroups.Where(g => group.Members.Contains(g.Id)).ToList();
+ var usersInThisGroup = allUsers.Where(u => group.Members.Contains(u.Id)).ToList();
foreach(var user in usersInThisGroup)
{
- if(!user.Groups.Contains(group.DistinguishedName))
+ if(!user.Groups.Contains(group.Id))
{
- user.Groups.Add(group.DistinguishedName);
+ user.Groups.Add(group.Id);
}
}
@@ -101,9 +101,9 @@ namespace Bit.Core.Utilities
{
foreach(var user in currentGroupsUsers)
{
- if(!user.Groups.Contains(group.DistinguishedName))
+ if(!user.Groups.Contains(group.Id))
{
- user.Groups.Add(group.DistinguishedName);
+ user.Groups.Add(group.Id);
}
}
diff --git a/src/Core/packages.config b/src/Core/packages.config
index e51e4099..2acd2287 100644
--- a/src/Core/packages.config
+++ b/src/Core/packages.config
@@ -1,5 +1,9 @@
+
+
+
+
\ No newline at end of file