[FEAT] Adds support for enterprise audit logs

This commit is contained in:
gitasaurus
2023-05-09 11:28:58 -04:00
committed by GitHub
parent ecf5af499e
commit 3c82ff359c
31 changed files with 1090 additions and 35 deletions

View File

@@ -0,0 +1,111 @@
using Octokit.Models.Request.Enterprise;
using System;
using System.Diagnostics.CodeAnalysis;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
///</remarks>
public interface IObservableEnterpriseAuditLogClient
{
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<AuditLogEvent> GetAll(string enterprise);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogRequest request);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<object> GetAllJson(string enterprise);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<object> GetAllJson(string enterprise, AuditLogRequest request);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
IObservable<object> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
[ManualRoute("GET", "/enterprise/audit-log")]
IObservable<object> GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions);
}
}

View File

@@ -16,6 +16,14 @@
/// </remarks> /// </remarks>
IObservableEnterpriseAdminStatsClient AdminStats { get; } IObservableEnterpriseAdminStatsClient AdminStats { get; }
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
/// </remarks>
IObservableEnterpriseAuditLogClient AuditLog { get; }
/// <summary> /// <summary>
/// A client for GitHub's Enterprise LDAP API /// A client for GitHub's Enterprise LDAP API
/// </summary> /// </summary>

View File

@@ -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
{
/// <summary>
/// A client for GitHub's Enterprise Admin Stats API
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/enterprise/admin_stats/">Enterprise Admin Stats API documentation</a> for more information.
///</remarks>
public class ObservableEnterpriseAuditLogClient : IObservableEnterpriseAuditLogClient
{
readonly IConnection _connection;
public ObservableEnterpriseAuditLogClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, "client");
_connection = client.Connection;
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<AuditLogEvent> GetAll(string enterprise)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAll(enterprise, new AuditLogRequest(), new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogRequest request)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
Ensure.ArgumentNotNull(request, nameof(request));
return GetAll(enterprise, request, new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions));
return GetAll(enterprise, new AuditLogRequest(), auditLogApiOptions);
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<AuditLogEvent> GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
ApiOptionsExtended options = new ApiOptionsExtended()
{
PageSize = auditLogApiOptions.PageSize
};
return _connection.GetAndFlattenAllPages<AuditLogEvent>(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options));
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<object> GetAllJson(string enterprise)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAllJson(enterprise, new AuditLogRequest(), new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<object> GetAllJson(string enterprise, AuditLogRequest request)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAllJson(enterprise, request, new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<object> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAllJson(enterprise, new AuditLogRequest(), auditLogApiOptions);
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
public IObservable<object> GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
ApiOptionsExtended options = new ApiOptionsExtended()
{
PageSize = auditLogApiOptions.PageSize
};
return _connection.GetAndFlattenAllPages<AuditLogEvent>(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options));
}
private static Func<object, object> GeneratePreProcessFunction(AuditLogApiOptions auditLogApiOptions, ApiOptionsExtended options)
{
Func<object, object> 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;
}
}
}

View File

@@ -13,6 +13,7 @@
Ensure.ArgumentNotNull(client, nameof(client)); Ensure.ArgumentNotNull(client, nameof(client));
AdminStats = new ObservableEnterpriseAdminStatsClient(client); AdminStats = new ObservableEnterpriseAdminStatsClient(client);
AuditLog = new ObservableEnterpriseAuditLogClient(client);
Ldap = new ObservableEnterpriseLdapClient(client); Ldap = new ObservableEnterpriseLdapClient(client);
License = new ObservableEnterpriseLicenseClient(client); License = new ObservableEnterpriseLicenseClient(client);
ManagementConsole = new ObservableEnterpriseManagementConsoleClient(client); ManagementConsole = new ObservableEnterpriseManagementConsoleClient(client);
@@ -30,6 +31,14 @@
/// </remarks> /// </remarks>
public IObservableEnterpriseAdminStatsClient AdminStats { get; private set; } public IObservableEnterpriseAdminStatsClient AdminStats { get; private set; }
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
/// </remarks>
public IObservableEnterpriseAuditLogClient AuditLog { get; }
/// <summary> /// <summary>
/// A client for GitHub's Enterprise LDAP API /// A client for GitHub's Enterprise LDAP API
/// </summary> /// </summary>

