Skip to content

Commit 7c49a0d

Browse files
committed
Add permission message service and integrate with authorization
Introduce `PermissionMessageService` to handle custom permission messages per resource and HTTP method. Updated `CustomAuthorizationMiddlewareResultHandler` to use the new service for more specific error feedback. Registered the service in the application builder to streamline permission handling.
1 parent cdefb35 commit 7c49a0d

File tree

4 files changed

+217
-84
lines changed

4 files changed

+217
-84
lines changed

Program.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using AdminHubApi.Interfaces;
77
using AdminHubApi.Repositories;
88
using AdminHubApi.Security;
9+
using AdminHubApi.Security.Permissions;
910
using AdminHubApi.Services;
1011
using Microsoft.AspNetCore.Authentication.JwtBearer;
1112
using Microsoft.AspNetCore.Authorization;
@@ -143,7 +144,10 @@ await tokenBlacklistRepository.IsTokenBlacklistedAsync(tokenId))
143144
// Register token cleanup background service
144145
builder.Services.AddHostedService<TokenCleanupService>();
145146

146-
// Custom Authorization Handler
147+
// Add permission message service
148+
builder.Services.AddSingleton<IPermissionMessageService, PermissionMessageService>();
149+
150+
// Register the custom authorization handler
147151
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationMiddlewareResultHandler>();
148152

149153
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
@@ -186,12 +190,12 @@ await tokenBlacklistRepository.IsTokenBlacklistedAsync(tokenId))
186190
await NormalUserSeeder.SeedNormalUserAsync(app.Services);
187191
await ManagerUserSeeder.SeedManagerUserAsync(app.Services);
188192
}
189-
193+
190194
// Always update permissions to ensure new permissions are added
191195
logger.LogInformation("Updating role permissions...");
192196
await PermissionUpdateSeeder.UpdateRolePermissionsAsync(app.Services);
193197
logger.LogInformation("Role permissions updated successfully");
194-
198+
195199
logger.LogInformation("Updating user permissions...");
196200
await UserPermissionUpdateSeeder.UpdateUserPermissionsAsync(app.Services);
197201
logger.LogInformation("User permissions updated successfully");
Lines changed: 64 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,87 @@
11
using System.Text.Json;
22
using AdminHubApi.Dtos.ApiResponse;
3+
using AdminHubApi.Security.Permissions;
34
using Microsoft.AspNetCore.Authorization;
45
using Microsoft.AspNetCore.Authorization.Policy;
56

