Skip to content

Commit db81abe

Browse files
authored
Copy list of scopes before removing them (#10275)
A collection modified exception is thrown since internally the DbContext modifies the collection we are enumerating.
1 parent 5be598a commit db81abe

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

src/NuGetGallery.Services/Authentication/AuthenticationService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -714,7 +714,7 @@ public virtual async Task RemoveCredential(User user, Credential cred, bool comm
714714

715715
public virtual async Task EditCredentialScopes(User user, Credential cred, ICollection<Scope> newScopes)
716716
{
717-
foreach (var oldScope in cred.Scopes)
717+
foreach (var oldScope in cred.Scopes.ToList())
718718
{
719719
Entities.Scopes.Remove(oldScope);
720720
}
@@ -1053,4 +1053,4 @@ private async Task MigrateCredentials(User user, List<Credential> creds, string
10531053
}
10541054
}
10551055
}
1056-
}
1056+
}

tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Data.Entity;
67
using System.Globalization;
78
using System.Linq;
89
using System.Security.Claims;
@@ -2382,6 +2383,44 @@ public async Task SavesChangesInTheDataStore()
23822383
authService.Entities.VerifyCommitChanges();
23832384
}
23842385

2386+
/// <summary>
2387+
/// Needed to avoid collection modified exception caused by the entity context.
2388+
/// </summary>
2389+
[Fact]
2390+
public async Task CopiesScopeCollectionForDeletion()
2391+
{
2392+
// Arrange
2393+
var credentialBuilder = new CredentialBuilder();
2394+
2395+
var credScopes =
2396+
Enumerable.Range(0, 5)
2397+
.Select(
2398+
i => new Scope { AllowedAction = NuGetScopes.PackagePush, Key = i, Subject = "package" + i }).ToList();
2399+
2400+
var mockScopes = new Mock<DbSet<Scope>>();
2401+
var dbContext = GetMock<IEntitiesContext>();
2402+
dbContext.Setup(x => x.Scopes).Returns(mockScopes.Object);
2403+
mockScopes.Setup(x => x.Remove(It.IsAny<Scope>())).Callback<Scope>(x => credScopes.Remove(x));
2404+
2405+
var fakes = Get<Fakes>();
2406+
var cred = credentialBuilder.CreateApiKey(null, out string plaintextApiKey);
2407+
var user = fakes.CreateUser("test", credentialBuilder.CreatePasswordCredential(Fakes.Password), cred);
2408+
var authService = Get<AuthenticationService>();
2409+
2410+
var newScopes =
2411+
Enumerable.Range(1, 2)
2412+
.Select(
2413+
i => new Scope { AllowedAction = NuGetScopes.PackageUnlist, Key = i * 10, Subject = "otherpackage" + i }).ToList();
2414+
2415+
cred.Scopes = credScopes;
2416+
2417+
// Act
2418+
await authService.EditCredentialScopes(user, cred, newScopes);
2419+
2420+
// Act
2421+
Assert.Empty(credScopes);
2422+
}
2423+
23852424
[Fact]
23862425
public async Task WritesAuditRecordForTheEditedCredential()
23872426
{

0 commit comments

Comments
 (0)