View File

@@ -46,6 +46,15 @@ namespace Octokit.Reactive.Internal
}); });
} }
public static IObservable<T> GetAndFlattenAllPages<T>(this IConnection connection, Uri url, IDictionary<string, string> parameters, string accepts, ApiOptions options, Func<object, object> preprocessResponseBody)
{
return GetPagesWithOptionsAndCallback(url, parameters, options, preprocessResponseBody, (pageUrl, pageParams, o, preprocess) =>
{
var passingParameters = Pagination.Setup(parameters, options);
return connection.Get<List<T>>(pageUrl, passingParameters, accepts).ToObservable();
});
}
static IObservable<T> GetPages<T>(Uri uri, IDictionary<string, string> parameters, static IObservable<T> GetPages<T>(Uri uri, IDictionary<string, string> parameters,
Func<Uri, IDictionary<string, string>, IObservable<IApiResponse<List<T>>>> getPageFunc) Func<Uri, IDictionary<string, string>, IObservable<IApiResponse<List<T>>>> getPageFunc)
{ {
@@ -75,5 +84,21 @@ namespace Octokit.Reactive.Internal
.Where(resp => resp != null) .Where(resp => resp != null)
.SelectMany(resp => resp.Body); .SelectMany(resp => resp.Body);
} }
static IObservable<T> GetPagesWithOptionsAndCallback<T>(Uri uri, IDictionary<string, string> parameters, ApiOptions options, Func<object, object> preprocessResponseBody, Func<Uri, IDictionary<string, string>, ApiOptions, Func<object, object>, IObservable<IApiResponse<List<T>>>> 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<IApiResponse<List<T>>>();
})
.Where(resp => resp != null)
.SelectMany(resp => resp.Body);
}
} }
} }

View File

@@ -35,7 +35,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Octokit\Helpers\Ensure.cs;..\Octokit\Helpers\Pagination.cs" /> <Compile Include="..\Octokit\Helpers\Ensure.cs;..\Octokit\Helpers\Pagination.cs;..\Octokit\Models\Request\Enterprise\ApiOptionsExtended.cs" />
<None Include="app.config" /> <None Include="app.config" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
using System; using Octokit.Models.Request.Enterprise;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -89,8 +90,10 @@ namespace Octokit.Tests.Conventions
var lastParameter = actual.LastOrDefault(); var lastParameter = actual.LastOrDefault();
return lastParameter != null 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<object[]> GetClientInterfaces() public static IEnumerable<object[]> GetClientInterfaces()

View File

@@ -0,0 +1,18 @@
using System;
using Xunit;
namespace Octokit.Tests.Clients
{
public class EnterpriseAuditLogClientTests
{
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(
() => new EnterpriseAuditLogClient(null));
}
}
}
}

View File

@@ -560,7 +560,7 @@ namespace Octokit.Tests.Clients
private class CancellationTestHttpClient : IHttpClient private class CancellationTestHttpClient : IHttpClient
{ {
public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken) public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
{ {
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);

View File

@@ -98,8 +98,8 @@ namespace Octokit.Tests.Http
var data = await apiConnection.GetAll<object>(getAllUri); var data = await apiConnection.GetAll<object>(getAllUri);
Assert.Equal(2, data.Count); Assert.Equal(0, data.Count);
connection.Received().Get<List<object>>(getAllUri, Args.EmptyDictionary, null); connection.Received().Get<List<object>>(getAllUri, Args.EmptyDictionary, null, CancellationToken.None, null);
} }
[Fact] [Fact]

View File

@@ -189,9 +189,9 @@ namespace Octokit.Tests.Http
return BuildRequestMessage(request); return BuildRequestMessage(request);
} }
public async Task<IResponse> BuildResponseTester(HttpResponseMessage responseMessage) public async Task<IResponse> BuildResponseTester(HttpResponseMessage responseMessage, Func<object, object> preprocessResponseBody = null)
{ {
return await BuildResponse(responseMessage); return await BuildResponse(responseMessage, preprocessResponseBody);
} }
} }
} }

View File