6-
namespace AdminHubApi.Security
7+
namespace AdminHubApi.Security;
8+
9+
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
710
{
8-
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
11+
private readonly AuthorizationMiddlewareResultHandler _defaultHandler =
12+
new AuthorizationMiddlewareResultHandler();
13+
14+
private readonly IPermissionMessageService _permissionMessageService;
15+
16+
public CustomAuthorizationMiddlewareResultHandler(IPermissionMessageService permissionMessageService)
917
{
10-
private readonly AuthorizationMiddlewareResultHandler _defaultHandler =
11-
new AuthorizationMiddlewareResultHandler();
12-
13-
public async Task HandleAsync(
14-
RequestDelegate next,
15-
HttpContext context,
16-
AuthorizationPolicy policy,
17-
PolicyAuthorizationResult authorizeResult)
18+
_permissionMessageService = permissionMessageService;
19+
}
20+
21+
public async Task HandleAsync(
22+
RequestDelegate next,
23+
HttpContext context,
24+
AuthorizationPolicy policy,
25+
PolicyAuthorizationResult authorizeResult)
26+
{
27+
// If the authorization was successful, continue down the pipeline
28+
if (authorizeResult.Succeeded)
1829
{
19-
// If the authorization was successful, continue down the pipeline
20-
if (authorizeResult.Succeeded)
21-
{
22-
await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
30+
await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
2331

24-
return;
25-
}
32+
return;
33+
}
2634

27-
// Check if unauthorized or forbidden
28-
if (context.User.Identity?.IsAuthenticated != true)
29-
{
30-
// Return a 401 Unauthorized with a descriptive message
31-
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
35+
// Check if unauthorized or forbidden
36+
if (context.User.Identity?.IsAuthenticated != true)
37+
{
38+
// Return a 401 Unauthorized with a descriptive message
39+
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
3240

33-
var response = new ApiResponse<object>
34-
{
35-
Succeeded = false,
36-
Message = "Authentication required",
37-
Errors = new List<string>()
38-
{
39-
"You need to authenticate to access this resource."
40-
}
41-
};
42-
43-
await WriteJsonResponseAsync(context, response);
44-
}
45-
else
41+
var response = new ApiResponse<object>
4642
{
47-
// Return a 403 Forbidden with a descriptive message
48-
context.Response.StatusCode = StatusCodes.Status403Forbidden;
49-
50-
// Check if it's related to project permissions
51-
if (context.Request.Path.StartsWithSegments("/api/projects"))
43+
Succeeded = false,
44+
Message = "Authentication required",
45+
Errors = new List<string>()
5246
{
53-
// Customize message based on the HTTP method
54-
string errorMessage = context.Request.Method switch
55-
{
56-
"POST" => "You do not have sufficient permissions to create a project.",
57-
"PUT" => "You do not have sufficient permissions to edit a project.",
58-
"DELETE" => "You do not have sufficient permissions to delete a project.",
59-
_ => "You do not have sufficient permissions to perform this action on projects."
60-
};
61-
62-
var response = new ApiResponse<object>
63-
{
64-
Succeeded = false,
65-
Message = "Permission denied",
66-
Errors = new List<string>()
67-
{
68-
errorMessage
69-
}
70-
};
71-
72-
await WriteJsonResponseAsync(context, response);
47+
"You need to authenticate to access this resource."
7348
}
74-
else
75-
{
76-
// Generic permission error
77-
var response = new ApiResponse<object>
78-
{
79-
Succeeded = false,
80-
Message = "Permission denied",
81-
Errors = new List<string>()
82-
{
83-
"You do not have sufficient permissions to perform this action."
84-
}
85-
};
86-
87-
await WriteJsonResponseAsync(context, response);
88-
}
89-
}
90-
}
49+
};
9150

92-
private static async Task WriteJsonResponseAsync<T>(HttpContext context, ApiResponse<T> response)
51+
await WriteJsonResponseAsync(context, response);
52+
}
53+
else
9354
{
94-
context.Response.ContentType = "application/json";
55+
// Return a 403 Forbidden with a descriptive message
56+
context.Response.StatusCode = StatusCodes.Status403Forbidden;
57+
58+
// Get the path and HTTP method
59+
string path = context.Request.Path.Value ?? "";
60+
string method = context.Request.Method;
61+
62+
// Get a specific error message for the resource and action
63+
string errorMessage = _permissionMessageService.GetPermissionMessage(path, method);
9564

96-
var options = new JsonSerializerOptions
65+
var response = new ApiResponse<object>
9766
{
98-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
67+
Succeeded = false,
68+
Message = "Permission denied",
69+
Errors = new List<string>() { errorMessage }
9970
};
10071

101-
await JsonSerializer.SerializeAsync(context.Response.Body, response, options);
72+
await WriteJsonResponseAsync(context, response);
10273
}
10374
}
75+
76+
private static async Task WriteJsonResponseAsync<T>(HttpContext context, ApiResponse<T> response)
77+
{
78+
context.Response.ContentType = "application/json";
79+
80+
var options = new JsonSerializerOptions
81+
{
82+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
83+
};
84+
85+
await JsonSerializer.SerializeAsync(context.Response.Body, response, options);
86+
}
10487
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
namespace AdminHubApi.Security.Permissions;
2+
3+
public interface IPermissionMessageService
4+
{
5+
string GetPermissionMessage(string path, string httpMethod);
6+
}
7+
8+
public class PermissionMessageService : IPermissionMessageService
9+
{
10+
private readonly List<ResourcePermissionMessages> _resourcePermissions;
11+
12+
public PermissionMessageService()
13+
{
14+
_resourcePermissions = new List<ResourcePermissionMessages>
15+
{
16+
new()
17+
{
18+
Resource = "projects",
19+
Prefix = "/api/projects",
20+
Permissions = new Dictionary<string, HttpMethodPermissions>
21+
{
22+
["*"] = new HttpMethodPermissions
23+
{
24+
Get = "You do not have sufficient permissions to view projects.",
25+
Post = "You do not have sufficient permissions to create a project.",
26+
Put = "You do not have sufficient permissions to edit a project.",
27+
Delete = "You do not have sufficient permissions to delete a project.",
28+
Default = "You do not have sufficient permissions to perform this action on projects."
29+
}
30+
}
31+
},
32+
new()
33+
{
34+
Resource = "products",
35+
Prefix = "/api/products",
36+
Permissions = new Dictionary<string, HttpMethodPermissions>
37+
{
38+
["*"] = new HttpMethodPermissions
39+
{
40+
Get = "You do not have sufficient permissions to view products.",
41+
Post = "You do not have sufficient permissions to create a product.",
42+
Put = "You do not have sufficient permissions to edit a product.",
43+
Delete = "You do not have sufficient permissions to delete a product.",
44+
Default = "You do not have sufficient permissions to perform this action on products."
45+
}
46+
}
47+
},
48+
new()
49+
{
50+
Resource = "product-categories",
51+
Prefix = "/api/product-categories",
52+
Permissions = new Dictionary<string, HttpMethodPermissions>
53+
{
54+
["*"] = new HttpMethodPermissions
55+
{
56+
Get = "You do not have sufficient permissions to view product categories.",
57+
Post = "You do not have sufficient permissions to create a product category.",
58+
Put = "You do not have sufficient permissions to edit a product category.",
59+
Delete = "You do not have sufficient permissions to delete a product category.",
60+
Default = "You do not have sufficient permissions to perform this action on product categories."
61+
}
62+
}
63+
},
64+
new()
65+
{
66+
Resource = "users",
67+
Prefix = "/api/users",
68+
Permissions = new Dictionary<string, HttpMethodPermissions>
69+
{
70+
["*"] = new HttpMethodPermissions
71+
{
72+
Get = "You do not have sufficient permissions to view user information.",
73+
Post = "You do not have sufficient permissions to create a user.",
74+
Put = "You do not have sufficient permissions to edit user information.",
75+
Delete = "You do not have sufficient permissions to delete a user.",
76+
Default = "You do not have sufficient permissions to perform this action on users."
77+
}
78+
}
79+
},
80+
new()
81+
{
82+
Resource = "orders",
83+
Prefix = "/api/orders",
84+
Permissions = new Dictionary<string, HttpMethodPermissions>
85+
{
86+
["*"] = new HttpMethodPermissions
87+
{
88+
Get = "You do not have sufficient permissions to view orders.",
89+
Post = "You do not have sufficient permissions to create an order.",
90+
Put = "You do not have sufficient permissions to modify an order.",
91+
Delete = "You do not have sufficient permissions to cancel an order.",
92+
Default = "You do not have sufficient permissions to perform this action on orders."
93+
}
94+
}
95+
}
96+
// Add more resources as needed
97+
};
98+
}
99+
100+
public string GetPermissionMessage(string path, string httpMethod)
101+
{
102+
// Find the matching resource configuration
103+
var resourceConfig =
104+
_resourcePermissions.FirstOrDefault(r => path.StartsWith(r.Prefix, StringComparison.OrdinalIgnoreCase));
105+
106+
if (resourceConfig == null)
107+
{
108+
return "You do not have sufficient permissions to perform this action.";
109+
}
110+
111+
// Get the specific action configuration (default to "*" if not found)
112+
var actionKey = "*"; // We can later extend this to match specific actions like "/api/projects/{id}"
113+
114+
if (!resourceConfig.Permissions.TryGetValue(actionKey, out var permissions))
115+
{
116+
return "You do not have sufficient permissions to perform this action.";
117+
}
118+
119+
// Return the appropriate message based on HTTP method
120+
return httpMethod.ToUpper() switch
121+
{
122+
"GET" => permissions.Get ?? permissions.Default,
123+
"POST" => permissions.Post ?? permissions.Default,
124+
"PUT" => permissions.Put ?? permissions.Default,
125+
"DELETE" => permissions.Delete ?? permissions.Default,
126+
_ => permissions.Default ?? "You do not have sufficient permissions to perform this action."
127+
};
128+
}
129+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace AdminHubApi.Security.Permissions;
2+
3+
public class ResourcePermissionMessages
4+
{
5+
public string Resource { get; set; }
6+
public string Prefix { get; set; }
7+
public Dictionary<string, HttpMethodPermissions> Permissions { get; set; }
8+
}
9+
10+
public class HttpMethodPermissions
11+
{
12+
public string Get { get; set; }
13+
public string Post { get; set; }
14+
public string Put { get; set; }
15+
public string Delete { get; set; }
16+
public string Default { get; set; }
17+
}

0 commit comments

Comments
 (0)