Skip to content

Commit

Permalink
Refactor AuditingService base class for external use (#3599)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtivel authored Feb 28, 2017
1 parent 124ac9e commit 3cbe156
Show file tree
Hide file tree
Showing 36 changed files with 1,592 additions and 154 deletions.
60 changes: 54 additions & 6 deletions src/NuGetGallery.Core/Auditing/AuditActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Web;

namespace NuGetGallery.Auditing
{
Expand All @@ -34,15 +35,62 @@ public AuditActor(string machineName, string machineIP, string userName, string
OnBehalfOf = onBehalfOf;
}

public static Task<AuditActor> GetCurrentMachineActor()
public static Task<AuditActor> GetAspNetOnBehalfOfAsync()
{
return GetCurrentMachineActor(null);
// Use HttpContext to build an actor representing the user performing the action
var context = HttpContext.Current;
if (context == null)
{
return Task.FromResult<AuditActor>(null);
}

return GetAspNetOnBehalfOfAsync(new HttpContextWrapper(context));
}

public static Task<AuditActor> GetAspNetOnBehalfOfAsync(HttpContextBase context)
{
// Try to identify the client IP using various server variables
var clientIpAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(clientIpAddress)) // Try REMOTE_ADDR server variable
{
clientIpAddress = context.Request.ServerVariables["REMOTE_ADDR"];
}

if (string.IsNullOrEmpty(clientIpAddress)) // Try UserHostAddress property
{
clientIpAddress = context.Request.UserHostAddress;
}

if (!string.IsNullOrEmpty(clientIpAddress) && clientIpAddress.IndexOf(".", StringComparison.Ordinal) > 0)
{
clientIpAddress = clientIpAddress.Substring(0, clientIpAddress.LastIndexOf(".", StringComparison.Ordinal)) + ".0";
}

string user = null;
string authType = null;
if (context.User != null)
{
user = context.User.Identity.Name;
authType = context.User.Identity.AuthenticationType;
}

return Task.FromResult(new AuditActor(
null,
clientIpAddress,
user,
authType,
DateTime.UtcNow));
}

public static Task<AuditActor> GetCurrentMachineActorAsync()
{
return GetCurrentMachineActorAsync(null);
}

public static async Task<AuditActor> GetCurrentMachineActor(AuditActor onBehalfOf)
public static async Task<AuditActor> GetCurrentMachineActorAsync(AuditActor onBehalfOf)
{
// Try to get local IP
string ipAddress = await GetLocalIP();
string ipAddress = await GetLocalIpAddressAsync();

return new AuditActor(
Environment.MachineName,
Expand All @@ -53,7 +101,7 @@ public static async Task<AuditActor> GetCurrentMachineActor(AuditActor onBehalfO
onBehalfOf);
}

public static async Task<string> GetLocalIP()
public static async Task<string> GetLocalIpAddressAsync()
{
string ipAddress = null;
if (NetworkInterface.GetIsNetworkAvailable())
Expand All @@ -74,4 +122,4 @@ private static string TryGetAddress(IEnumerable<IPAddress> addrs, AddressFamily
return addrs.Where(a => a.AddressFamily == family).Select(a => a.ToString()).FirstOrDefault();
}
}
}
}
49 changes: 37 additions & 12 deletions src/NuGetGallery.Core/Auditing/AuditingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@

namespace NuGetGallery.Auditing
{
/// <summary>
/// Base class for auditing services.
/// </summary>
public abstract class AuditingService
{
/// <summary>
/// An auditing service instance with no backing store.
/// </summary>
public static readonly AuditingService None = new NullAuditingService();

private static readonly JsonSerializerSettings AuditRecordSerializerSettings;
Expand All @@ -31,20 +37,39 @@ static AuditingService()
AuditRecordSerializerSettings = settings;
}

public virtual async Task<Uri> SaveAuditRecord(AuditRecord record)
/// <summary>
/// Persists the audit record to storage.
/// </summary>
/// <param name="record">An audit record.</param>
/// <returns>A task that represents the asynchronous save operation.
/// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="System.Uri"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="record" /> is <c>null</c>.</exception>
public virtual async Task<Uri> SaveAuditRecordAsync(AuditRecord record)
{
// Build an audit entry
var entry = new AuditEntry(record, await GetActor());
if (record == null)
{
throw new ArgumentNullException(nameof(record));
}

// Serialize to json
string rendered = RenderAuditEntry(entry);
var entry = new AuditEntry(record, await GetActorAsync());
var rendered = RenderAuditEntry(entry);

// Save the record
return await SaveAuditRecord(rendered, record.GetResourceType(), record.GetPath(), record.GetAction(), entry.Actor.TimestampUtc);
return await SaveAuditRecordAsync(rendered, record.GetResourceType(), record.GetPath(), record.GetAction(), entry.Actor.TimestampUtc);
}

/// <summary>
/// Renders an audit entry as JSON.
/// </summary>
/// <param name="entry">An audit entry.</param>
/// <returns>A JSON string.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="entry" /> is <c>null</c>.</exception>
public virtual string RenderAuditEntry(AuditEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}

return JsonConvert.SerializeObject(entry, AuditRecordSerializerSettings);
}