@@ -21,13 +21,13 @@ namespace Octokit.Caching
_responseCache = responseCache; _responseCache = responseCache;
} }
public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken) public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(request, nameof(request));
if (request.Method != HttpMethod.Get) if (request.Method != HttpMethod.Get)
{ {
return await _httpClient.Send(request, cancellationToken); return await _httpClient.Send(request, cancellationToken, preprocessResponseBody);
} }
var cachedResponse = await TryGetCachedResponse(request); var cachedResponse = await TryGetCachedResponse(request);

View File

@@ -0,0 +1,198 @@
using Octokit.Models.Request.Enterprise;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Enterprise Admin Stats API
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/enterprise/admin_stats/">Enterprise Admin Stats API documentation</a> for more information.
///</remarks>
public class EnterpriseAuditLogClient : ApiClient, IEnterpriseAuditLogClient
{
public EnterpriseAuditLogClient(IApiConnection apiConnection)
: base(apiConnection)
{ }
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAll(enterprise, new AuditLogRequest(), new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise, AuditLogRequest request)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
Ensure.ArgumentNotNull(request, nameof(request));
return GetAll(enterprise, request, new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions));
return GetAll(enterprise, new AuditLogRequest(), auditLogApiOptions);
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<AuditLogEvent>> 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<AuditLogEvent>(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options));
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<object>> GetAllJson(string enterprise)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAllJson(enterprise, new AuditLogRequest(), new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user). Note: Defaults to 100 entries per page (max count).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<object>> GetAllJson(string enterprise, AuditLogRequest request)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
return GetAllJson(enterprise, request, new AuditLogApiOptions());
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<object>> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions)
{
Ensure.ArgumentNotNull(enterprise, nameof(enterprise));
Ensure.ArgumentNotNull(auditLogApiOptions, nameof(auditLogApiOptions));
return GetAllJson(enterprise, new AuditLogRequest(), auditLogApiOptions);
}
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[ManualRoute("GET", "/enterprise/audit-log")]
public Task<IReadOnlyList<object>> 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<object>(ApiUrls.EnterpriseAuditLog(enterprise), request.ToParametersDictionary(), null, options, GeneratePreProcessFunction(auditLogApiOptions, options));
}
private static Func<object, object> GeneratePreProcessFunction(AuditLogApiOptions auditLogApiOptions, ApiOptionsExtended options)
{
Func<object, object> 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;
}
}
}

View File

