Skip to content

Commit bac0448

Browse files
authored
Push BuildScopes and VerifyScopes into ICredentialBuilder (#10213)
* Push BuildScopes and VerifyScopes into ICredentialBuilder This allows more code sharing for other places that create credentials. * Fix typo
1 parent 405b9b0 commit bac0448

4 files changed

Lines changed: 102 additions & 89 deletions

File tree

src/NuGetGallery.Services/Authentication/CredentialBuilder.cs

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
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;
5+
using System.Collections.Generic;
56
using System.Linq;
67
using NuGet.Services.Entities;
78
using NuGetGallery.Authentication;
@@ -12,6 +13,14 @@ public class CredentialBuilder : ICredentialBuilder
1213
{
1314
public const string LatestPasswordType = CredentialTypes.Password.V3;
1415

16+
private static readonly IReadOnlyDictionary<string, IReadOnlyList<IActionRequiringEntityPermissions>> AllowedActionToActionRequiringEntityPermissionsMap = new Dictionary<string, IReadOnlyList<IActionRequiringEntityPermissions>>
17+
{
18+
{ NuGetScopes.PackagePush, new IActionRequiringEntityPermissions[] { ActionsRequiringPermissions.UploadNewPackageId, ActionsRequiringPermissions.UploadNewPackageVersion } },
19+
{ NuGetScopes.PackagePushVersion, new [] { ActionsRequiringPermissions.UploadNewPackageVersion } },
20+
{ NuGetScopes.PackageUnlist, new [] { ActionsRequiringPermissions.UnlistOrRelistPackage } },
21+
{ NuGetScopes.PackageVerify, new [] { ActionsRequiringPermissions.VerifyPackage } },
22+
};
23+
1524
public Credential CreatePasswordCredential(string plaintextPassword)
1625
{
1726
return new Credential(
@@ -73,9 +82,81 @@ public Credential CreateExternalCredential(string issuer, string value, string i
7382
};
7483
}
7584

85+
public IList<Scope> BuildScopes(User scopeOwner, string[] scopes, string[] subjects)
86+
{
87+
var result = new List<Scope>();
88+
89+
var subjectsList = subjects?.Where(s => !string.IsNullOrWhiteSpace(s)).ToList() ?? new List<string>();
90+
91+
// No package filtering information was provided. So allow any pattern.
92+
if (!subjectsList.Any())
93+
{
94+
subjectsList.Add(NuGetPackagePattern.AllInclusivePattern);
95+
}
96+
97+
if (scopes != null)
98+
{
99+
foreach (var scope in scopes)
100+
{
101+
result.AddRange(subjectsList.Select(subject => new Scope(scopeOwner, subject, scope)));
102+
}
103+
}
104+
else
105+
{
106+
result.AddRange(subjectsList.Select(subject => new Scope(scopeOwner, subject, NuGetScopes.All)));
107+
}
108+
109+
return result;
110+
}
111+
112+
public bool VerifyScopes(User currentUser, IEnumerable<Scope> scopes)
113+
{
114+
if (!scopes.Any())
115+
{
116+
// All API keys must have at least one scope.
117+
return false;
118+
}
119+
120+
foreach (var scope in scopes)
121+
{
122+
if (string.IsNullOrEmpty(scope.AllowedAction))
123+
{
124+
// All scopes must have an allowed action.
125+
return false;
126+
}
127+
128+
// Get the list of actions allowed by this scope.
129+
var actions = new List<IActionRequiringEntityPermissions>();
130+
foreach (var allowedAction in AllowedActionToActionRequiringEntityPermissionsMap.Keys)
131+
{
132+
if (scope.AllowsActions(allowedAction))
133+
{
134+
actions.AddRange(AllowedActionToActionRequiringEntityPermissionsMap[allowedAction]);
135+
}
136+
}
137+
138+
if (!actions.Any())
139+
{
140+
// A scope should allow at least one action.
141+
return false;
142+
}
143+
144+
foreach (var action in actions)
145+
{
146+
if (!action.IsAllowedOnBehalfOfAccount(currentUser, scope.Owner))
147+
{
148+
// The user must be able to perform the actions allowed by the scope on behalf of the scope's owner.
149+
return false;
150+
}
151+
}
152+
}
153+
154+
return true;
155+
}
156+
76157
private static string CreateKeyString()
77158
{
78159
return Guid.NewGuid().ToString().ToLowerInvariant();
79160
}
80161
}
81-
}
162+
}

src/NuGetGallery.Services/Authentication/IAuthenticationService.cs

Lines changed: 9 additions & 2 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.Threading.Tasks;
@@ -43,5 +43,12 @@ public interface IAuthenticationService
4343
/// </summary>
4444
/// <returns>Returns whether the API key credential is active or not</returns>
4545
bool IsActiveApiKeyCredential(Credential credential);
46+
47+
/// <summary>
48+
/// Adds a new credential to the user. This method saves changes in the entity context.
49+
/// </summary>
50+
/// <param name="user">The user who owns the credential.</param>
51+
/// <param name="credential">The credential to be added.</param>
52+
Task AddCredential(User user, Credential credential);
4653
}
47-
}
54+
}

src/NuGetGallery.Services/Authentication/ICredentialBuilder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
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;
5+
using System.Collections.Generic;
56
using NuGet.Services.Entities;
67

78
namespace NuGetGallery.Infrastructure.Authentication
@@ -15,5 +16,9 @@ public interface ICredentialBuilder
1516
Credential CreatePackageVerificationApiKey(Credential originalApiKey, string id);
1617

