Skip to content

Commit 3f92a09

Browse files
authored
Audit the AddCredential action after we commit to the DB (#9324)
Resolve NuGet/Engineering#4660
1 parent 430e952 commit 3f92a09

2 files changed

Lines changed: 66 additions & 6 deletions

File tree

src/NuGetGallery.Services/Authentication/AuthenticationService.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ public virtual async Task ReplaceCredential(User user, Credential credential)
473473
{
474474
await ReplaceCredentialInternal(user, credential);
475475
await Entities.SaveChangesAsync();
476+
477+
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(
478+
user, AuditedUserAction.AddCredential, credential));
476479
}
477480

478481
public virtual async Task<Credential> ResetPasswordWithToken(string username, string token, string newPassword)
@@ -501,6 +504,10 @@ public virtual async Task<Credential> ResetPasswordWithToken(string username, st
501504
user.FailedLoginCount = 0;
502505
user.LastFailedLoginUtc = null;
503506
await Entities.SaveChangesAsync();
507+
508+
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(
509+
user, AuditedUserAction.AddCredential, cred));
510+
504511
return cred;
505512
}
506513

@@ -590,6 +597,10 @@ public virtual async Task<bool> ChangePassword(User user, string oldPassword, st
590597

591598
// Save changes
592599
await Entities.SaveChangesAsync();
600+
601+
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(
602+
user, AuditedUserAction.AddCredential, passwordCredential));
603+
593604
return true;
594605
}
595606

@@ -623,10 +634,10 @@ public virtual async Task AddCredential(User user, Credential credential)
623634
throw new InvalidOperationException(ServicesStrings.OrganizationsCannotCreateCredentials);
624635
}
625636

626-
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, credential));
627637
user.Credentials.Add(credential);
628638
await Entities.SaveChangesAsync();
629639

640+
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, credential));
630641
_telemetryService.TrackNewCredentialCreated(user, credential);
631642
}
632643

@@ -838,9 +849,6 @@ await Auditing.SaveAuditRecordAsync(new UserAuditRecord(
838849
}
839850

840851
user.Credentials.Add(credential);
841-
842-
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(
843-
user, AuditedUserAction.AddCredential, credential));
844852
}
845853

846854
private static CredentialKind GetCredentialKind(string type)
@@ -1024,15 +1032,20 @@ private async Task MigrateCredentials(User user, List<Credential> creds, string
10241032
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.RemoveCredential, toRemove));
10251033

10261034
// Now add one if there are no credentials left
1035+
Credential newCred = null;
10271036
if (creds.Count == 0)
10281037
{
1029-
var newCred = _credentialBuilder.CreatePasswordCredential(password);
1030-
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, newCred));
1038+
newCred = _credentialBuilder.CreatePasswordCredential(password);
10311039
user.Credentials.Add(newCred);
10321040
}
10331041

10341042
// Save changes, if any
10351043
await Entities.SaveChangesAsync();
1044+
1045+
if (newCred != null)
1046+
{
1047+
await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, newCred));
1048+
}
10361049
}
10371050
}
10381051
}

tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
using NuGetGallery.Auditing;
1616
using NuGetGallery.Authentication.Providers;
1717
using NuGetGallery.Authentication.Providers.MicrosoftAccount;
18+
using NuGetGallery.Configuration;
19+
using NuGetGallery.Diagnostics;
1820
using NuGetGallery.Framework;
1921
using NuGetGallery.Infrastructure.Authentication;
2022
using Xunit;
@@ -2071,6 +2073,51 @@ public async Task WritesAuditRecordForTheNewCredential()
20712073
ar.AffectedCredential[0].Type == cred.Type &&
20722074
ar.AffectedCredential[0].Identity == cred.Identity));
20732075
}
2076+
2077+
[Fact]
2078+
public async Task WritesAuditRecordAfterDbCommit()
2079+
{
2080+
// Arrange
2081+
var entitiesContext = new Mock<IEntitiesContext>();
2082+
var auditingService = new Mock<IAuditingService>();
2083+
var credentialBuilder = new CredentialBuilder();
2084+
2085+
var authService = new AuthenticationService(
2086+
entitiesContext.Object,
2087+
Get<IAppConfiguration>(),
2088+
Get<IDiagnosticsService>(),
2089+
auditingService.Object,
2090+
Enumerable.Empty<Authenticator>(),
2091+
credentialBuilder,
2092+
Get<ICredentialValidator>(),
2093+
Get<IDateTimeProvider>(),
2094+
Get<ITelemetryService>(),
2095+
Get<IContentObjectService>(),
2096+
Get<IFeatureFlagService>());
2097+
var operations = new List<string>();
2098+
2099+
var fakes = Get<Fakes>();
2100+
var user = fakes.CreateUser("test", credentialBuilder.CreatePasswordCredential(Fakes.Password));
2101+
var cred = credentialBuilder.CreateExternalCredential("flarg", "glarb", "blarb");
2102+
Mock
2103+
.Get(authService.Auditing)
2104+
.Setup(x => x.SaveAuditRecordAsync(It.IsAny<AuditRecord>()))
2105+
.Returns(Task.CompletedTask)
2106+
.Callback(() => operations.Add(nameof(IAuditingService.SaveAuditRecordAsync)));
2107+
Mock
2108+
.Get(authService.Entities)
2109+
.Setup(x => x.SaveChangesAsync())
2110+
.ReturnsAsync(() => 0)
2111+
.Callback(() => operations.Add(nameof(IEntitiesContext.SaveChangesAsync)));
2112+
2113+
// Act
2114+
await authService.AddCredential(user, cred);
2115+
2116+
// Assert
2117+
Assert.Equal(
2118+
new List<string> { nameof(IEntitiesContext.SaveChangesAsync), nameof(IAuditingService.SaveAuditRecordAsync) },
2119+
operations);
2120+
}
20742121
}
20752122

20762123
public class TheDescribeCredentialMethod : TestContainer

0 commit comments

Comments
 (0)