@@ -15,6 +15,7 @@
public EnterpriseClient(IApiConnection apiConnection) : base(apiConnection) public EnterpriseClient(IApiConnection apiConnection) : base(apiConnection)
{ {
AdminStats = new EnterpriseAdminStatsClient(apiConnection); AdminStats = new EnterpriseAdminStatsClient(apiConnection);
AuditLog = new EnterpriseAuditLogClient(apiConnection);
Ldap = new EnterpriseLdapClient(apiConnection); Ldap = new EnterpriseLdapClient(apiConnection);
License = new EnterpriseLicenseClient(apiConnection); License = new EnterpriseLicenseClient(apiConnection);
ManagementConsole = new EnterpriseManagementConsoleClient(apiConnection); ManagementConsole = new EnterpriseManagementConsoleClient(apiConnection);
@@ -32,6 +33,14 @@
/// </remarks> /// </remarks>
public IEnterpriseAdminStatsClient AdminStats { get; private set; } public IEnterpriseAdminStatsClient AdminStats { get; private set; }
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
/// </remarks>
public IEnterpriseAuditLogClient AuditLog { get; }
/// <summary> /// <summary>
/// A client for GitHub's Enterprise LDAP API /// A client for GitHub's Enterprise LDAP API
/// </summary> /// </summary>

View File

@@ -0,0 +1,111 @@
using Octokit.Models.Request.Enterprise;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
///</remarks>
public interface IEnterpriseAuditLogClient
{
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise, AuditLogRequest request);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<AuditLogEvent>> GetAll(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<object>> GetAllJson(string enterprise);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of events returned</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<object>> GetAllJson(string enterprise, AuditLogRequest request);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
/// <returns>The <see cref="AuditLogEvent"/> list.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<object>> GetAllJson(string enterprise, AuditLogApiOptions auditLogApiOptions);
/// <summary>
/// Gets GitHub Enterprise Audit Log Entries as raw Json (must be Site Admin user).
/// </summary>
/// <remarks>
/// https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log/#get-the-audit-log-for-an-enterprise
/// </remarks>
/// <param name="enterprise">Name of enterprise</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <param name="auditLogApiOptions">Options for changing the API response</param>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<object>> GetAllJson(string enterprise, AuditLogRequest request, AuditLogApiOptions auditLogApiOptions);
}
}

View File

@@ -16,6 +16,14 @@
/// </remarks> /// </remarks>
IEnterpriseAdminStatsClient AdminStats { get; } IEnterpriseAdminStatsClient AdminStats { get; }
/// <summary>
/// A client for GitHub's Enterprise Audit Log API
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log">Enterprise Audit Log API documentation</a> for more information.
/// </remarks>
IEnterpriseAuditLogClient AuditLog { get; }
/// <summary> /// <summary>
/// A client for GitHub's Enterprise LDAP API /// A client for GitHub's Enterprise LDAP API
/// </summary> /// </summary>

View File

@@ -2827,6 +2827,11 @@ namespace Octokit
return "repos/{0}/{1}/stats/punch_card".FormatUri(owner, name); 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) private static Uri EnterpriseAdminStats(string type)
{ {
return "enterprise/stats/{0}".FormatUri(type); return "enterprise/stats/{0}".FormatUri(type);

View File

@@ -1,4 +1,5 @@
using System; using Octokit.Models.Request.Enterprise;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -33,6 +34,9 @@ namespace Octokit
return false; return false;
} }
if (options is ApiOptionsExtended apiOptionsInternal)
return !apiOptionsInternal.IsDone;
if (uri.Query.Contains("page=") && options.PageCount.HasValue) if (uri.Query.Contains("page=") && options.PageCount.HasValue)
{ {
var allValues = ToQueryStringDictionary(uri); var allValues = ToQueryStringDictionary(uri);

View File

@@ -82,7 +82,7 @@ namespace Octokit
} }
} }
Func<string, string, string> mapValueFunc = (key, value) => key == "q" ? value : Uri.EscapeDataString(value); Func<string, string, string> 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))); string query = string.Join("&", p.Select(kvp => kvp.Key + "=" + mapValueFunc(kvp.Key, kvp.Value)));
if (uri.IsAbsoluteUri) if (uri.IsAbsoluteUri)

View File

@@ -91,6 +91,24 @@ namespace Octokit
return response.Body; return response.Body;
} }
/// <summary>
/// Gets the API resource at the specified URI.
/// </summary>
/// <typeparam name="T">Type of the API resource to get.</typeparam>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="parameters">Parameters to add to the API request</param>
/// <param name="accepts">Accept header to use for the API request</param>
/// <param name="preprocessResponseBody">Function to preprocess HTTP response prior to deserialization (can be null)</param>
/// <returns>The API resource.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
public async Task<T> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, Func<object, object> preprocessResponseBody)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await Connection.Get<T>(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false);
return response.Body;
}
/// <summary> /// <summary>
/// Gets the HTML content of the API resource at the specified URI. /// Gets the HTML content of the API resource at the specified URI.
/// </summary> /// </summary>
@@ -212,6 +230,16 @@ namespace Octokit
return _pagination.GetAllPages(async () => await GetPage<T>(uri, parameters, accepts, options).ConfigureAwait(false), uri); return _pagination.GetAllPages(async () => await GetPage<T>(uri, parameters, accepts, options).ConfigureAwait(false), uri);
} }
public Task<IReadOnlyList<T>> GetAll<T>(Uri uri, IDictionary<string, string> parameters, string accepts, ApiOptions options, Func<object, object> preprocessResponseBody)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
Ensure.ArgumentNotNull(options, nameof(options));
parameters = Pagination.Setup(parameters, options);
return _pagination.GetAllPages(async () => await GetPage<T>(uri, parameters, accepts, options, preprocessResponseBody).ConfigureAwait(false), uri);
}
/// <summary> /// <summary>
/// Creates a new API resource in the list at the specified URI. /// Creates a new API resource in the list at the specified URI.
/// </summary> /// </summary>
@@ -619,27 +647,29 @@ namespace Octokit
async Task<IReadOnlyPagedCollection<T>> GetPage<T>( async Task<IReadOnlyPagedCollection<T>> GetPage<T>(
Uri uri, Uri uri,
IDictionary<string, string> parameters, IDictionary<string, string> parameters,
string accepts) string accepts,
Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await Connection.Get<List<T>>(uri, parameters, accepts).ConfigureAwait(false); var response = await Connection.Get<List<T>>(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false);
return new ReadOnlyPagedCollection<T>( return new ReadOnlyPagedCollection<T>(
response, response,
nextPageUri => Connection.Get<List<T>>(nextPageUri, parameters, accepts)); nextPageUri => Connection.Get<List<T>>(nextPageUri, parameters, accepts, CancellationToken.None, preprocessResponseBody));
} }
async Task<IReadOnlyPagedCollection<TU>> GetPage<TU>( async Task<IReadOnlyPagedCollection<TU>> GetPage<TU>(
Uri uri, Uri uri,
IDictionary<string, string> parameters, IDictionary<string, string> parameters,
string accepts, string accepts,
ApiOptions options) ApiOptions options,
Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));
var connection = Connection; var connection = Connection;
var response = await connection.Get<List<TU>>(uri, parameters, accepts).ConfigureAwait(false); var response = await connection.Get<List<TU>>(uri, parameters, accepts, CancellationToken.None, preprocessResponseBody).ConfigureAwait(false);
return new ReadOnlyPagedCollection<TU>( return new ReadOnlyPagedCollection<TU>(
response, response,
nextPageUri => nextPageUri =>
@@ -649,7 +679,7 @@ namespace Octokit
options); options);
return shouldContinue return shouldContinue
? connection.Get<List<TU>>(nextPageUri, parameters, accepts) ? connection.Get<List<TU>>(nextPageUri, parameters, accepts, CancellationToken.None, preprocessResponseBody)
: null; : null;
}); });
} }