1718
Credential CreateExternalCredential(string issuer, string value, string identity, string tenantId = null);
19+
20+
IList<Scope> BuildScopes(User scopeOwner, string[] scopes, string[] subjects);
21+
22+
bool VerifyScopes(User currentUser, IEnumerable<Scope> scopes);
1823
}
1924
}

src/NuGetGallery/Controllers/UsersController.cs

Lines changed: 4 additions & 84 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;
@@ -1025,8 +1025,8 @@ public virtual async Task<JsonResult> GenerateApiKey(string description, string
10251025
return Json(Strings.UserNotFound);
10261026
}
10271027

1028-
var resolvedScopes = BuildScopes(scopeOwner, scopes, subjects);
1029-
if (!VerifyScopes(resolvedScopes))
1028+
var resolvedScopes = _credentialBuilder.BuildScopes(scopeOwner, scopes, subjects);
1029+
if (!_credentialBuilder.VerifyScopes(GetCurrentUser(), resolvedScopes))
10301030
{
10311031
Response.StatusCode = (int)HttpStatusCode.BadRequest;
10321032
return Json(Strings.ApiKeyScopesNotAllowed);
@@ -1079,7 +1079,7 @@ public virtual async Task<JsonResult> EditCredential(string credentialType, int?
10791079

10801080
var scopeOwner = cred.Scopes.GetOwnerScope();
10811081
var scopes = cred.Scopes.Select(x => x.AllowedAction).Distinct().ToArray();
1082-
var newScopes = BuildScopes(scopeOwner, scopes, subjects);
1082+
var newScopes = _credentialBuilder.BuildScopes(scopeOwner, scopes, subjects);
10831083

10841084
await AuthenticationService.EditCredentialScopes(user, cred, newScopes);
10851085

@@ -1111,86 +1111,6 @@ private async Task<CredentialViewModel> GenerateApiKeyInternal(string descriptio
11111111
return credentialViewModel;
11121112
}
11131113

1114-
private static IDictionary<string, IActionRequiringEntityPermissions[]> AllowedActionToActionRequiringEntityPermissionsMap = new Dictionary<string, IActionRequiringEntityPermissions[]>
1115-
{
1116-
{ NuGetScopes.PackagePush, new IActionRequiringEntityPermissions[] { ActionsRequiringPermissions.UploadNewPackageId, ActionsRequiringPermissions.UploadNewPackageVersion } },
1117-
{ NuGetScopes.PackagePushVersion, new [] { ActionsRequiringPermissions.UploadNewPackageVersion } },
1118-
{ NuGetScopes.PackageUnlist, new [] { ActionsRequiringPermissions.UnlistOrRelistPackage } },
1119-
{ NuGetScopes.PackageVerify, new [] { ActionsRequiringPermissions.VerifyPackage } },
1120-
};
1121-
1122-
private bool VerifyScopes(IEnumerable<Scope> scopes)
1123-
{
1124-
if (!scopes.Any())
1125-
{
1126-
// All API keys must have at least one scope.
1127-
return false;
1128-
}
1129-
1130-
foreach (var scope in scopes)
1131-
{
1132-
if (string.IsNullOrEmpty(scope.AllowedAction))
1133-
{
1134-
// All scopes must have an allowed action.
1135-
return false;
1136-
}
1137-
1138-
// Get the list of actions allowed by this scope.
1139-
var actions = new List<IActionRequiringEntityPermissions>();
1140-
foreach (var allowedAction in AllowedActionToActionRequiringEntityPermissionsMap.Keys)
1141-
{
1142-
if (scope.AllowsActions(allowedAction))
1143-
{
1144-
actions.AddRange(AllowedActionToActionRequiringEntityPermissionsMap[allowedAction]);
1145-
}
1146-
}
1147-
1148-
if (!actions.Any())
1149-
{
1150-
// A scope should allow at least one action.
1151-
return false;
1152-
}
1153-
1154-
foreach (var action in actions)
1155-
{
1156-
if (!action.IsAllowedOnBehalfOfAccount(GetCurrentUser(), scope.Owner))
1157-
{
1158-
// The user must be able to perform the actions allowed by the scope on behalf of the scope's owner.
1159-
return false;
1160-
}
1161-
}
1162-
}
1163-
1164-
return true;
1165-
}
1166-
1167-
private IList<Scope> BuildScopes(User scopeOwner, string[] scopes, string[] subjects)
1168-
{
1169-
var result = new List<Scope>();
1170-
1171-
var subjectsList = subjects?.Where(s => !string.IsNullOrWhiteSpace(s)).ToList() ?? new List<string>();
1172-
1173-
// No package filtering information was provided. So allow any pattern.
1174-
if (!subjectsList.Any())
1175-
{
1176-
subjectsList.Add(NuGetPackagePattern.AllInclusivePattern);
1177-
}
1178-
1179-
if (scopes != null)
1180-
{
1181-
foreach (var scope in scopes)
1182-
{
1183-
result.AddRange(subjectsList.Select(subject => new Scope(scopeOwner, subject, scope)));
1184-
}
1185-
}
1186-
else
1187-
{
1188-
result.AddRange(subjectsList.Select(subject => new Scope(scopeOwner, subject, NuGetScopes.All)));
1189-
}
1190-
1191-
return result;
1192-
}
1193-
11941114
private static IList<Scope> BuildScopes(IEnumerable<Scope> scopes)
11951115
{
11961116
return scopes.Select(scope => new Scope(scope.Owner, scope.Subject, scope.AllowedAction)).ToList();

0 commit comments

Comments
 (0)