Implement admin-only authorization policy

Adds an authorization policy to restrict access to admin-only endpoints.
Creates an `AdminOnlyRequirement` and `AdminOnlyHandler` to check if a user has admin privileges.
Applies the "AdminOnly" policy to the AdminController to secure admin functionalities.
Modifies the endpoint for changing user admin status to include the user ID in the route.
This commit is contained in:
martinshoob 2025-08-10 18:07:34 +02:00
parent b2b8d1e076
commit b59fef222f
4 changed files with 74 additions and 30 deletions

View file

@ -1,7 +1,6 @@
namespace DrinkRateAPI.ApiModels.UserProfile;
public class ChangeUserAdminStatusRequest
public class ChangeAdminStatusBody
{
public required string UserId { get; set; }
public bool ChangeStatusTo { get; set; }
}

View file

@ -0,0 +1,49 @@
using DrinkRateAPI.DbEntities;
using DrinkRateAPI.Services;
namespace DrinkRateAPI.AuthorizationPolicies;
using Microsoft.AspNetCore.Authorization;
public class AdminOnlyRequirement : IAuthorizationRequirement
{
}
public class AdminOnlyHandler : AuthorizationHandler<AdminOnlyRequirement>
{
private readonly ApplicationUserService _applicationUserService;
private readonly UserProfileService _userProfileService;
public AdminOnlyHandler(
ApplicationUserService applicationUserService,
UserProfileService userProfileService)
{
_applicationUserService = applicationUserService;
_userProfileService = userProfileService;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AdminOnlyRequirement requirement)
{
DbUserProfile userProfile;
try
{
userProfile = await _applicationUserService.UserProfileByApplicationUserAsync(context.User);
}
catch (Exception _)
{
context.Fail();
return;
}
if (_userProfileService.IsUserProfileAdmin(userProfile))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}

View file

@ -1,38 +1,30 @@
using DrinkRateAPI.ApiModels.UserProfile;
using DrinkRateAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DrinkRateAPI.Controllers;
[ApiController]
[Route("admin")]
[Authorize(Policy = "AdminOnly")]
public class AdminController : ControllerBase
{
private readonly ILogger<AdminController> _logger;
private readonly ApplicationUserService _applicationUserService;
private readonly UserProfileService _userProfileService;
public AdminController(ILogger<AdminController> logger, ApplicationUserService applicationUserService,
UserProfileService userProfileService)
public AdminController(ILogger<AdminController> logger, UserProfileService userProfileService)
{
_logger = logger;
_applicationUserService = applicationUserService;
_userProfileService = userProfileService;
}
[HttpPut]
[Route("adminStatus")]
[HttpPut("users/{userId}/adminStatus")]
[Produces("application/json")]
public async Task<IActionResult> PutUserAdminStatus([FromBody] ChangeUserAdminStatusRequest request)
public async Task<IActionResult> PutUserAdminStatus(string userId, [FromBody] ChangeAdminStatusBody body)
{
var userProfile = await _applicationUserService.UserProfileByApplicationUserAsync(User);
if (!_userProfileService.IsUserProfileAdmin(userProfile))
{
return Unauthorized();
}
var changedProfile = await _userProfileService.ChangeUserAdminStatusAsync(userId, body.ChangeStatusTo);
var changedProfile = await _userProfileService.ChangeUserAdminStatusAsync(request.UserId, request.ChangeStatusTo);
return Ok(changedProfile);
}
}
}

View file

@ -1,6 +1,8 @@
using DrinkRateAPI.AuthorizationPolicies;
using DrinkRateAPI.Contexts;
using DrinkRateAPI.DbEntities;
using DrinkRateAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.OpenApi.Models;
@ -11,10 +13,13 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthorization();
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy =>
policy.Requirements.Add(new AdminOnlyRequirement()));
builder.Services.AddIdentityApiEndpoints<DbApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddScoped<UserManager<DbApplicationUser>, UserWithProfileManager>();
builder.Services.AddScoped<IAuthorizationHandler, AdminOnlyHandler>();
builder.Services.AddSwaggerGen(c =>
{
@ -32,20 +37,19 @@ builder.Services.AddSwaggerGen(c =>
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
new OpenApiSecurityScheme
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
new List<string>()
}
});
});
@ -72,4 +76,4 @@ app.UseAuthorization();
app.MapControllers();
app.Run();
app.Run();