View File

@@ -191,6 +191,13 @@ namespace Octokit
return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, cancellationToken); return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, cancellationToken);
} }
public Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, cancellationToken, null, null, preprocessResponseBody);
}
public Task<IApiResponse<T>> Get<T>(Uri uri, TimeSpan timeout) public Task<IApiResponse<T>> Get<T>(Uri uri, TimeSpan timeout)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));
@@ -382,7 +389,8 @@ namespace Octokit
TimeSpan timeout, TimeSpan timeout,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string twoFactorAuthenticationCode = null, string twoFactorAuthenticationCode = null,
Uri baseAddress = null) Uri baseAddress = null,
Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));
Ensure.GreaterThanZero(timeout, nameof(timeout)); Ensure.GreaterThanZero(timeout, nameof(timeout));
@@ -395,7 +403,7 @@ namespace Octokit
Timeout = timeout Timeout = timeout
}; };
return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request); return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request, preprocessResponseBody);
} }
Task<IApiResponse<T>> SendData<T>( Task<IApiResponse<T>> SendData<T>(
@@ -406,7 +414,8 @@ namespace Octokit
string contentType, string contentType,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string twoFactorAuthenticationCode = null, string twoFactorAuthenticationCode = null,
Uri baseAddress = null) Uri baseAddress = null,
Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(uri, nameof(uri));
@@ -417,10 +426,10 @@ namespace Octokit
Endpoint = uri Endpoint = uri
}; };
return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request); return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request, preprocessResponseBody);
} }
Task<IApiResponse<T>> SendDataInternal<T>(object body, string accepts, string contentType, CancellationToken cancellationToken, string twoFactorAuthenticationCode, Request request) Task<IApiResponse<T>> SendDataInternal<T>(object body, string accepts, string contentType, CancellationToken cancellationToken, string twoFactorAuthenticationCode, Request request, Func<object, object> preprocessResponseBody)
{ {
if (!string.IsNullOrEmpty(accepts)) if (!string.IsNullOrEmpty(accepts))
{ {
@@ -439,7 +448,7 @@ namespace Octokit
request.ContentType = contentType ?? "application/x-www-form-urlencoded"; request.ContentType = contentType ?? "application/x-www-form-urlencoded";
} }
return Run<T>(request, cancellationToken); return Run<T>(request, cancellationToken, preprocessResponseBody);
} }
/// <summary> /// <summary>
@@ -680,19 +689,19 @@ namespace Octokit
return new ApiResponse<byte[]>(response, response.Body as byte[]); return new ApiResponse<byte[]>(response, response.Body as byte[]);
} }
async Task<IApiResponse<T>> Run<T>(IRequest request, CancellationToken cancellationToken) async Task<IApiResponse<T>> Run<T>(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
{ {
_jsonPipeline.SerializeRequest(request); _jsonPipeline.SerializeRequest(request);
var response = await RunRequest(request, cancellationToken).ConfigureAwait(false); var response = await RunRequest(request, cancellationToken, preprocessResponseBody).ConfigureAwait(false);
return _jsonPipeline.DeserializeResponse<T>(response); return _jsonPipeline.DeserializeResponse<T>(response);
} }
// THIS IS THE METHOD THAT EVERY REQUEST MUST GO THROUGH! // THIS IS THE METHOD THAT EVERY REQUEST MUST GO THROUGH!
async Task<IResponse> RunRequest(IRequest request, CancellationToken cancellationToken) async Task<IResponse> RunRequest(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
{ {
request.Headers.Add("User-Agent", UserAgent); request.Headers.Add("User-Agent", UserAgent);
await _authenticator.Apply(request).ConfigureAwait(false); 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) 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 // Use the clone method to avoid keeping hold of the original (just in case it effect the lifetime of the whole response

View File

@@ -37,8 +37,9 @@ namespace Octokit.Internal
/// </summary> /// </summary>
/// <param name="request">A <see cref="IRequest"/> that represents the HTTP request</param> /// <param name="request">A <see cref="IRequest"/> that represents the HTTP request</param>
/// <param name="cancellationToken">Used to cancel the request</param> /// <param name="cancellationToken">Used to cancel the request</param>
/// <param name="preprocessResponseBody">Function to preprocess HTTP response prior to deserialization (can be null)</param>
/// <returns>A <see cref="Task" /> of <see cref="IResponse"/></returns> /// <returns>A <see cref="Task" /> of <see cref="IResponse"/></returns>
public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken) public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
{ {
Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(request, nameof(request));
@@ -48,7 +49,7 @@ namespace Octokit.Internal
{ {
var responseMessage = await SendAsync(requestMessage, cancellationTokenForRequest).ConfigureAwait(false); 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; return cancellationTokenForRequest;
} }
protected virtual async Task<IResponse> BuildResponse(HttpResponseMessage responseMessage) protected virtual async Task<IResponse> BuildResponse(HttpResponseMessage responseMessage, Func<object, object> preprocessResponseBody)
{ {
Ensure.ArgumentNotNull(responseMessage, nameof(responseMessage)); Ensure.ArgumentNotNull(responseMessage, nameof(responseMessage));
@@ -97,6 +98,9 @@ namespace Octokit.Internal
{ {
responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
} }
if (!(preprocessResponseBody is null))
responseBody = preprocessResponseBody(responseBody);
} }
} }

View File

@@ -144,6 +144,19 @@ namespace Octokit
/// <exception cref="ApiException">Thrown when an API error occurs.</exception> /// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<IReadOnlyList<T>> GetAll<T>(Uri uri, IDictionary<string, string> parameters, string accepts, ApiOptions options); Task<IReadOnlyList<T>> GetAll<T>(Uri uri, IDictionary<string, string> parameters, string accepts, ApiOptions options);
/// <summary>
/// Gets all API resources in the list at the specified URI.
/// </summary>
/// <typeparam name="T">Type of the API resource in the list.</typeparam>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="parameters">Parameters to add to the API request</param>
/// <param name="accepts">Accept header to use for the API request</param>
/// <param name="options">Options for changing the API response</param>
/// <param name="preprocessResponseBody">Function to preprocess HTTP response prior to deserialization (can be null)</param>
/// <returns><see cref="IReadOnlyList{T}"/> of the API resources in the list.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<IReadOnlyList<T>> GetAll<T>(Uri uri, IDictionary<string, string> parameters, string accepts, ApiOptions options, Func<object, object> preprocessResponseBody);
/// <summary> /// <summary>
/// Creates a new API resource in the list at the specified URI. /// Creates a new API resource in the list at the specified URI.
/// </summary> /// </summary>

View File

@@ -65,6 +65,20 @@ namespace Octokit
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")]
Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken); Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken);
/// <summary>
/// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The type to map the response to</typeparam>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <param name="accepts">Specifies accepted response media types.</param>
/// <param name="cancellationToken">A token used to cancel the Get request</param>
/// <param name="preprocessResponseBody">Function to preprocess HTTP response prior to deserialization (can be null)</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")]
Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody);
/// <summary> /// <summary>
/// Performs an asynchronous HTTP GET request. /// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/> /// Attempts to map the response to an object of type <typeparamref name="T"/>

