Skip to content

Commit

Permalink
Add auditing for all user actions #3078 (#3083)
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenba committed Jun 22, 2016
1 parent 853a8bc commit b24c2ec
Show file tree
Hide file tree
Showing 44 changed files with 1,020 additions and 228 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -173,4 +173,5 @@ src/NuGetGallery.Cloud/ecf/
*.trx

# Vs2015
.vs/config/applicationhost.config
.vs/config/applicationhost.config
src/NuGetGallery/App_Data/Files/auditing/
4 changes: 3 additions & 1 deletion src/NuGetGallery.Core/Auditing/AuditActor.cs
@@ -1,12 +1,12 @@
// 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;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace NuGetGallery.Auditing
Expand All @@ -23,9 +23,11 @@ public class AuditActor

public AuditActor(string machineName, string machineIP, string userName, string authenticationType, DateTime timeStampUtc)
: this(machineName, machineIP, userName, authenticationType, timeStampUtc, null) { }

public AuditActor(string machineName, string machineIP, string userName, string authenticationType, DateTime timeStampUtc, AuditActor onBehalfOf)
{
MachineName = machineName;
MachineIP = machineIP;
UserName = userName;
AuthenticationType = authenticationType;
TimestampUtc = timeStampUtc;
Expand Down
@@ -0,0 +1,23 @@
// 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.

namespace NuGetGallery.Auditing
{
public enum AuditedAuthenticatedOperationAction
{
/// <summary>
/// Package push was attempted by a non-owner of the package
/// </summary>
PackagePushAttemptByNonOwner,

/// <summary>
/// Login failed, no such user
/// </summary>
FailedLoginNoSuchUser,

/// <summary>
/// Login failed, user exists but password is invalid
/// </summary>
FailedLoginInvalidPassword,
}
}
91 changes: 91 additions & 0 deletions src/NuGetGallery.Core/Auditing/AuditedEntities/AuditedPackage.cs
@@ -0,0 +1,91 @@
// 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;

namespace NuGetGallery.Auditing.AuditedEntities
{
public class AuditedPackage
{
public int PackageRegistrationKey { get; private set; }
public string Copyright { get; private set; }
public DateTime Created { get; private set; }
public string Description { get; private set; }
public string ReleaseNotes { get; private set; }
public int DownloadCount { get; private set; }
public string ExternalPackageUrl { get; private set; }
public string HashAlgorithm { get; private set; }
public string Hash { get; private set; }
public string IconUrl { get; private set; }
public bool IsLatest { get; private set; }
public bool IsLatestStable { get; private set; }
public DateTime LastUpdated { get; private set; }
public DateTime? LastEdited { get; private set; }
public string LicenseUrl { get; private set; }
public bool HideLicenseReport { get; private set; }
public string Language { get; private set; }
public DateTime Published { get; private set; }
public long PackageFileSize { get; private set; }
public string ProjectUrl { get; private set; }
public bool RequiresLicenseAcceptance { get; private set; }
public string Summary { get; private set; }
public string Tags { get; private set; }
public string Title { get; private set; }
public string Version { get; private set; }
public string NormalizedVersion { get; private set; }
public string LicenseNames { get; private set; }
public string LicenseReportUrl { get; private set; }
public bool Listed { get; private set; }
public bool IsPrerelease { get; private set; }
public string FlattenedAuthors { get; private set; }
public string FlattenedDependencies { get; private set; }
public int Key { get; private set; }
public string MinClientVersion { get; private set; }
public int? UserKey { get; private set; }
public bool Deleted { get; private set; }

public static AuditedPackage CreateFrom(Package package)
{
return new AuditedPackage
{
PackageRegistrationKey = package.PackageRegistrationKey,
Copyright = package.Copyright,
Created = package.Created,
Description = package.Description,
ReleaseNotes = package.ReleaseNotes,
DownloadCount = package.DownloadCount,
#pragma warning disable 612
#pragma warning restore 612
HashAlgorithm = package.HashAlgorithm,
Hash = package.Hash,
IconUrl = package.IconUrl,
IsLatest = package.IsLatest,
IsLatestStable = package.IsLatestStable,
LastUpdated = package.LastUpdated,
LastEdited = package.LastEdited,
LicenseUrl = package.LicenseUrl,
HideLicenseReport = package.HideLicenseReport,
Language = package.Language,
Published = package.Published,
PackageFileSize = package.PackageFileSize,
ProjectUrl = package.ProjectUrl,
RequiresLicenseAcceptance = package.RequiresLicenseAcceptance,
Summary = package.Summary,
Tags = package.Tags,
Title = package.Title,
Version = package.Version,
NormalizedVersion = package.NormalizedVersion,
LicenseNames = package.LicenseNames,
LicenseReportUrl = package.LicenseReportUrl,
Listed = package.Listed,
IsPrerelease = package.IsPrerelease,
FlattenedAuthors = package.FlattenedAuthors,
FlattenedDependencies = package.FlattenedDependencies,
Key = package.Key,
MinClientVersion = package.MinClientVersion,
UserKey = package.UserKey,
Deleted = package.Deleted
};
}
}
}
@@ -0,0 +1,17 @@
// 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.

namespace NuGetGallery.Auditing.AuditedEntities
{
public class AuditedPackageIdentifier
{
public string Id { get; }
public string Version { get; }

public AuditedPackageIdentifier(string id, string version)
{
Id = id;
Version = version;
}
}
}
@@ -0,0 +1,22 @@
// 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.

namespace NuGetGallery.Auditing.AuditedEntities
{
public class AuditedPackageRegistration
{
public string Id { get; private set; }
public int DownloadCount { get; private set; }
public int Key { get; private set; }

public static AuditedPackageRegistration CreateFrom(PackageRegistration packageRegistration)
{
return new AuditedPackageRegistration
{
Id = packageRegistration.Id,
DownloadCount = packageRegistration.DownloadCount,
Key = packageRegistration.Key
};
}
}
}
18 changes: 18 additions & 0 deletions src/NuGetGallery.Core/Auditing/AuditedPackageAction.cs
@@ -0,0 +1,18 @@
// 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.

namespace NuGetGallery.Auditing
{
public enum AuditedPackageAction
{
Delete,
SoftDelete,
Create,
List,
Unlist,
Edit,
UndoEdit,


}
}
11 changes: 11 additions & 0 deletions src/NuGetGallery.Core/Auditing/AuditedPackageRegistrationAction.cs
@@ -0,0 +1,11 @@
// 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.

namespace NuGetGallery.Auditing
{
public enum AuditedPackageRegistrationAction
{
AddOwner,
RemoveOwner
}
}
17 changes: 17 additions & 0 deletions src/NuGetGallery.Core/Auditing/AuditedUserAction.cs
@@ -0,0 +1,17 @@
// 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.

namespace NuGetGallery.Auditing
{
public enum AuditedUserAction
{
Register,
AddCredential,
RemoveCredential,
RequestPasswordReset,
ChangeEmail,
CancelChangeEmail,
ConfirmEmail,
Login
}
}
12 changes: 5 additions & 7 deletions src/NuGetGallery.Core/Auditing/AuditingService.cs
@@ -1,10 +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.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
Expand All @@ -15,7 +12,7 @@ public abstract class AuditingService
{
public static readonly AuditingService None = new NullAuditingService();

private static readonly JsonSerializerSettings _auditRecordSerializerSettings;
private static readonly JsonSerializerSettings AuditRecordSerializerSettings;

static AuditingService()
{
Expand All @@ -31,7 +28,7 @@ static AuditingService()
TypeNameHandling = TypeNameHandling.None
};
settings.Converters.Add(new StringEnumConverter());
_auditRecordSerializerSettings = settings;
AuditRecordSerializerSettings = settings;
}

public virtual async Task<Uri> SaveAuditRecord(AuditRecord record)
Expand All @@ -48,7 +45,7 @@ public virtual async Task<Uri> SaveAuditRecord(AuditRecord record)

public virtual string RenderAuditEntry(AuditEntry entry)
{
return JsonConvert.SerializeObject(entry, _auditRecordSerializerSettings);
return JsonConvert.SerializeObject(entry, AuditRecordSerializerSettings);
}

/// <summary>
Expand All @@ -73,6 +70,7 @@ protected override Task<Uri> SaveAuditRecord(string auditData, string resourceTy
{
var uriString = $"http://auditing.local/{resourceType}/{filePath}/{timestamp:s}-{action.ToLowerInvariant()}";
var uri = new Uri(uriString);

return Task.FromResult(uri);
}
}
Expand Down
38 changes: 21 additions & 17 deletions src/NuGetGallery.Core/Auditing/CloudAuditingService.cs
@@ -1,11 +1,9 @@
// 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.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Microsoft.WindowsAzure.Storage;
Expand All @@ -24,23 +22,23 @@ public class CloudAuditingService : AuditingService
private CloudBlobContainer _auditContainer;
private string _instanceId;
private string _localIP;
private Func<Task<AuditActor>> _onBehalfOfThunk;
private Func<Task<AuditActor>> _getOnBehalfOf;

public CloudAuditingService(string instanceId, string localIP, string storageConnectionString, Func<Task<AuditActor>> onBehalfOfThunk)
: this(instanceId, localIP, GetContainer(storageConnectionString), onBehalfOfThunk)
public CloudAuditingService(string instanceId, string localIP, string storageConnectionString, Func<Task<AuditActor>> getOnBehalfOf)
: this(instanceId, localIP, GetContainer(storageConnectionString), getOnBehalfOf)
{

}

public CloudAuditingService(string instanceId, string localIP, CloudBlobContainer auditContainer, Func<Task<AuditActor>> onBehalfOfThunk)
public CloudAuditingService(string instanceId, string localIP, CloudBlobContainer auditContainer, Func<Task<AuditActor>> getOnBehalfOf)
{
_instanceId = instanceId;
_localIP = localIP;
_auditContainer = auditContainer;
_onBehalfOfThunk = onBehalfOfThunk;
_getOnBehalfOf = getOnBehalfOf;
}

public static Task<AuditActor> AspNetActorThunk()
public static Task<AuditActor> GetAspNetOnBehalfOf()
{
// Use HttpContext to build an actor representing the user performing the action
var context = HttpContext.Current;
Expand All @@ -50,14 +48,20 @@ public static Task<AuditActor> AspNetActorThunk()
}

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

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

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

string user = null;
Expand All @@ -70,7 +74,7 @@ public static Task<AuditActor> AspNetActorThunk()

return Task.FromResult(new AuditActor(
null,
clientIP,
clientIpAddress,
user,
authType,
DateTime.UtcNow));
Expand All @@ -80,8 +84,8 @@ protected override async Task<AuditActor> GetActor()
{
// Construct an actor representing the user the service is acting on behalf of
AuditActor onBehalfOf = null;
if(_onBehalfOfThunk != null) {
onBehalfOf = await _onBehalfOfThunk();
if(_getOnBehalfOf != null) {
onBehalfOf = await _getOnBehalfOf();
}
return await AuditActor.GetCurrentMachineActor(onBehalfOf);
}
Expand Down

0 comments on commit b24c2ec

Please sign in to comment.