Expand All @@ -57,16 +82,16 @@ public virtual string RenderAuditEntry(AuditEntry entry)
/// <param name="action">The action recorded in this audit record</param>
/// <param name="timestamp">A timestamp indicating when the record was created</param>
/// <returns>The URI identifying the audit record resource</returns>
protected abstract Task<Uri> SaveAuditRecord(string auditData, string resourceType, string filePath, string action, DateTime timestamp);
protected abstract Task<Uri> SaveAuditRecordAsync(string auditData, string resourceType, string filePath, string action, DateTime timestamp);

protected virtual Task<AuditActor> GetActor()
protected virtual Task<AuditActor> GetActorAsync()
{
return AuditActor.GetCurrentMachineActor();
return AuditActor.GetCurrentMachineActorAsync();
}

private class NullAuditingService : AuditingService
{
protected override Task<Uri> SaveAuditRecord(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
protected override Task<Uri> SaveAuditRecordAsync(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
{
var uriString = $"http://auditing.local/{resourceType}/{filePath}/{timestamp:s}-{action.ToLowerInvariant()}";
var uri = new Uri(uriString);
Expand All @@ -75,4 +100,4 @@ protected override Task<Uri> SaveAuditRecord(string auditData, string resourceTy
}
}
}
}
}
49 changes: 3 additions & 46 deletions src/NuGetGallery.Core/Auditing/CloudAuditingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using System.Web;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Blob.Protocol;
Expand Down Expand Up @@ -38,59 +37,17 @@ public CloudAuditingService(string instanceId, string localIP, CloudBlobContaine
_getOnBehalfOf = getOnBehalfOf;
}

public static Task<AuditActor> GetAspNetOnBehalfOf()
{
// Use HttpContext to build an actor representing the user performing the action
var context = HttpContext.Current;
if (context == null)
{
return null;
}

// Try to identify the client IP using various server variables
string clientIpAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(clientIpAddress)) // Try REMOTE_ADDR server variable
{
clientIpAddress = context.Request.ServerVariables["REMOTE_ADDR"];
}

if (string.IsNullOrEmpty(clientIpAddress)) // Try UserHostAddress property
{
clientIpAddress = context.Request.UserHostAddress;
}

if (!string.IsNullOrEmpty(clientIpAddress) && clientIpAddress.IndexOf(".", StringComparison.Ordinal) > 0)
{
clientIpAddress = clientIpAddress.Substring(0, clientIpAddress.LastIndexOf(".", StringComparison.Ordinal)) + ".0";
}

string user = null;
string authType = null;
if (context.User != null)
{
user = context.User.Identity.Name;
authType = context.User.Identity.AuthenticationType;
}

return Task.FromResult(new AuditActor(
null,
clientIpAddress,
user,
authType,
DateTime.UtcNow));
}

protected override async Task<AuditActor> GetActor()
protected override async Task<AuditActor> GetActorAsync()
{
// Construct an actor representing the user the service is acting on behalf of
AuditActor onBehalfOf = null;
if(_getOnBehalfOf != null) {
onBehalfOf = await _getOnBehalfOf();
}
return await AuditActor.GetCurrentMachineActor(onBehalfOf);
return await AuditActor.GetCurrentMachineActorAsync(onBehalfOf);
}