View File

@@ -17,8 +17,9 @@ namespace Octokit.Internal
/// </summary> /// </summary>
/// <param name="request">A <see cref="IRequest"/> that represents the HTTP request</param> /// <param name="request">A <see cref="IRequest"/> that represents the HTTP request</param>
/// <param name="cancellationToken">Used to cancel the request</param> /// <param name="cancellationToken">Used to cancel the request</param>
/// <param name="preprocessResponseBody">Function to preprocess HTTP response prior to deserialization (can be null)</param>
/// <returns>A <see cref="Task" /> of <see cref="IResponse"/></returns> /// <returns>A <see cref="Task" /> of <see cref="IResponse"/></returns>
Task<IResponse> Send(IRequest request, CancellationToken cancellationToken); Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null);
/// <summary> /// <summary>

View File

@@ -25,7 +25,7 @@ namespace Octokit.Internal
public async Task<IReadOnlyPagedCollection<T>> GetNextPage() public async Task<IReadOnlyPagedCollection<T>> GetNextPage()
{ {
var nextPageUrl = _info.GetNextPageUrl(); var nextPageUrl = _info?.GetNextPageUrl();
if (nextPageUrl == null) return null; if (nextPageUrl == null) return null;
var maybeTask = _nextPageFunc(nextPageUrl); var maybeTask = _nextPageFunc(nextPageUrl);

View File

@@ -0,0 +1,7 @@
namespace Octokit.Models.Request.Enterprise
{
internal class ApiOptionsExtended : ApiOptions
{
public bool IsDone { get; set; } = false;
}
}

View File

@@ -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;
/// <summary>
/// Specify the number of results to return for each page
/// </summary>
/// <remarks>
/// Results returned may be less than this total if you reach the final page of results
/// </remarks>
public int PageSize { get; set; } = 100;
internal string DebuggerDisplay
{
get
{
var values = new List<string>
{
"PageSize: " + PageSize
};
if (string.IsNullOrEmpty(StopWhenFound))
values.Add("StopWhenFound: " + StopWhenFound);
return String.Join(", ", values);
}
}
}
}

