From 29a9fca8e8b9ac1aa4ef8ed68489aaed2b684220 Mon Sep 17 00:00:00 2001 From: Ryan Gribble Date: Sat, 6 Feb 2016 22:02:48 +1000 Subject: [PATCH] Implement Enterprise LDAP Client --- .../Enterprise/EnterpriseLdapClientTests.cs | 76 ++++++++++ .../Octokit.Tests.Integration.csproj | 1 + .../Enterprise/EnterpriseLdapClientTests.cs | 136 ++++++++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 1 + .../Clients/Enterprise/EnterpriseClient.cs | 9 ++ .../Enterprise/EnterpriseLdapClient.cs | 91 ++++++++++++ .../Clients/Enterprise/IEnterpriseClient.cs | 8 ++ .../Enterprise/IEnterpriseLdapClient.cs | 55 +++++++ Octokit/Helpers/ApiUrls.cs | 20 +++ .../Request/Enterprise/NewLdapMapping.cs | 36 +++++ .../Response/Enterprise/LdapSyncResponse.cs | 33 +++++ .../Models/Response/Enterprise/LdapTeam.cs | 27 ++++ .../Models/Response/Enterprise/LdapUser.cs | 27 ++++ Octokit/Octokit-Mono.csproj | 6 + Octokit/Octokit-MonoAndroid.csproj | 6 + Octokit/Octokit-Monotouch.csproj | 6 + Octokit/Octokit-Portable.csproj | 6 + Octokit/Octokit-netcore45.csproj | 6 + Octokit/Octokit.csproj | 6 + 19 files changed, 556 insertions(+) create mode 100644 Octokit.Tests.Integration/Clients/Enterprise/EnterpriseLdapClientTests.cs create mode 100644 Octokit.Tests/Clients/Enterprise/EnterpriseLdapClientTests.cs create mode 100644 Octokit/Clients/Enterprise/EnterpriseLdapClient.cs create mode 100644 Octokit/Clients/Enterprise/IEnterpriseLdapClient.cs create mode 100644 Octokit/Models/Request/Enterprise/NewLdapMapping.cs create mode 100644 Octokit/Models/Response/Enterprise/LdapSyncResponse.cs create mode 100644 Octokit/Models/Response/Enterprise/LdapTeam.cs create mode 100644 Octokit/Models/Response/Enterprise/LdapUser.cs diff --git a/Octokit.Tests.Integration/Clients/Enterprise/EnterpriseLdapClientTests.cs b/Octokit.Tests.Integration/Clients/Enterprise/EnterpriseLdapClientTests.cs new file mode 100644 index 00000000..38923861 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/Enterprise/EnterpriseLdapClientTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using Octokit.Tests.Integration; +using Xunit; + +public class EnterpriseLdapClientTests +{ + readonly IGitHubClient _github; + readonly Team _context; + readonly string _distinguishedNameUser = "uid=test-user,ou=users,dc=company,dc=com"; + readonly string _distinguishedNameTeam = "uid=DG-Test-Team,ou=groups,dc=company,dc=com"; + + public EnterpriseLdapClientTests() + { + _github = EnterpriseHelper.GetAuthenticatedClient(); + + NewTeam newTeam = new NewTeam(Helper.MakeNameWithTimestamp("test-team")) { Description = "Test Team" }; + _context = _github.Organization.Team.Create(EnterpriseHelper.Organization, newTeam).Result; + } + + [GitHubEnterpriseTest] + public async Task CanUpdateUserMapping() + { + var newLDAPMapping = new NewLdapMapping(_distinguishedNameUser); + var ldapUser = await + _github.Enterprise.Ldap.UpdateUserMapping(EnterpriseHelper.UserName, newLDAPMapping); + + Assert.NotNull(ldapUser); + + // Get user and check mapping was updated + var checkUser = await _github.User.Get(EnterpriseHelper.UserName); + Assert.Equal(checkUser.Login, ldapUser.Login); + //Assert.Equal(checkUser.LdapDN, _distinguishedNameUser); + } + + [GitHubEnterpriseTest] + public async Task CanQueueSyncUserMapping() + { + var response = await + _github.Enterprise.Ldap.QueueSyncUserMapping(EnterpriseHelper.UserName); + + // Check response message indicates LDAP sync was queued + Assert.NotNull(response); + Assert.NotNull(response.Status); + Assert.True(response.Status.All(m => m.Contains("was added to the indexing queue"))); + } + + [GitHubEnterpriseTest] + public async Task CanUpdateTeamMapping() + { + var newLDAPMapping = new NewLdapMapping(_distinguishedNameTeam); + var ldapTeam = await + _github.Enterprise.Ldap.UpdateTeamMapping(_context.Id, newLDAPMapping); + + Assert.NotNull(ldapTeam); + + // Get Team and check mapping was updated + var checkTeam = await _github.Organization.Team.Get(_context.Id); + Assert.Equal(checkTeam.Name, ldapTeam.Name); + //Assert.Equal(checkTeam.LDAPDN, _fixtureDistinguishedNameTeam); + } + + [GitHubEnterpriseTest] + public async Task CanQueueSyncTeamMapping() + { + var response = await + _github.Enterprise.Ldap.QueueSyncTeamMapping(_context.Id); + + // Check response message indicates LDAP sync was queued + Assert.NotNull(response); + Assert.NotNull(response.Status); + Assert.True(response.Status.All(m => m.Contains("was added to the indexing queue"))); + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index d8df0040..dc3278f1 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -77,6 +77,7 @@ + diff --git a/Octokit.Tests/Clients/Enterprise/EnterpriseLdapClientTests.cs b/Octokit.Tests/Clients/Enterprise/EnterpriseLdapClientTests.cs new file mode 100644 index 00000000..430124be --- /dev/null +++ b/Octokit.Tests/Clients/Enterprise/EnterpriseLdapClientTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Threading.Tasks; +using NSubstitute; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class EnterpriseLdapClientTests + { + public class TheUpdateUserMappingMethod + { + readonly string _distinguishedNameUser = "uid=test-user,ou=users,dc=company,dc=com"; + + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + string expectedUri = "admin/ldap/users/test-user/mapping"; + client.UpdateUserMapping("test-user", new NewLdapMapping(_distinguishedNameUser)); + + connection.Received().Patch(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); + } + + [Fact] + public void PassesRequestObject() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + client.UpdateUserMapping("test-user", new NewLdapMapping(_distinguishedNameUser)); + + connection.Received().Patch( + Arg.Any(), + Arg.Is(a => + a.LdapDN == _distinguishedNameUser)); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + await Assert.ThrowsAsync(() => client.UpdateUserMapping(null, new NewLdapMapping(_distinguishedNameUser))); + await Assert.ThrowsAsync(() => client.UpdateUserMapping("test-user", null)); + } + } + + public class TheQueueSyncUserMappingMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + string expectedUri = "admin/ldap/users/test-user/sync"; + client.QueueSyncUserMapping("test-user"); + + connection.Received().Post( + Arg.Is(u => u.ToString() == expectedUri), + Arg.Any()); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + await Assert.ThrowsAsync(() => client.QueueSyncUserMapping(null)); + } + } + + public class TheUpdateTeamMappingMethod + { + readonly string _distinguishedNameTeam = "uid=DG-Test-Team,ou=groups,dc=company,dc=com"; + + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + string expectedUri = "admin/ldap/teams/1/mapping"; + client.UpdateTeamMapping(1, new NewLdapMapping(_distinguishedNameTeam)); + + connection.Received().Patch( + Arg.Is(u => u.ToString() == expectedUri), + Arg.Any()); + } + + [Fact] + public void PassesRequestObject() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + client.UpdateTeamMapping(1, new NewLdapMapping(_distinguishedNameTeam)); + + connection.Received().Patch( + Arg.Any(), + Arg.Is(a => + a.LdapDN == _distinguishedNameTeam)); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + await Assert.ThrowsAsync(() => client.UpdateTeamMapping(1, null)); + } + } + + public class TheQueueSyncTeamMappingMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new EnterpriseLdapClient(connection); + + string expectedUri = "admin/ldap/teams/1/sync"; + client.QueueSyncTeamMapping(1); + + connection.Received().Post( + Arg.Is(u => u.ToString() == expectedUri), + Arg.Any()); + } + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index bc124880..10d15310 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -86,6 +86,7 @@ + diff --git a/Octokit/Clients/Enterprise/EnterpriseClient.cs b/Octokit/Clients/Enterprise/EnterpriseClient.cs index 614d9bce..aa5bb9d4 100644 --- a/Octokit/Clients/Enterprise/EnterpriseClient.cs +++ b/Octokit/Clients/Enterprise/EnterpriseClient.cs @@ -15,6 +15,7 @@ public EnterpriseClient(IApiConnection apiConnection) : base(apiConnection) { AdminStats = new EnterpriseAdminStatsClient(apiConnection); + Ldap = new EnterpriseLdapClient(apiConnection); License = new EnterpriseLicenseClient(apiConnection); Organization = new EnterpriseOrganizationClient(apiConnection); SearchIndexing = new EnterpriseSearchIndexingClient(apiConnection); @@ -28,6 +29,14 @@ /// public IEnterpriseAdminStatsClient AdminStats { get; private set; } + /// + /// A client for GitHub's Enterprise LDAP API + /// + /// + /// See the Enterprise LDAP API documentation for more information. + /// + public IEnterpriseLdapClient Ldap { get; private set; } + /// /// A client for GitHub's Enterprise License API /// diff --git a/Octokit/Clients/Enterprise/EnterpriseLdapClient.cs b/Octokit/Clients/Enterprise/EnterpriseLdapClient.cs new file mode 100644 index 00000000..3fecb6ec --- /dev/null +++ b/Octokit/Clients/Enterprise/EnterpriseLdapClient.cs @@ -0,0 +1,91 @@ +using System.Net; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Enterprise LDAP API + /// + /// + /// See the Enterprise LDAP API documentation for more information. + /// + public class EnterpriseLdapClient : ApiClient, IEnterpriseLdapClient + { + public EnterpriseLdapClient(IApiConnection apiConnection) + : base(apiConnection) + { } + + /// + /// Update the LDAP mapping for a user on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-user + /// + /// The username to update LDAP mapping + /// The + /// The object. + public async Task UpdateUserMapping(string userName, NewLdapMapping newLdapMapping) + { + Ensure.ArgumentNotNull(userName, "userName"); + Ensure.ArgumentNotNull(newLdapMapping, "newLdapMapping"); + + var endpoint = ApiUrls.EnterpriseLdapUserMapping(userName); + + return await ApiConnection.Patch(endpoint, newLdapMapping); + } + + /// + /// Queue an LDAP Sync job for a user on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#sync-ldap-mapping-for-a-user + /// + /// The username to sync LDAP mapping + /// The to the queue request. + public async Task QueueSyncUserMapping(string userName) + { + Ensure.ArgumentNotNull(userName, "userName"); + + var endpoint = ApiUrls.EnterpriseLdapUserSync(userName); + + var response = await (Task)ApiConnection.Post(endpoint); + return new LdapSyncResponse(); + } + + /// + /// Update the LDAP mapping for a team on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-team + /// + /// The teamId to update LDAP mapping + /// The + /// The object. + public async Task UpdateTeamMapping(int teamId, NewLdapMapping newLdapMapping) + { + Ensure.ArgumentNotNull(teamId, "teamId"); + Ensure.ArgumentNotNull(newLdapMapping, "newLdapMapping"); + + var endpoint = ApiUrls.EnterpriseLdapTeamMapping(teamId); + + return await ApiConnection.Patch(endpoint, newLdapMapping); + } + + /// + /// Queue an LDAP Sync job for a team on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#sync-ldap-mapping-for-a-team + /// + /// The teamId to update LDAP mapping + /// The to the queue request. + public async Task QueueSyncTeamMapping(int teamId) + { + Ensure.ArgumentNotNull(teamId, "teamId"); + + var endpoint = ApiUrls.EnterpriseLdapTeamSync(teamId); + + return await ApiConnection.Post(endpoint, new object()); + } + } +} diff --git a/Octokit/Clients/Enterprise/IEnterpriseClient.cs b/Octokit/Clients/Enterprise/IEnterpriseClient.cs index 899751c0..28a5db22 100644 --- a/Octokit/Clients/Enterprise/IEnterpriseClient.cs +++ b/Octokit/Clients/Enterprise/IEnterpriseClient.cs @@ -16,6 +16,14 @@ /// IEnterpriseAdminStatsClient AdminStats { get; } + /// + /// A client for GitHub's Enterprise LDAP API + /// + /// + /// See the Enterprise LDAP API documentation for more information. + /// + IEnterpriseLdapClient Ldap { get; } + /// /// A client for GitHub's Enterprise License API /// diff --git a/Octokit/Clients/Enterprise/IEnterpriseLdapClient.cs b/Octokit/Clients/Enterprise/IEnterpriseLdapClient.cs new file mode 100644 index 00000000..a2b28063 --- /dev/null +++ b/Octokit/Clients/Enterprise/IEnterpriseLdapClient.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Enterprise LDAP API + /// + /// + /// See the Enterprise LDAP API documentation for more information. + /// + public interface IEnterpriseLdapClient + { + /// + /// Update the LDAP mapping for a user on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-user + /// + /// The username to update LDAP mapping + /// The + /// The object. + Task UpdateUserMapping(string userName, NewLdapMapping newLdapMapping); + + /// + /// Queue an LDAP Sync job for a user on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#sync-ldap-mapping-for-a-user + /// + /// The username to sync LDAP mapping + /// The to the queue request. + Task QueueSyncUserMapping(string userName); + + /// + /// Update the LDAP mapping for a team on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-team + /// + /// The teamId to update LDAP mapping + /// The + /// The object. + Task UpdateTeamMapping(int teamId, NewLdapMapping newLdapMapping); + + /// + /// Queue an LDAP Sync job for a team on a GitHub Enterprise appliance (must be Site Admin user). + /// + /// + /// https://developer.github.com/v3/enterprise/ldap/#sync-ldap-mapping-for-a-team + /// + /// The teamId to update LDAP mapping + /// The to the queue request. + Task QueueSyncTeamMapping(int teamId); + } +} diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 30436b85..2c7e131c 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1664,6 +1664,26 @@ namespace Octokit return EnterpriseAdminStats("all"); } + public static Uri EnterpriseLdapTeamMapping(int teamId) + { + return "admin/ldap/teams/{0}/mapping".FormatUri(teamId); + } + + public static Uri EnterpriseLdapTeamSync(int teamId) + { + return "admin/ldap/teams/{0}/sync".FormatUri(teamId); + } + + public static Uri EnterpriseLdapUserMapping(string userName) + { + return "admin/ldap/users/{0}/mapping".FormatUri(userName); + } + + public static Uri EnterpriseLdapUserSync(string userName) + { + return "admin/ldap/users/{0}/sync".FormatUri(userName); + } + public static Uri EnterpriseLicense() { return "enterprise/settings/license".FormatUri(); diff --git a/Octokit/Models/Request/Enterprise/NewLdapMapping.cs b/Octokit/Models/Request/Enterprise/NewLdapMapping.cs new file mode 100644 index 00000000..b12e7843 --- /dev/null +++ b/Octokit/Models/Request/Enterprise/NewLdapMapping.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// Describes a new organization to create via the method. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewLdapMapping + { + /// + /// Initializes a new instance of the class. + /// + /// The LDAP Distinguished Name + public NewLdapMapping(string ldapDN) + { + LdapDN = ldapDN; + } + + /// + /// The LDAP Distinguished Name (required) + /// + public string LdapDN { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "LdapDn: {0}", LdapDN); + } + } + } +} diff --git a/Octokit/Models/Response/Enterprise/LdapSyncResponse.cs b/Octokit/Models/Response/Enterprise/LdapSyncResponse.cs new file mode 100644 index 00000000..a95391cd --- /dev/null +++ b/Octokit/Models/Response/Enterprise/LdapSyncResponse.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class LdapSyncResponse + { + public LdapSyncResponse() { } + + public LdapSyncResponse(IReadOnlyList status) + { + Status = status; + } + + public IReadOnlyList Status + { + get; + private set; + } + + internal string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, "Status: {0}", string.Join("\r\n", Status)); + } + } + } +} \ No newline at end of file diff --git a/Octokit/Models/Response/Enterprise/LdapTeam.cs b/Octokit/Models/Response/Enterprise/LdapTeam.cs new file mode 100644 index 00000000..1a1ce545 --- /dev/null +++ b/Octokit/Models/Response/Enterprise/LdapTeam.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class LdapTeam : Team + { + public LdapTeam() { } + + public LdapTeam(Uri url, int id, string name, Permission permission, int membersCount, int reposCount, Organization organization, string ldapDN) + : base(url, id, name, permission, membersCount, reposCount, organization) + { + LdapDN = ldapDN; + } + + public string LdapDN { get; protected set; } + + internal new string DebuggerDisplay + { + get { return base.DebuggerDisplay; } + } + } +} diff --git a/Octokit/Models/Response/Enterprise/LdapUser.cs b/Octokit/Models/Response/Enterprise/LdapUser.cs new file mode 100644 index 00000000..e34634d8 --- /dev/null +++ b/Octokit/Models/Response/Enterprise/LdapUser.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class LdapUser : User + { + public LdapUser() { } + + public LdapUser(string avatarUrl, string bio, string blog, int collaborators, string company, DateTimeOffset createdAt, int diskUsage, string email, int followers, int following, bool? hireable, string htmlUrl, int totalPrivateRepos, int id, string location, string login, string name, int ownedPrivateRepos, Plan plan, int privateGists, int publicGists, int publicRepos, string url, bool siteAdmin, string ldapDN) + : base(avatarUrl, bio, blog, collaborators, company, createdAt, diskUsage, email, followers, following, hireable, htmlUrl, totalPrivateRepos, id, location, login, name, ownedPrivateRepos, plan, privateGists, publicGists, publicRepos, url, siteAdmin) + { + LdapDN = ldapDN; + } + + public string LdapDN { get; protected set; } + + internal new string DebuggerDisplay + { + get { return base.DebuggerDisplay; } + } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index cf4adab8..ce489ea1 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -447,6 +447,12 @@ + + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index ca3641e9..dd4855fd 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -455,6 +455,12 @@ + + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 9b911c87..3f16c64c 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -451,6 +451,12 @@ + + + + + + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 54301735..1a2b7205 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -444,6 +444,12 @@ + + + + + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 47276432..ecc416fe 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -451,6 +451,12 @@ + + + + + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index d293cab7..96a611e3 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -59,10 +59,12 @@ + + @@ -110,6 +112,7 @@ + @@ -150,6 +153,9 @@ + + +