protected override async Task<Uri> SaveAuditRecord(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
protected override async Task<Uri> SaveAuditRecordAsync(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
{
string fullPath =
$"{resourceType.ToLowerInvariant()}/" +
Expand Down
5 changes: 5 additions & 0 deletions src/NuGetGallery.Core/Auditing/CredentialAuditRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public class CredentialAuditRecord

public CredentialAuditRecord(Credential credential, bool removed)
{
if (credential == null)
{
throw new ArgumentNullException(nameof(credential));
}

Key = credential.Key;
Type = credential.Type;
Description = credential.Description;
Expand Down
59 changes: 8 additions & 51 deletions src/NuGetGallery.Core/Auditing/FileSystemAuditingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Web;

namespace NuGetGallery.Auditing
{
Expand All @@ -17,14 +16,14 @@ public class FileSystemAuditingService : AuditingService

private readonly string _auditingPath;
private readonly Func<Task<AuditActor>> _getOnBehalfOf;

public FileSystemAuditingService(string auditingPath, Func<Task<AuditActor>> getOnBehalfOf)
{
if (string.IsNullOrEmpty(auditingPath))
{
throw new ArgumentNullException(nameof(auditingPath));
}

if (getOnBehalfOf == null)
{
throw new ArgumentNullException(nameof(getOnBehalfOf));
Expand All @@ -34,49 +33,7 @@ public FileSystemAuditingService(string auditingPath, Func<Task<AuditActor>> get
_getOnBehalfOf = getOnBehalfOf;
}

public static Task<AuditActor> GetAspNetOnBehalfOf()
{
// Use HttpContext to build an actor representing the user performing the action
var context = HttpContext.Current;
if (context == null)
{
return null;
}

// Try to identify the client IP using various server variables
var clientIpAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(clientIpAddress)) // Try REMOTE_ADDR server variable
{
clientIpAddress = context.Request.ServerVariables["REMOTE_ADDR"];
}

if (string.IsNullOrEmpty(clientIpAddress)) // Try UserHostAddress property
{
clientIpAddress = context.Request.UserHostAddress;
}

if (!string.IsNullOrEmpty(clientIpAddress) && clientIpAddress.IndexOf(".", StringComparison.Ordinal) > 0)
{
clientIpAddress = clientIpAddress.Substring(0, clientIpAddress.LastIndexOf(".", StringComparison.Ordinal)) + ".0";
}

string user = null;
string authType = null;
if (context.User != null)
{
user = context.User.Identity.Name;
authType = context.User.Identity.AuthenticationType;
}

return Task.FromResult(new AuditActor(
null,
clientIpAddress,
user,
authType,
DateTime.UtcNow));
}

protected override async Task<AuditActor> GetActor()
protected override async Task<AuditActor> GetActorAsync()
{
// Construct an actor representing the user the service is acting on behalf of
AuditActor onBehalfOf = null;
Expand All @@ -85,17 +42,17 @@ protected override async Task<AuditActor> GetActor()
onBehalfOf = await _getOnBehalfOf();
}

return await AuditActor.GetCurrentMachineActor(onBehalfOf);
return await AuditActor.GetCurrentMachineActorAsync(onBehalfOf);
}

protected override Task<Uri> SaveAuditRecord(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
protected override Task<Uri> SaveAuditRecordAsync(string auditData, string resourceType, string filePath, string action, DateTime timestamp)
{
// Build relative file path
var relativeFilePath =
var relativeFilePath =
$"{resourceType.ToLowerInvariant()}{Path.DirectorySeparatorChar}" +
$"{filePath}{Path.DirectorySeparatorChar}" +
$"{Guid.NewGuid().ToString("N")}-{action.ToLowerInvariant()}.audit.v1.json";

// Build full file path
var fullFilePath = Path.Combine(_auditingPath, relativeFilePath);

Expand All @@ -115,4 +72,4 @@ protected override Task<Uri> SaveAuditRecord(string auditData, string resourceTy
return Task.FromResult(uri);
}
}
}
}
6 changes: 6 additions & 0 deletions src/NuGetGallery.Core/Auditing/UserAuditRecord.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -29,6 +30,11 @@ public UserAuditRecord(User user, AuditedUserAction action, Credential affected)
public UserAuditRecord(User user, AuditedUserAction action, IEnumerable<Credential> affected)
: base(action)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}

Username = user.Username;
EmailAddress = user.EmailAddress;
UnconfirmedEmailAddress = user.UnconfirmedEmailAddress;
Expand Down
Loading

0 comments on commit 3cbe156

Please sign in to comment.