Skip to content

Commit f7d3468

Browse files
merge pull request #7 from https-richardy/fix/06-prevent-permission-privilege-escalation
prevent permission privilege escalation
2 parents cbaf06e + 191a89d commit f7d3468

17 files changed

Lines changed: 250 additions & 64 deletions

File tree

Source/HttpsRichardy.Federation.Application/Handlers/Permission/PermissionCreationHandler.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
namespace HttpsRichardy.Federation.Application.Handlers.Permission;
22

3-
public sealed class PermissionCreationHandler(IPermissionCollection collection, IRealmProvider realmProvider) :
3+
public sealed class PermissionCreationHandler(IPermissionCollection collection, IPermissionNamespacePolicy policy, IRealmProvider realmProvider) :
44
IDispatchHandler<PermissionCreationScheme, Result<PermissionDetailsScheme>>
55
{
66
public async Task<Result<PermissionDetailsScheme>> HandleAsync(PermissionCreationScheme parameters, CancellationToken cancellation = default)
77
{
88
var realm = realmProvider.GetCurrentRealm();
9+
var result = await policy.EnsurePermissionIsAllowedAsync(realm, new() { Name = parameters.Name }, cancellation);
10+
11+
if (result.IsFailure)
12+
{
13+
return Result<PermissionDetailsScheme>.Failure(PermissionErrors.PermissionNameIsReserved);
14+
}
15+
916
var filters = PermissionFilters.WithSpecifications()
1017
.WithName(parameters.Name)
1118
.Build();
@@ -23,4 +30,4 @@ public async Task<Result<PermissionDetailsScheme>> HandleAsync(PermissionCreatio
2330

2431
return Result<PermissionDetailsScheme>.Success(PermissionMapper.AsResponse(createdPermission));
2532
}
26-
}
33+
}

Source/HttpsRichardy.Federation.Application/Handlers/Permission/PermissionUpdateHandler.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace HttpsRichardy.Federation.Application.Handlers.Permission;
22

3-
public sealed class PermissionUpdateHandler(IPermissionCollection collection, IRealmProvider realmProvider) :
3+
public sealed class PermissionUpdateHandler(IPermissionCollection collection, IPermissionNamespacePolicy policy, IRealmProvider realmProvider) :
44
IDispatchHandler<PermissionUpdateScheme, Result<PermissionDetailsScheme>>
55
{
66
public async Task<Result<PermissionDetailsScheme>> HandleAsync(PermissionUpdateScheme parameters, CancellationToken cancellation = default)
@@ -18,10 +18,16 @@ public async Task<Result<PermissionDetailsScheme>> HandleAsync(PermissionUpdateS
1818
return Result<PermissionDetailsScheme>.Failure(PermissionErrors.PermissionDoesNotExist);
1919
}
2020

21+
var result = await policy.EnsurePermissionIsAllowedAsync(realm, new() { Name = parameters.Name }, cancellation);
22+
if (result.IsFailure)
23+
{
24+
return Result<PermissionDetailsScheme>.Failure(PermissionErrors.PermissionNameIsReserved);
25+
}
26+
2127
permission = PermissionMapper.AsPermission(parameters, permission, realm);
2228

2329
var updatedPermission = await collection.UpdateAsync(permission, cancellation: cancellation);
2430

2531
return Result<PermissionDetailsScheme>.Success(PermissionMapper.AsResponse(updatedPermission));
2632
}
27-
}
33+
}