View File

@@ -0,0 +1,88 @@
using Octokit.Internal;
using System.Diagnostics;
using System.Globalization;
namespace Octokit.Models.Request.Enterprise
{
/// <summary>
/// Used to filter a audit log request.
/// </summary>
[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);
}
}
}
/// <summary>
/// The range of filters available for event types.
/// </summary>
/// <remarks>https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/audit-log</remarks>
public enum IncludeFilter
{
/// <summary>
/// Web (non-git) events. (Default)
/// </summary>
[Parameter(Value = "web")]
Web,
/// <summary>
/// Git events.
/// </summary>
[Parameter(Value = "git")]
Git,
/// <summary>
/// Both web and Git events.
/// </summary>
[Parameter(Value = "all")]
All
}
/// <summary>
/// The two possible sort directions.
/// </summary>
public enum AuditLogSortDirection
{
/// <summary>
/// Sort ascending
/// </summary>
[Parameter(Value = "asc")]
Ascending,
/// <summary>
/// Sort descending
/// </summary>
[Parameter(Value = "desc")]
Descending
}
}

View File

@@ -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<string> org, IReadOnlyList<long> 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<string> Org { get; private set; }
public IReadOnlyList<long> 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);
}
}
}
}

View File

@@ -1422,7 +1422,9 @@ namespace Octokit
bool valueIsDouble = value is double; bool valueIsDouble = value is double;
if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double)))
return value; return value;
if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) if (valueIsLong && type == typeof(IReadOnlyList<long>))
obj = new long[] { (long)value };
else if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long)))
{ {
if (valueIsLong && (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))) if (valueIsLong && (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?)))
{ {
@@ -1436,6 +1438,8 @@ namespace Octokit
? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture)
: value; : value;
} }
else if (type == typeof(object) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(object)))
obj = value;
else else
{ {
IDictionary<string, object> objects = value as IDictionary<string, object>; IDictionary<string, object> objects = value as IDictionary<string, object>;