Skip to content

Commit 4e62f82

Browse files
committed
Configurable admin actions.
1 parent 7722974 commit 4e62f82

4 files changed

Lines changed: 169 additions & 27 deletions

File tree

src/NuGetGallery.Services/Permissions/ActionsRequiringPermissions.cs

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,163 +8,168 @@ namespace NuGetGallery
88
/// </summary>
99
public static class ActionsRequiringPermissions
1010
{
11-
private const PermissionsRequirement RequireOwnerOrSiteAdmin =
12-
PermissionsRequirement.Owner | PermissionsRequirement.SiteAdmin;
13-
private const PermissionsRequirement RequireOwnerOrSiteAdminOrOrganizationAdmin =
14-
PermissionsRequirement.Owner | PermissionsRequirement.SiteAdmin | PermissionsRequirement.OrganizationAdmin;
15-
private const PermissionsRequirement RequireOwnerOrOrganizationAdmin =
11+
public static bool AdminAccessEnabled { get; set; } = true;
12+
13+
private static PermissionsRequirement AdminRequirement =>
14+
AdminAccessEnabled ? PermissionsRequirement.SiteAdmin : PermissionsRequirement.Unsatisfiable;
15+
16+
private static PermissionsRequirement RequireOwnerOrSiteAdmin =>
17+
PermissionsRequirement.Owner | AdminRequirement;
18+
private static PermissionsRequirement RequireOwnerOrSiteAdminOrOrganizationAdmin =>
19+
PermissionsRequirement.Owner | AdminRequirement | PermissionsRequirement.OrganizationAdmin;
20+
private static PermissionsRequirement RequireOwnerOrOrganizationAdmin =>
1621
PermissionsRequirement.Owner | PermissionsRequirement.OrganizationAdmin;
17-
private const PermissionsRequirement RequireOwnerOrOrganizationMember =
22+
private static PermissionsRequirement RequireOwnerOrOrganizationMember =>
1823
PermissionsRequirement.Owner | PermissionsRequirement.OrganizationAdmin | PermissionsRequirement.OrganizationCollaborator;
19-
private const PermissionsRequirement RequireOwnerOrSiteAdminOrOrganizationMember =
20-
PermissionsRequirement.Owner | PermissionsRequirement.SiteAdmin | PermissionsRequirement.OrganizationAdmin | PermissionsRequirement.OrganizationCollaborator;
24+
private static PermissionsRequirement RequireOwnerOrSiteAdminOrOrganizationMember =>
25+
PermissionsRequirement.Owner | AdminRequirement | PermissionsRequirement.OrganizationAdmin | PermissionsRequirement.OrganizationCollaborator;
2126

2227
/// <summary>
2328
/// The action of seeing private metadata about a package.
2429
/// For example, if a package is validating, only users who can perform this action can see the metadata of the package.
2530
/// </summary>
26-
public static ActionRequiringPackagePermissions DisplayPrivatePackageMetadata =
31+
public static ActionRequiringPackagePermissions DisplayPrivatePackageMetadata =>
2732
new ActionRequiringPackagePermissions(
2833
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
2934
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
3035

3136
/// <summary>
3237
/// The action of uploading a new package ID.
3338
/// </summary>
34-
public static ActionRequiringReservedNamespacePermissions UploadNewPackageId =
39+
public static ActionRequiringReservedNamespacePermissions UploadNewPackageId =>
3540
new ActionRequiringReservedNamespacePermissions(
3641
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
3742
reservedNamespacePermissionsRequirement: PermissionsRequirement.Owner);
3843

3944
/// <summary>
4045
/// The action of uploading a new version of an existing package ID.
4146
/// </summary>
42-
public static ActionRequiringPackagePermissions UploadNewPackageVersion =
47+
public static ActionRequiringPackagePermissions UploadNewPackageVersion =>
4348
new ActionRequiringPackagePermissions(
4449
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
4550
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
4651

4752
/// <summary>
4853
/// The action of uploading a symbols package for an existing package.
4954
/// </summary>
50-
public static ActionRequiringPackagePermissions UploadSymbolPackage =
55+
public static ActionRequiringPackagePermissions UploadSymbolPackage =>
5156
new ActionRequiringPackagePermissions(
5257
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
5358
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
5459

5560
/// <summary>
5661
/// The action of deleting a symbols package for an existing package.
5762
/// </summary>
58-
public static ActionRequiringPackagePermissions DeleteSymbolPackage =
63+
public static ActionRequiringPackagePermissions DeleteSymbolPackage =>
5964
new ActionRequiringPackagePermissions(
6065
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
6166
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
6267

6368
/// <summary>
6469
/// The action of verify a package verification key.
6570
/// </summary>
66-
public static ActionRequiringPackagePermissions VerifyPackage =
71+
public static ActionRequiringPackagePermissions VerifyPackage =>
6772
new ActionRequiringPackagePermissions(
6873
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
6974
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
7075

7176
/// <summary>
7277
/// The action of editing an existing version of an existing package ID.
7378
/// </summary>
74-
public static ActionRequiringPackagePermissions EditPackage =
79+
public static ActionRequiringPackagePermissions EditPackage =>
7580
new ActionRequiringPackagePermissions(
7681
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
7782
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
7883

7984
/// <summary>
8085
/// The action of unlisting or relisting an existing version of an existing package ID.
8186
/// </summary>
82-
public static ActionRequiringPackagePermissions UnlistOrRelistPackage =
87+
public static ActionRequiringPackagePermissions UnlistOrRelistPackage =>
8388
new ActionRequiringPackagePermissions(
8489
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
8590
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
8691

8792
/// <summary>
8893
/// The action of deprecating an existing version of an existing package ID.
8994
/// </summary>
90-
public static ActionRequiringPackagePermissions DeprecatePackage =
95+
public static ActionRequiringPackagePermissions DeprecatePackage =>
9196
new ActionRequiringPackagePermissions(
9297
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
9398
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
9499

95100
/// <summary>
96101
/// The action of managing the ownership of an existing package ID.
97102
/// </summary>
98-
public static ActionRequiringPackagePermissions ManagePackageOwnership =
103+
public static ActionRequiringPackagePermissions ManagePackageOwnership =>
99104
new ActionRequiringPackagePermissions(
100105
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationAdmin,
101106
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
102107

103108
/// <summary>
104109
/// The action of reporting an existing package ID as the owner of the package.
105110
/// </summary>
106-
public static ActionRequiringPackagePermissions ReportPackageAsOwner =
111+
public static ActionRequiringPackagePermissions ReportPackageAsOwner =>
107112
new ActionRequiringPackagePermissions(
108113
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
109114
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
110115

111116
/// <summary>
112117
/// The action of seeing a breadcrumb linking the user back to their profile when performing actions on a package.
113118
/// </summary>
114-
public static ActionRequiringPackagePermissions ShowProfileBreadcrumb =
119+
public static ActionRequiringPackagePermissions ShowProfileBreadcrumb =>
115120
new ActionRequiringPackagePermissions(
116121
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
117122
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
118123

119124
/// <summary>
120125
/// The action of handling package ownership requests for a user to become an owner of a package.
121126
/// </summary>
122-
public static ActionRequiringAccountPermissions HandlePackageOwnershipRequest =
127+
public static ActionRequiringAccountPermissions HandlePackageOwnershipRequest =>
123128
new ActionRequiringAccountPermissions(
124129
accountPermissionsRequirement: RequireOwnerOrOrganizationAdmin);
125130

126131
/// <summary>
127132
/// The action of viewing (read-only) a user or organization account.
128133
/// </summary>
129-
public static ActionRequiringAccountPermissions ViewAccount =
134+
public static ActionRequiringAccountPermissions ViewAccount =>
130135
new ActionRequiringAccountPermissions(
131136
accountPermissionsRequirement: RequireOwnerOrSiteAdminOrOrganizationMember);
132137

133138
/// <summary>
134139
/// The action of managing a user or organization account. This includes confirming an account,
135140
/// changing the email address, changing email subscriptions, modifying sign-in credentials, etc.
136141
/// </summary>
137-
public static ActionRequiringAccountPermissions ManageAccount =
142+
public static ActionRequiringAccountPermissions ManageAccount =>
138143
new ActionRequiringAccountPermissions(
139144
accountPermissionsRequirement: RequireOwnerOrOrganizationAdmin);
140145

141146
/// <summary>
142147
/// The action of managing an organization's memberships.
143148
/// </summary>
144-
public static ActionRequiringAccountPermissions ManageMembership =
149+
public static ActionRequiringAccountPermissions ManageMembership =>
145150
new ActionRequiringAccountPermissions(
146151
accountPermissionsRequirement: RequireOwnerOrSiteAdminOrOrganizationAdmin);
147152

148153
/// <summary>
149154
/// The action of changing a package's required signer.
150155
/// </summary>
151-
public static ActionRequiringPackagePermissions ManagePackageRequiredSigner =
156+
public static ActionRequiringPackagePermissions ManagePackageRequiredSigner =>
152157
new ActionRequiringPackagePermissions(
153158
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationAdmin,
154159
packageRegistrationPermissionsRequirement: RequireOwnerOrOrganizationAdmin);
155160

156161
/// <summary>
157162
/// The action of adding a package to a reserved namespace that the package is in.
158163
/// </summary>
159-
public static ActionRequiringReservedNamespacePermissions AddPackageToReservedNamespace =
164+
public static ActionRequiringReservedNamespacePermissions AddPackageToReservedNamespace =>
160165
new ActionRequiringReservedNamespacePermissions(
161166
accountOnBehalfOfPermissionsRequirement: PermissionsRequirement.Owner,
162167
reservedNamespacePermissionsRequirement: PermissionsRequirement.Owner);
163168

164169
/// <summary>
165170
/// The action of removing a package from a reserved namespace that the package is in.
166171
/// </summary>
167-
public static ActionRequiringReservedNamespacePermissions RemovePackageFromReservedNamespace =
172+
public static ActionRequiringReservedNamespacePermissions RemovePackageFromReservedNamespace =>
168173
new ActionRequiringReservedNamespacePermissions(
169174
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrSiteAdminOrOrganizationAdmin,
170175
reservedNamespacePermissionsRequirement: RequireOwnerOrOrganizationAdmin);

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ protected override void Load(ContainerBuilder builder)
110110
loggerConfiguration,
111111
telemetryConfiguration: applicationInsightsConfiguration.TelemetryConfiguration);
112112

113+
ActionsRequiringPermissions.AdminAccessEnabled = configuration.Current.AdminPanelEnabled;
114+
113115
builder.RegisterInstance(applicationInsightsConfiguration.TelemetryConfiguration)
114116
.AsSelf()
115117
.SingleInstance();

tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
<Compile Include="Helpers\StreamHelperFacts.cs" />
104104
<Compile Include="Infrastructure\Mail\Messages\SearchSideBySideMessageFacts.cs" />
105105
<Compile Include="Infrastructure\SearchServiceFactoryFacts.cs" />
106+
<Compile Include="Services\ActionsRequiringPermissionsAdminFacts.cs" />
106107
<Compile Include="Services\ImageDomainValidatorFacts.cs" />
107108
<Compile Include="Services\MarkdownServiceFacts.cs" />
108109
<Compile Include="Services\PackageVulnerabilitiesCacheServiceFacts.cs" />
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using NuGet.Services.Entities;
10+
using Xunit;
11+
12+
namespace NuGetGallery.Services
13+
{
14+
public class ActionsRequiringPermissionsAdminFacts
15+
{
16+
private int _key = 0;
17+
18+
public static IEnumerable<object[]> PackageActions = new object[][]
19+
{
20+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.DisplayPrivatePackageMetadata) },
21+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.DeleteSymbolPackage) },
22+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.EditPackage) },
23+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.UnlistOrRelistPackage) },
24+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.DeprecatePackage) },
25+
new object[] { (Func<ActionRequiringPackagePermissions>)(() => ActionsRequiringPermissions.ManagePackageOwnership) },
26+
};
27+
28+
public static IEnumerable<object[]> PackageActionsWithAdmin =>
29+
from actionProvider in PackageActions
30+
from isAdmin in new[] { false, true }
31+
select new object[] { actionProvider[0], isAdmin };
32+
33+
[Theory]
34+
[MemberData(nameof(PackageActionsWithAdmin))]
35+
public void SatisfiedWhenAdminIsEnabledForPackagesAnyAccount(
36+
Func<ActionRequiringPackagePermissions> actionProvider,
37+
bool isAdmin)
38+
{
39+
// separate set of requirements are used for CheckPermission call is used
40+
// which never includes SiteAdmin check, so we are not going to test those
41+
42+
ActionsRequiringPermissions.AdminAccessEnabled = isAdmin;
43+
44+
var user = new User("testuser" + _key) { Key = _key++ };
45+
user.Roles.Add(new Role { Name = Constants.AdminRoleName });
46+
47+
var pkg = new Package();
48+
pkg.PackageRegistration = new PackageRegistration { Owners = new User[0] };
49+
50+
var action = actionProvider();
51+
var result = action.CheckPermissionsOnBehalfOfAnyAccount(user, pkg);
52+
Assert.Equal(isAdmin, PermissionsCheckResult.Allowed == result);
53+
}
54+
55+
public static IEnumerable<object[]> AccountActions = new object[][]
56+
{
57+
new object[]{ (Func<ActionRequiringAccountPermissions>)(() => ActionsRequiringPermissions.ViewAccount) },
58+
new object[]{ (Func<ActionRequiringAccountPermissions>)(() => ActionsRequiringPermissions.ManageMembership) },
59+
};
60+
61+
public static IEnumerable<object[]> AccountActionsWithAdmin =>
62+
from actionProvider in AccountActions
63+
from isAdmin in new[] { false, true }
64+
select new object[] { actionProvider[0], isAdmin };
65+
66+
[Theory]
67+
[MemberData(nameof(AccountActionsWithAdmin))]
68+
public void SatisfiedDependingOnAdminEnabledForAccounts(
69+
Func<ActionRequiringAccountPermissions> actionProvider,
70+
bool isAdmin)
71+
{
72+
ActionsRequiringPermissions.AdminAccessEnabled = isAdmin;
73+
74+
var user = new User("testuser" + _key) { Key = _key++ };
75+
user.Roles.Add(new Role { Name = Constants.AdminRoleName });
76+
77+
var target = new User("testuser" + _key) { Key = _key++ };
78+
79+
var action = actionProvider();
80+
var result = action.CheckPermissions(user, target);
81+
Assert.Equal(isAdmin, PermissionsCheckResult.Allowed == result);
82+
}
83+
84+
public static IEnumerable<object[]> ReservedNamespaceAction = new object[][]
85+
{
86+
new object[]{ (Func<ActionRequiringReservedNamespacePermissions>)(() => ActionsRequiringPermissions.RemovePackageFromReservedNamespace) },
87+
};
88+
89+
public static IEnumerable<object[]> ReservedNamespaceActionWithAdmin =>
90+
from actionProvider in ReservedNamespaceAction
91+
from isAdmin in new[] { false, true }
92+
select new object[] { actionProvider[0], isAdmin };
93+
94+
[Theory]
95+
[MemberData(nameof(ReservedNamespaceActionWithAdmin))]
96+
public void SatisfiedDependingOnAdminEnabledForReservedNamespaces(
97+
Func<ActionRequiringReservedNamespacePermissions> actionProvider,
98+
bool isAdmin)
99+
{
100+
ActionsRequiringPermissions.AdminAccessEnabled = isAdmin;
101+
102+
var user = new User("testuser" + _key) { Key = _key++ };
103+
user.Roles.Add(new Role { Name = Constants.AdminRoleName });
104+
105+
var target = new User("testuser" + _key) { Key = _key++ };
106+
var reservedNamespace = new ReservedNamespace("Prefix", false, false);
107+
reservedNamespace.Owners = new[] { target };
108+
109+
var action = actionProvider();
110+
var result = action.CheckPermissions(user, target, reservedNamespace);
111+
Assert.Equal(isAdmin, PermissionsCheckResult.Allowed == result);
112+
}
113+
114+
[Theory]
115+
[MemberData(nameof(ReservedNamespaceActionWithAdmin))]
116+
public void SatisfiedDependingOnAdminEnabledForReservedNamespacesAnyAccount(
117+
Func<ActionRequiringReservedNamespacePermissions> actionProvider,
118+
bool isAdmin)
119+
{
120+
ActionsRequiringPermissions.AdminAccessEnabled = isAdmin;
121+
122+
var user = new User("testuser" + _key) { Key = _key++ };
123+
user.Roles.Add(new Role { Name = Constants.AdminRoleName });
124+
125+
var target = new User("testuser" + _key) { Key = _key++ };
126+
var reservedNamespace = new ReservedNamespace("Prefix", false, false);
127+
reservedNamespace.Owners = new[] { target };
128+
129+
var action = actionProvider();
130+
var result = action.CheckPermissionsOnBehalfOfAnyAccount(user, reservedNamespace);
131+
Assert.Equal(isAdmin, PermissionsCheckResult.Allowed == result);
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)