Source/HttpsRichardy.Federation.Application/Handlers/Realm/RealmCreationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public async Task<Result<RealmDetailsScheme>> HandleAsync(
3131
var defaultRealm = matchingRealms.FirstOrDefault()!;
3232

3333
realm.Permissions = defaultRealm.Permissions
34-
.Where(permission => DefaultRealmPermissions.InitialPermissions.Contains(permission.Name))
34+
.Where(permission => RealmPermissions.InitialPermissions.Contains(permission.Name))
3535
.ToList();
3636

3737
await collection.InsertAsync(realm, cancellation: cancellation);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace HttpsRichardy.Federation.Application.Policies;
2+
3+
public sealed class PermissionNamespacePolicy : IPermissionNamespacePolicy
4+
{
5+
public async Task<Result> EnsurePermissionIsAllowedAsync(
6+
Realm realm, Permission permission, CancellationToken cancellation = default)
7+
{
8+
var isReserved = RealmPermissions.SystemPermissions
9+
.Contains(permission.Name);
10+
11+
return isReserved
12+
? Result.Failure(PermissionErrors.PermissionNameIsReserved)
13+
: Result.Success();
14+
}
15+
}

Source/HttpsRichardy.Federation.Application/Usings.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
global using HttpsRichardy.Federation.Application.Providers;
3131
global using HttpsRichardy.Federation.Application.Mappers;
3232
global using HttpsRichardy.Federation.Application.Utilities;
33-
global using HttpsRichardy.Federation.Application.Handlers.Authorization;
3433

3534
global using FluentValidation;
3635
global using HttpsRichardy.Dispatcher.Contracts;

Source/HttpsRichardy.Federation.Common/Constants/DefaultRealmPermissions.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace HttpsRichardy.Federation.Common.Constants;
2+
3+
public static class RealmPermissions
4+
{
5+
public static readonly HashSet<string> InitialPermissions =
6+
[
7+
Permissions.CreateGroup,
8+
Permissions.DeleteGroup,
9+
Permissions.ViewGroups,
10+
Permissions.EditGroup,
11+
12+
Permissions.DeleteUser,
13+
Permissions.EditUser,
14+
Permissions.ViewUsers,
15+
16+
Permissions.CreatePermission,
17+
Permissions.AssignPermissions,
18+
Permissions.RevokePermissions,
19+
Permissions.ViewPermissions,
20+
Permissions.EditPermission,
21+
Permissions.DeletePermission,
22+
23+
Permissions.CreateScope,
24+
Permissions.EditScope,
25+
Permissions.DeleteGroup,
26+
Permissions.ViewScopes
27+
];
28+
29+
public static readonly HashSet<string> SystemPermissions =
30+
[
31+
Permissions.CreateGroup,
32+
Permissions.DeleteGroup,
33+
Permissions.EditGroup,
34+
Permissions.ViewGroups,
35+
36+
Permissions.DeleteUser,
37+
Permissions.EditUser,
38+
Permissions.ViewUsers,
39+
40+
Permissions.CreatePermission,
41+
Permissions.AssignPermissions,
42+
Permissions.RevokePermissions,
43+
Permissions.ViewPermissions,
44+
Permissions.EditPermission,
45+
Permissions.DeletePermission,
46+
47+
Permissions.CreateRealm,
48+
Permissions.DeleteRealm,
49+
Permissions.EditRealm,
50+
Permissions.ViewRealms,
51+
52+
Permissions.CreateScope,
53+
Permissions.EditScope,
54+
Permissions.DeleteScope,
55+
Permissions.ViewScopes
56+
];
57+
}

Source/HttpsRichardy.Federation.Domain/Errors/PermissionErrors.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ public static class PermissionErrors
77
Description: "The permission with the specified name already exists."
88
);
99

10+
public static readonly Error PermissionNameIsReserved = new(
11+
Code: "#ERROR-7B1E2",
12+
Description: "The permission name is reserved by the system."
13+
);
14+
1015
public static readonly Error PermissionDoesNotExist = new(
1116
Code: "#ERROR-93697",
1217
Description: "The specified permission does not exist."
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace HttpsRichardy.Federation.Domain.Policies;
2+
3+
// defines a policy responsible for protecting the system permission namespace
4+
// from unauthorized usage by realms
5+
6+
// certain permissions are reserved by the federation system and represent
7+
// privileged administrative capabilities (e.g. managing realms or federation resources)
8+
9+
// this policy ensures that realms cannot create or manipulate permissions
10+
// whose identifiers belong to the reserved system namespace, preventing
11+
// privilege escalation through permission name collision
12+
13+
public interface IPermissionNamespacePolicy
14+
{
15+
public Task<Result> EnsurePermissionIsAllowedAsync(
16+
Realm realm,
17+
Permission permission,
18+
CancellationToken cancellation = default
19+
);
20+
}

Source/HttpsRichardy.Federation.Infrastructure.IoC/Extensions/ApplicationServicesExtension.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public static void AddServices(this IServiceCollection services)
99
services.AddTransient<IAuthenticationService, AuthenticationService>();
1010
services.AddTransient<ISecurityTokenService, JwtSecurityTokenService>();
1111
services.AddTransient<IClientCredentialsGenerator, ClientCredentialsGenerator>();
12+
1213
services.AddTransient<IRedirectUriPolicy, RedirectUriPolicy>();
14+
services.AddTransient<IPermissionNamespacePolicy, PermissionNamespacePolicy>();
1315

1416
services.AddTransient<IAuthorizationFlowHandler, ClientCredentialsGrantHandler>();
1517
services.AddTransient<IAuthorizationFlowHandler, AuthorizationCodeGrantHandler>();

0 commit comments

Comments
 (0)