diff --git a/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseAuditLogClient.cs b/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseAuditLogClient.cs new file mode 100644 index 00000000..74e2396c --- /dev/null +++ b/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseAuditLogClient.cs @@ -0,0 +1,111 @@ +using Octokit.Models.Request.Enterprise; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Enterprise Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + public interface IObservableEnterpriseAuditLogClient + { + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAll(string enterprise); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAll(string enterprise, AuditLogRequest request); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllJson(string enterprise); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllJson(string enterprise, AuditLogRequest request); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of issues returned + /// Options for changing the API response + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + [ManualRoute("GET", "/enterprise/audit-log")] + IObservable GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions); + } +} diff --git a/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseClient.cs b/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseClient.cs index d1b8a384..6acb2020 100644 --- a/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseClient.cs +++ b/Octokit.Reactive/Clients/Enterprise/IObservableEnterpriseClient.cs @@ -16,6 +16,14 @@ /// IObservableEnterpriseAdminStatsClient AdminStats { get; } + /// + /// A client for GitHub's Enterprise Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + IObservableEnterpriseAuditLogClient AuditLog { get; } + /// /// A client for GitHub's Enterprise LDAP API /// diff --git a/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseAuditLogClient.cs b/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseAuditLogClient.cs new file mode 100644 index 00000000..317d6d4f --- /dev/null +++ b/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseAuditLogClient.cs @@ -0,0 +1,195 @@ +using Octokit.Models.Request.Enterprise; +using Octokit.Reactive.Internal; +using System; +using System.Collections.Generic; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Enterprise Admin Stats API + /// + /// + /// See the Enterprise Admin Stats API documentation for more information. + /// + public class ObservableEnterpriseAuditLogClient : IObservableEnterpriseAuditLogClient + { + readonly IConnection _connection; + + public ObservableEnterpriseAuditLogClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _connection = client.Connection; + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + public IObservable GetAll(string enterprise) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAll(enterprise, new AuditLogRequest(), new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of issues returned + /// The list. + public IObservable GetAll(string enterprise, AuditLogRequest request) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(request, nameof(request)); + + return GetAll(enterprise, request, new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + public IObservable GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions)); + + return GetAll(enterprise, new AuditLogRequest(), auditLogApiOptions); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + public IObservable GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + ApiOptionsExtended options = new ApiOptionsExtended() + { + PageSize = auditLogApiOptions.PageSize + }; + + + return _connection.GetAndFlattenAllPages(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options)); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + public IObservable GetAllJson(string enterprise) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAllJson(enterprise, new AuditLogRequest(), new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + public IObservable GetAllJson(string enterprise, AuditLogRequest request) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAllJson(enterprise, request, new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + public IObservable GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAllJson(enterprise, new AuditLogRequest(), auditLogApiOptions); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + public IObservable GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + ApiOptionsExtended options = new ApiOptionsExtended() + { + PageSize = auditLogApiOptions.PageSize + }; + + return _connection.GetAndFlattenAllPages(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options)); + } + + private static Func GeneratePreProcessFunction(AuditLogApiOptions auditLogApiOptions, ApiOptionsExtended options) + { + Func preProcessResponseBody = null; + if (string.IsNullOrEmpty(auditLogApiOptions?.StopWhenFound)) + preProcessResponseBody = (r) => + { + if (r is string body) + r = body.Replace("_document_id", "document_id").Replace("@timestamp", "timestamp"); + + return r; + }; + else + preProcessResponseBody = (r) => + { + if (r is string body) + { + if (body.Contains(auditLogApiOptions.StopWhenFound)) + options.IsDone = true; + + r = body.Replace("_document_id", "document_id").Replace("@timestamp", "timestamp"); + } + return r; + }; + + return preProcessResponseBody; + } + } +} diff --git a/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseClient.cs b/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseClient.cs index c542cb8f..0896c377 100644 --- a/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseClient.cs +++ b/Octokit.Reactive/Clients/Enterprise/ObservableEnterpriseClient.cs @@ -13,6 +13,7 @@ Ensure.ArgumentNotNull(client, nameof(client)); AdminStats = new ObservableEnterpriseAdminStatsClient(client); + AuditLog = new ObservableEnterpriseAuditLogClient(client); Ldap = new ObservableEnterpriseLdapClient(client); License = new ObservableEnterpriseLicenseClient(client); ManagementConsole = new ObservableEnterpriseManagementConsoleClient(client); @@ -30,6 +31,14 @@ /// public IObservableEnterpriseAdminStatsClient AdminStats { get; private set; } + /// + /// A client for GitHub's Enterprise Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + public IObservableEnterpriseAuditLogClient AuditLog { get; } + /// /// A client for GitHub's Enterprise LDAP API /// diff --git a/Octokit.Reactive/Helpers/ConnectionExtensions.cs b/Octokit.Reactive/Helpers/ConnectionExtensions.cs index 74902050..d2564e40 100644 --- a/Octokit.Reactive/Helpers/ConnectionExtensions.cs +++ b/Octokit.Reactive/Helpers/ConnectionExtensions.cs @@ -46,6 +46,15 @@ namespace Octokit.Reactive.Internal }); } + public static IObservable GetAndFlattenAllPages(this IConnection connection, Uri url, IDictionary parameters, string accepts, ApiOptions options, Func preprocessResponseBody) + { + return GetPagesWithOptionsAndCallback(url, parameters, options, preprocessResponseBody, (pageUrl, pageParams, o, preprocess) => + { + var passingParameters = Pagination.Setup(parameters, options); + return connection.Get>(pageUrl, passingParameters, accepts).ToObservable(); + }); + } + static IObservable GetPages(Uri uri, IDictionary parameters, Func, IObservable>>> getPageFunc) { @@ -75,5 +84,21 @@ namespace Octokit.Reactive.Internal .Where(resp => resp != null) .SelectMany(resp => resp.Body); } + + static IObservable GetPagesWithOptionsAndCallback(Uri uri, IDictionary parameters, ApiOptions options, Func preprocessResponseBody, Func, ApiOptions, Func, IObservable>>> getPageFunc) + { + return getPageFunc(uri, parameters, options, preprocessResponseBody).Expand(resp => + { + var nextPageUrl = resp.HttpResponse.ApiInfo.GetNextPageUrl(); + + var shouldContinue = Pagination.ShouldContinue(nextPageUrl, options); + + return shouldContinue + ? Observable.Defer(() => getPageFunc(nextPageUrl, null, null, preprocessResponseBody)) + : Observable.Empty>>(); + }) + .Where(resp => resp != null) + .SelectMany(resp => resp.Body); + } } } diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 6182847a..3fe9e7cc 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -35,7 +35,7 @@ - + diff --git a/Octokit.Tests.Conventions/PaginationTests.cs b/Octokit.Tests.Conventions/PaginationTests.cs index 5996aa55..b22cb02e 100644 --- a/Octokit.Tests.Conventions/PaginationTests.cs +++ b/Octokit.Tests.Conventions/PaginationTests.cs @@ -1,4 +1,5 @@ -using System; +using Octokit.Models.Request.Enterprise; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -89,8 +90,10 @@ namespace Octokit.Tests.Conventions var lastParameter = actual.LastOrDefault(); return lastParameter != null - && lastParameter.Name == "options" - && lastParameter.ParameterType == typeof(ApiOptions); + && ( + (lastParameter.Name == "options" && lastParameter.ParameterType == typeof(ApiOptions)) || + (lastParameter.Name == "auditLogApiOptions" && lastParameter.ParameterType == typeof(AuditLogApiOptions)) + ); } public static IEnumerable GetClientInterfaces() diff --git a/Octokit.Tests/Clients/Enterprise/EnterpriseAuditLogClientTests.cs b/Octokit.Tests/Clients/Enterprise/EnterpriseAuditLogClientTests.cs new file mode 100644 index 00000000..f0325b4d --- /dev/null +++ b/Octokit.Tests/Clients/Enterprise/EnterpriseAuditLogClientTests.cs @@ -0,0 +1,18 @@ +using System; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class EnterpriseAuditLogClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws( + () => new EnterpriseAuditLogClient(null)); + } + } + } +} \ No newline at end of file diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs index 15173458..1b3af745 100644 --- a/Octokit.Tests/Clients/ReleasesClientTests.cs +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -560,7 +560,7 @@ namespace Octokit.Tests.Clients private class CancellationTestHttpClient : IHttpClient { - public async Task Send(IRequest request, CancellationToken cancellationToken) + public async Task Send(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); diff --git a/Octokit.Tests/Http/ApiConnectionTests.cs b/Octokit.Tests/Http/ApiConnectionTests.cs index 52d98168..22c43c08 100644 --- a/Octokit.Tests/Http/ApiConnectionTests.cs +++ b/Octokit.Tests/Http/ApiConnectionTests.cs @@ -98,8 +98,8 @@ namespace Octokit.Tests.Http var data = await apiConnection.GetAll(getAllUri); - Assert.Equal(2, data.Count); - connection.Received().Get>(getAllUri, Args.EmptyDictionary, null); + Assert.Equal(0, data.Count); + connection.Received().Get>(getAllUri, Args.EmptyDictionary, null, CancellationToken.None, null); } [Fact] diff --git a/Octokit.Tests/Http/HttpClientAdapterTests.cs b/Octokit.Tests/Http/HttpClientAdapterTests.cs index 46e0bf6b..6a41c30f 100644 --- a/Octokit.Tests/Http/HttpClientAdapterTests.cs +++ b/Octokit.Tests/Http/HttpClientAdapterTests.cs @@ -189,9 +189,9 @@ namespace Octokit.Tests.Http return BuildRequestMessage(request); } - public async Task BuildResponseTester(HttpResponseMessage responseMessage) + public async Task BuildResponseTester(HttpResponseMessage responseMessage, Func preprocessResponseBody = null) { - return await BuildResponse(responseMessage); + return await BuildResponse(responseMessage, preprocessResponseBody); } } } diff --git a/Octokit/Caching/CachingHttpClient.cs b/Octokit/Caching/CachingHttpClient.cs index 059c1aef..e78e3f06 100644 --- a/Octokit/Caching/CachingHttpClient.cs +++ b/Octokit/Caching/CachingHttpClient.cs @@ -21,13 +21,13 @@ namespace Octokit.Caching _responseCache = responseCache; } - public async Task Send(IRequest request, CancellationToken cancellationToken) + public async Task Send(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(request, nameof(request)); if (request.Method != HttpMethod.Get) { - return await _httpClient.Send(request, cancellationToken); + return await _httpClient.Send(request, cancellationToken, preprocessResponseBody); } var cachedResponse = await TryGetCachedResponse(request); diff --git a/Octokit/Clients/Enterprise/EnterpriseAuditLogClient.cs b/Octokit/Clients/Enterprise/EnterpriseAuditLogClient.cs new file mode 100644 index 00000000..641cccd1 --- /dev/null +++ b/Octokit/Clients/Enterprise/EnterpriseAuditLogClient.cs @@ -0,0 +1,198 @@ +using Octokit.Models.Request.Enterprise; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Enterprise Admin Stats API + /// + /// + /// See the Enterprise Admin Stats API documentation for more information. + /// + public class EnterpriseAuditLogClient : ApiClient, IEnterpriseAuditLogClient + { + public EnterpriseAuditLogClient(IApiConnection apiConnection) + : base(apiConnection) + { } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAll(string enterprise) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAll(enterprise, new AuditLogRequest(), new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of issues returned + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAll(string enterprise, AuditLogRequest request) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(request, nameof(request)); + + return GetAll(enterprise, request, new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions)); + + return GetAll(enterprise, new AuditLogRequest(), auditLogApiOptions); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions)); + + ApiOptionsExtended options = new ApiOptionsExtended() + { + PageSize = auditLogApiOptions.PageSize + }; + + return ApiConnection.GetAll(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options)); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAllJson(string enterprise) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAllJson(enterprise, new AuditLogRequest(), new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAllJson(string enterprise, AuditLogRequest request) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + + return GetAllJson(enterprise, request, new AuditLogApiOptions()); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions)); + + return GetAllJson(enterprise, new AuditLogRequest(), auditLogApiOptions); + } + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + [ManualRoute("GET", "/enterprise/audit-log")] + public Task> GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions) + { + Ensure.ArgumentNotNull(enterprise, nameof(enterprise)); + Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions)); + + ApiOptionsExtended options = new ApiOptionsExtended() + { + PageSize = auditLogApiOptions.PageSize + }; + + return ApiConnection.GetAll(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options)); + } + + private static Func GeneratePreProcessFunction(AuditLogApiOptions auditLogApiOptions, ApiOptionsExtended options) + { + Func preProcessResponseBody = null; + if (string.IsNullOrEmpty(auditLogApiOptions?.StopWhenFound)) + preProcessResponseBody = (r) => + { + if (r is string body) + r = body.Replace("_document_id", "document_id").Replace("@timestamp", "timestamp"); + + return r; + }; + else + preProcessResponseBody = (r) => + { + if (r is string body) + { + if (body.Contains(auditLogApiOptions.StopWhenFound)) + options.IsDone = true; + + r = body.Replace("_document_id", "document_id").Replace("@timestamp", "timestamp"); + } + return r; + }; + + return preProcessResponseBody; + } + } +} diff --git a/Octokit/Clients/Enterprise/EnterpriseClient.cs b/Octokit/Clients/Enterprise/EnterpriseClient.cs index c97e938b..04d61793 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); + AuditLog = new EnterpriseAuditLogClient(apiConnection); Ldap = new EnterpriseLdapClient(apiConnection); License = new EnterpriseLicenseClient(apiConnection); ManagementConsole = new EnterpriseManagementConsoleClient(apiConnection); @@ -32,6 +33,14 @@ /// public IEnterpriseAdminStatsClient AdminStats { get; private set; } + /// + /// A client for GitHub's Enterprise Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + public IEnterpriseAuditLogClient AuditLog { get; } + /// /// A client for GitHub's Enterprise LDAP API /// diff --git a/Octokit/Clients/Enterprise/IEnterpriseAuditLogClient.cs b/Octokit/Clients/Enterprise/IEnterpriseAuditLogClient.cs new file mode 100644 index 00000000..37c74794 --- /dev/null +++ b/Octokit/Clients/Enterprise/IEnterpriseAuditLogClient.cs @@ -0,0 +1,111 @@ +using Octokit.Models.Request.Enterprise; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Enterprise Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + public interface IEnterpriseAuditLogClient + { + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAll(string enterprise); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAll(string enterprise, AuditLogRequest request); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllJson(string enterprise); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of events returned + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllJson(string enterprise, AuditLogRequest request); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Options for changing the API response + /// The list. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions); + + /// + /// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). + /// + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise + /// + /// Name of enterprise + /// Used to filter and sort the list of issues returned + /// Options for changing the API response + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions); + } +} diff --git a/Octokit/Clients/Enterprise/IEnterpriseClient.cs b/Octokit/Clients/Enterprise/IEnterpriseClient.cs index 54a0313c..42f219a6 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 Audit Log API + /// + /// + /// See the Enterprise Audit Log API documentation for more information. + /// + IEnterpriseAuditLogClient AuditLog { get; } + /// /// A client for GitHub's Enterprise LDAP API /// diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index eb699627..0c870a42 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -2827,6 +2827,11 @@ namespace Octokit return "repos/{0}/{1}/stats/punch_card".FormatUri(owner, name); } + public static Uri EnterpriseAuditLog(string enterprise) + { + return "enterprises/{0}/audit-log".FormatUri(enterprise); + } + private static Uri EnterpriseAdminStats(string type) { return "enterprise/stats/{0}".FormatUri(type); diff --git a/Octokit/Helpers/Pagination.cs b/Octokit/Helpers/Pagination.cs index 1acf39ea..fbd6bfe5 100644 --- a/Octokit/Helpers/Pagination.cs +++ b/Octokit/Helpers/Pagination.cs @@ -1,4 +1,5 @@ -using System; +using Octokit.Models.Request.Enterprise; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -33,6 +34,9 @@ namespace Octokit return false; } + if (options is ApiOptionsExtended apiOptionsInternal) + return !apiOptionsInternal.IsDone; + if (uri.Query.Contains("page=") && options.PageCount.HasValue) { var allValues = ToQueryStringDictionary(uri); diff --git a/Octokit/Helpers/UriExtensions.cs b/Octokit/Helpers/UriExtensions.cs index eb64239f..b708f707 100644 --- a/Octokit/Helpers/UriExtensions.cs +++ b/Octokit/Helpers/UriExtensions.cs @@ -82,7 +82,7 @@ namespace Octokit } } - Func mapValueFunc = (key, value) => key == "q" ? value : Uri.EscapeDataString(value); + Func mapValueFunc = (key, value) => (key == "q" || key =="before" || key=="after") ? value : Uri.EscapeDataString(value); string query = string.Join("&", p.Select(kvp => kvp.Key + "=" + mapValueFunc(kvp.Key, kvp.Value))); if (uri.IsAbsoluteUri) diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 6893766b..e590a37a 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -91,6 +91,24 @@ namespace Octokit return response.Body; } + /// + /// Gets the API resource at the specified URI. + /// + /// Type of the API resource to get. + /// URI of the API resource to get + /// Parameters to add to the API request + /// Accept header to use for the API request + /// Function to preprocess HTTP response prior to deserialization (can be null) + /// The API resource. + /// Thrown when an API error occurs. + public async Task Get(Uri uri, IDictionary parameters, string accepts, Func preprocessResponseBody) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + var response = await Connection.Get(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false); + return response.Body; + } + /// /// Gets the HTML content of the API resource at the specified URI. /// @@ -212,6 +230,16 @@ namespace Octokit return _pagination.GetAllPages(async () => await GetPage(uri, parameters, accepts, options).ConfigureAwait(false), uri); } + public Task> GetAll(Uri uri, IDictionary parameters, string accepts, ApiOptions options, Func preprocessResponseBody) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(options, nameof(options)); + + parameters = Pagination.Setup(parameters, options); + + return _pagination.GetAllPages(async () => await GetPage(uri, parameters, accepts, options, preprocessResponseBody).ConfigureAwait(false), uri); + } + /// /// Creates a new API resource in the list at the specified URI. /// @@ -619,27 +647,29 @@ namespace Octokit async Task> GetPage( Uri uri, IDictionary parameters, - string accepts) + string accepts, + Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(uri, nameof(uri)); - var response = await Connection.Get>(uri, parameters, accepts).ConfigureAwait(false); + var response = await Connection.Get>(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false); return new ReadOnlyPagedCollection( response, - nextPageUri => Connection.Get>(nextPageUri, parameters, accepts)); + nextPageUri => Connection.Get>(nextPageUri, parameters, accepts, CancellationToken.None, preprocessResponseBody)); } async Task> GetPage( Uri uri, IDictionary parameters, string accepts, - ApiOptions options) + ApiOptions options, + Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(uri, nameof(uri)); var connection = Connection; - var response = await connection.Get>(uri, parameters, accepts).ConfigureAwait(false); + var response = await connection.Get>(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false); return new ReadOnlyPagedCollection( response, nextPageUri => @@ -649,7 +679,7 @@ namespace Octokit options); return shouldContinue - ? connection.Get>(nextPageUri, parameters, accepts) + ? connection.Get>(nextPageUri, parameters, accepts, CancellationToken.None, preprocessResponseBody) : null; }); } diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 3ade4de9..46eeadcc 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -191,6 +191,13 @@ namespace Octokit return SendData(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, cancellationToken); } + public Task> Get(Uri uri, IDictionary parameters, string accepts, CancellationToken cancellationToken, Func preprocessResponseBody) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + return SendData(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, cancellationToken, null, null, preprocessResponseBody); + } + public Task> Get(Uri uri, TimeSpan timeout) { Ensure.ArgumentNotNull(uri, nameof(uri)); @@ -382,7 +389,8 @@ namespace Octokit TimeSpan timeout, CancellationToken cancellationToken, string twoFactorAuthenticationCode = null, - Uri baseAddress = null) + Uri baseAddress = null, + Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.GreaterThanZero(timeout, nameof(timeout)); @@ -395,7 +403,7 @@ namespace Octokit Timeout = timeout }; - return SendDataInternal(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request); + return SendDataInternal(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request, preprocessResponseBody); } Task> SendData( @@ -406,7 +414,8 @@ namespace Octokit string contentType, CancellationToken cancellationToken, string twoFactorAuthenticationCode = null, - Uri baseAddress = null) + Uri baseAddress = null, + Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(uri, nameof(uri)); @@ -417,10 +426,10 @@ namespace Octokit Endpoint = uri }; - return SendDataInternal(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request); + return SendDataInternal(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request, preprocessResponseBody); } - Task> SendDataInternal(object body, string accepts, string contentType, CancellationToken cancellationToken, string twoFactorAuthenticationCode, Request request) + Task> SendDataInternal(object body, string accepts, string contentType, CancellationToken cancellationToken, string twoFactorAuthenticationCode, Request request, Func preprocessResponseBody) { if (!string.IsNullOrEmpty(accepts)) { @@ -439,7 +448,7 @@ namespace Octokit request.ContentType = contentType ?? "application/x-www-form-urlencoded"; } - return Run(request, cancellationToken); + return Run(request, cancellationToken, preprocessResponseBody); } /// @@ -680,19 +689,19 @@ namespace Octokit return new ApiResponse(response, response.Body as byte[]); } - async Task> Run(IRequest request, CancellationToken cancellationToken) + async Task> Run(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) { _jsonPipeline.SerializeRequest(request); - var response = await RunRequest(request, cancellationToken).ConfigureAwait(false); + var response = await RunRequest(request, cancellationToken, preprocessResponseBody).ConfigureAwait(false); return _jsonPipeline.DeserializeResponse(response); } // THIS IS THE METHOD THAT EVERY REQUEST MUST GO THROUGH! - async Task RunRequest(IRequest request, CancellationToken cancellationToken) + async Task RunRequest(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) { request.Headers.Add("User-Agent", UserAgent); await _authenticator.Apply(request).ConfigureAwait(false); - var response = await _httpClient.Send(request, cancellationToken).ConfigureAwait(false); + var response = await _httpClient.Send(request, cancellationToken, preprocessResponseBody).ConfigureAwait(false); if (response != null) { // Use the clone method to avoid keeping hold of the original (just in case it effect the lifetime of the whole response diff --git a/Octokit/Http/HttpClientAdapter.cs b/Octokit/Http/HttpClientAdapter.cs index eca20a74..6e0b8354 100644 --- a/Octokit/Http/HttpClientAdapter.cs +++ b/Octokit/Http/HttpClientAdapter.cs @@ -37,8 +37,9 @@ namespace Octokit.Internal /// /// A that represents the HTTP request /// Used to cancel the request + /// Function to preprocess HTTP response prior to deserialization (can be null) /// A of - public async Task Send(IRequest request, CancellationToken cancellationToken) + public async Task Send(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) { Ensure.ArgumentNotNull(request, nameof(request)); @@ -48,7 +49,7 @@ namespace Octokit.Internal { var responseMessage = await SendAsync(requestMessage, cancellationTokenForRequest).ConfigureAwait(false); - return await BuildResponse(responseMessage).ConfigureAwait(false); + return await BuildResponse(responseMessage, preprocessResponseBody).ConfigureAwait(false); } } @@ -67,7 +68,7 @@ namespace Octokit.Internal return cancellationTokenForRequest; } - protected virtual async Task BuildResponse(HttpResponseMessage responseMessage) + protected virtual async Task BuildResponse(HttpResponseMessage responseMessage, Func preprocessResponseBody) { Ensure.ArgumentNotNull(responseMessage, nameof(responseMessage)); @@ -97,6 +98,9 @@ namespace Octokit.Internal { responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); } + + if (!(preprocessResponseBody is null)) + responseBody = preprocessResponseBody(responseBody); } } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index ce915527..d6e60b39 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -144,6 +144,19 @@ namespace Octokit /// Thrown when an API error occurs. Task> GetAll(Uri uri, IDictionary parameters, string accepts, ApiOptions options); + /// + /// Gets all API resources in the list at the specified URI. + /// + /// Type of the API resource in the list. + /// URI of the API resource to get + /// Parameters to add to the API request + /// Accept header to use for the API request + /// Options for changing the API response + /// Function to preprocess HTTP response prior to deserialization (can be null) + /// of the API resources in the list. + /// Thrown when an API error occurs. + Task> GetAll(Uri uri, IDictionary parameters, string accepts, ApiOptions options, Func preprocessResponseBody); + /// /// Creates a new API resource in the list at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 1f0e052f..2696d8ee 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -65,6 +65,20 @@ namespace Octokit [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] Task> Get(Uri uri, IDictionary parameters, string accepts, CancellationToken cancellationToken); + /// + /// Performs an asynchronous HTTP GET request. + /// Attempts to map the response to an object of type + /// + /// The type to map the response to + /// URI endpoint to send request to + /// Querystring parameters for the request + /// Specifies accepted response media types. + /// A token used to cancel the Get request + /// Function to preprocess HTTP response prior to deserialization (can be null) + /// representing the received HTTP response + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + Task> Get(Uri uri, IDictionary parameters, string accepts, CancellationToken cancellationToken, Func preprocessResponseBody); + /// /// Performs an asynchronous HTTP GET request. /// Attempts to map the response to an object of type diff --git a/Octokit/Http/IHttpClient.cs b/Octokit/Http/IHttpClient.cs index 95e0d505..467104a5 100644 --- a/Octokit/Http/IHttpClient.cs +++ b/Octokit/Http/IHttpClient.cs @@ -17,8 +17,9 @@ namespace Octokit.Internal /// /// A that represents the HTTP request /// Used to cancel the request + /// Function to preprocess HTTP response prior to deserialization (can be null) /// A of - Task Send(IRequest request, CancellationToken cancellationToken); + Task Send(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null); /// diff --git a/Octokit/Http/ReadOnlyPagedCollection.cs b/Octokit/Http/ReadOnlyPagedCollection.cs index c75c76b2..37e40d72 100644 --- a/Octokit/Http/ReadOnlyPagedCollection.cs +++ b/Octokit/Http/ReadOnlyPagedCollection.cs @@ -25,7 +25,7 @@ namespace Octokit.Internal public async Task> GetNextPage() { - var nextPageUrl = _info.GetNextPageUrl(); + var nextPageUrl = _info?.GetNextPageUrl(); if (nextPageUrl == null) return null; var maybeTask = _nextPageFunc(nextPageUrl); diff --git a/Octokit/Models/Request/Enterprise/ApiOptionsExtended.cs b/Octokit/Models/Request/Enterprise/ApiOptionsExtended.cs new file mode 100644 index 00000000..94140895 --- /dev/null +++ b/Octokit/Models/Request/Enterprise/ApiOptionsExtended.cs @@ -0,0 +1,7 @@ +namespace Octokit.Models.Request.Enterprise +{ + internal class ApiOptionsExtended : ApiOptions + { + public bool IsDone { get; set; } = false; + } +} diff --git a/Octokit/Models/Request/Enterprise/AuditLogApiOptions.cs b/Octokit/Models/Request/Enterprise/AuditLogApiOptions.cs new file mode 100644 index 00000000..f454bc04 --- /dev/null +++ b/Octokit/Models/Request/Enterprise/AuditLogApiOptions.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Octokit.Models.Request.Enterprise +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class AuditLogApiOptions + { + public string StopWhenFound { get; set; } = null; + + /// + /// Specify the number of results to return for each page + /// + /// + /// Results returned may be less than this total if you reach the final page of results + /// + public int PageSize { get; set; } = 100; + + internal string DebuggerDisplay + { + get + { + var values = new List + { + "PageSize: " + PageSize + }; + + if (string.IsNullOrEmpty(StopWhenFound)) + values.Add("StopWhenFound: " + StopWhenFound); + + return String.Join(", ", values); + } + } + } +} diff --git a/Octokit/Models/Request/Enterprise/AuditLogRequest.cs b/Octokit/Models/Request/Enterprise/AuditLogRequest.cs new file mode 100644 index 00000000..19078354 --- /dev/null +++ b/Octokit/Models/Request/Enterprise/AuditLogRequest.cs @@ -0,0 +1,88 @@ +using Octokit.Internal; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit.Models.Request.Enterprise +{ + /// + /// Used to filter a audit log request. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class AuditLogRequest : RequestParameters + { + public AuditLogRequest() + { + After = null; + Before = null; + Phrase = null; + Filter = IncludeFilter.Web; + SortDirection = AuditLogSortDirection.Descending; + } + + [Parameter(Key = "after")] + public string After { get; set; } + + [Parameter(Key = "before")] + public string Before { get; set; } + + [Parameter(Key = "include")] + public IncludeFilter Filter { get; set; } + + [Parameter(Key = "phrase")] + public string Phrase { get; set; } + + [Parameter(Key = "order")] + public AuditLogSortDirection SortDirection { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Filter: {0} SortDirection: {1}", Filter, SortDirection); + } + } + } + + /// + /// The range of filters available for event types. + /// + /// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log + public enum IncludeFilter + { + /// + /// Web (non-git) events. (Default) + /// + [Parameter(Value = "web")] + Web, + + /// + /// Git events. + /// + [Parameter(Value = "git")] + Git, + + /// + /// Both web and Git events. + /// + [Parameter(Value = "all")] + All + } + + /// + /// The two possible sort directions. + /// + public enum AuditLogSortDirection + { + /// + /// Sort ascending + /// + [Parameter(Value = "asc")] + Ascending, + + /// + /// Sort descending + /// + [Parameter(Value = "desc")] + Descending + } +} diff --git a/Octokit/Models/Response/Enterprise/AuditLogEvent.cs b/Octokit/Models/Response/Enterprise/AuditLogEvent.cs new file mode 100644 index 00000000..e92e7f5f --- /dev/null +++ b/Octokit/Models/Response/Enterprise/AuditLogEvent.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class AuditLogEvent + { + public AuditLogEvent() { } + + public AuditLogEvent(string action, bool? active, bool? activeWas, string actor, long? actorId, string actorIp, object actorLocation, + string blockedUser, string business, long? businessId, + object config, object configWas, string contentType, long createdAt, + object data, string deployKeyFingerprint, string documentId, + string emoji, object events, object eventsWere, string explanation, + string fingerPrint, + string hashedToken, long? hookId, + bool? limitedAvailability, + string message, + string name, + string oldUser, string opensshPublicKey, string operationType, IReadOnlyList org, IReadOnlyList orgId, + string previousVisibility, bool? publicRepo, long? pullRequestId, string pullRequestTitle, string pullRequestUrl, + bool? readOnly, string repo, long? repoId, string repository, bool? repositoryPublic, + string targetLogin, string team, long? transportProtocol, string transportProtocolName, long timestamp, + string user, string userAgent, long? userId, + string visibility) + { + Action = action; + Active = active; + ActiveWas = activeWas; + Actor = actor; + ActorId = actorId; + ActorIp = actorIp; + ActorLocation = actorLocation; + BlockedUser = blockedUser; + Business = business; + BusinessId = businessId; + Config = config; + ConfigWas = configWas; + ContentType = contentType; + CreatedAt = createdAt; + Data = data; + DeployKeyFingerprint = deployKeyFingerprint; + DocumentId = documentId; + Emoji = emoji; + Events = events; + EventsWere = eventsWere; + Explanation = explanation; + Fingerprint = fingerPrint; + HashedToken = hashedToken; + HookId = hookId; + LimitedAvailability = limitedAvailability; + Message = message; + Name = name; + OldUser = oldUser; + OpensshPublicKey = opensshPublicKey; + OperationType = operationType; + Org = org; + OrgId = orgId; + PreviousVisibility = previousVisibility; + PublicRepo = publicRepo; + PullRequestId = pullRequestId; + PullRequestTitle = pullRequestTitle; + PullRequestUrl = pullRequestUrl; + ReadOnly = readOnly; + Repo = repo; + RepoId = repoId; + Repository = repository; + RepositoryPublic = repositoryPublic; + TargetLogin = targetLogin; + Team = team; + Timestamp = timestamp; + TransportProtocol = transportProtocol; + TransportProtocolName = transportProtocolName; + User = user; + UserAgent = userAgent; + UserId = userId; + Visibility = visibility; + } + + public string Action { get; private set; } + public bool? Active { get; private set; } + public bool? ActiveWas { get; private set; } + public string Actor { get; private set; } + public long? ActorId { get; private set; } + public string ActorIp { get; private set; } + public object ActorLocation { get; private set; } + public string BlockedUser { get; private set; } + public string Business { get; private set; } + public long? BusinessId { get; private set; } + public object Config { get; private set; } + public object ConfigWas { get; private set; } + public string ContentType { get; private set; } + public long CreatedAt { get; private set; } + public object Data { get; private set; } + public string DeployKeyFingerprint { get; private set; } + public string DocumentId { get; private set; } + public string Emoji { get; private set; } + public object Events { get; private set; } + public object EventsWere { get; private set; } + public string Explanation { get; private set; } + public string Fingerprint { get; private set; } + public string HashedToken { get; private set; } + public long? HookId { get; private set; } + public bool? LimitedAvailability { get; private set; } + public string Message { get; private set; } + public string Name { get; private set; } + public string OldUser { get; private set; } + public string OpensshPublicKey { get; private set; } + public string OperationType { get; private set; } + public IReadOnlyList Org { get; private set; } + public IReadOnlyList OrgId { get; private set; } + public string PreviousVisibility { get; private set; } + public bool? PublicRepo { get; private set; } + public long? PullRequestId { get; private set; } + public string PullRequestTitle { get; private set; } + public string PullRequestUrl { get; private set; } + public bool? ReadOnly { get; private set; } + public string Repo { get; private set; } + public long? RepoId { get; private set; } + public string Repository { get; private set; } + public bool? RepositoryPublic { get; private set; } + public string TargetLogin { get; private set; } + public string Team { get; private set; } + public long Timestamp { get; private set; } + public long? TransportProtocol { get; private set; } + public string TransportProtocolName { get; private set; } + public string User { get; private set; } + public string UserAgent { get; private set; } + public long? UserId { get; private set; } + public string Visibility { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Action: {0}, Actor: {1}, DocumentID: {2}", Action, Actor, DocumentId); + } + } + } +} + + + diff --git a/Octokit/SimpleJson.cs b/Octokit/SimpleJson.cs index eab7a32a..7b9a8554 100644 --- a/Octokit/SimpleJson.cs +++ b/Octokit/SimpleJson.cs @@ -1422,7 +1422,9 @@ namespace Octokit bool valueIsDouble = value is double; if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) return value; - if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) + if (valueIsLong && type == typeof(IReadOnlyList)) + obj = new long[] { (long)value }; + else if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) { if (valueIsLong && (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))) { @@ -1436,6 +1438,8 @@ namespace Octokit ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) : value; } + else if (type == typeof(object) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(object))) + obj = value; else { IDictionary objects = value as IDictionary;