Compare commits

...

6 commits

Author SHA1 Message Date
Jiří Vrabec
8058add053 Rename class 2025-08-11 07:46:14 +02:00
Martin Velebil
a246764e44 Movesadmin status update endpoint
Relocates the endpoint for updating user admin status from the dedicated AdminController to the UserProfileController.

This consolidates user profile management under a single controller
and leverages existing authorization policies.
2025-08-10 18:33:36 +02:00
Martin Velebil
3230a5ed0f Merge remote-tracking branch 'origin/250809_UserProfile' into 250810_UserProfileAdmin
# Conflicts:
#	DrinkRateAPI/Services/UserProfileService.cs
2025-08-10 18:18:06 +02:00
b59fef222f 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.
2025-08-10 18:07:34 +02:00
b2b8d1e076 Refactor admin endpoint and logic
Moves the ChangeUserAdminStatusRequest to the ApiModels folder.
Updates the admin controller route to "admin" and the admin status
endpoint to "adminStatus".
Makes the ChangeUserAdminStatus method asynchronous.
Uses NotFoundException instead of KeyNotFoundException.
2025-08-10 16:23:35 +02:00
c0860b05d1 Enable admin status management
Adds functionality to allow administrators to modify the admin status of other users.

Introduces an endpoint for changing user admin status, accessible only to existing administrators.
This change includes necessary services and request models to handle the logic.
2025-08-10 13:55:20 +02:00
6 changed files with 126 additions and 18 deletions

View file

@ -0,0 +1,6 @@
namespace DrinkRateAPI.ApiModels.UserProfile;
public class UserProfileAdminStatusPut
{
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,18 +1,38 @@
using System.Security.Claims;
using DrinkRateAPI.ApiModels.UserProfile;
using DrinkRateAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DrinkRateAPI.Controllers;
[ApiController]
[Route("user_profile")]
[Route("userProfile")]
public class UserProfileController : ControllerBase
{
[HttpPut(Name = "user_profile")]
public UserProfileGet PutUserProfile(UserProfilePut userProfile)
private readonly ILogger<UserProfileController> _logger;
private readonly UserProfileService _userProfileService;
public UserProfileController(ILogger<UserProfileController> logger, UserProfileService userProfileService)
{
_logger = logger;
_userProfileService = userProfileService;
}
public UserProfileGet PutUserProfile([FromBody] UserProfilePut userProfile)
{
throw new ApplicationException();
var x = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; //HttpContext.User.Identities.First();
return new();
}
[HttpPut("{userId}/adminStatus")]
[Authorize(Policy = "AdminOnly")]
[Produces("application/json")]
public async Task<IActionResult> PutUserAdminStatus(string userId, [FromBody] UserProfileAdminStatusPut body)
{
var changedProfile = await _userProfileService.PutUserProfileAdminStatusAsync(userId, body.ChangeStatusTo);
return Ok(changedProfile);
}
}

View file

@ -20,6 +20,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="ApiModels\" />
<Folder Include="Migrations\" />
</ItemGroup>

View file

@ -1,5 +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;
@ -10,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 =>
{
@ -31,25 +37,26 @@ 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>()
}
});
});
builder.Services.AddDbContext<ApplicationDbContext>();
builder.Services.AddScoped<ApplicationUserService>();
builder.Services.AddScoped<UserProfileService>();
var app = builder.Build();
@ -69,4 +76,4 @@ app.UseAuthorization();
app.MapControllers();
app.Run();
app.Run();

View file

@ -1,18 +1,36 @@
using System.Security.Claims;
using DrinkRateAPI.ApiModels.UserProfile;
using DrinkRateAPI.Contexts;
using DrinkRateAPI.DbEntities;
using DrinkRateAPI.DbEntities;
using DrinkRateAPI.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DrinkRateAPI.Services;
public class UserProfileService(ApplicationDbContext context,
ApplicationUserService applicationUserService)
public class UserProfileService(ApplicationDbContext context, ApplicationUserService applicationUserService)
{
private ApplicationDbContext _context = context;
private ApplicationUserService _applicationUserService = applicationUserService;
public bool IsUserProfileAdmin(DbUserProfile userProfile)
{
return userProfile.IsAdmin;
}
public async Task<DbUserProfile> PutUserProfileAdminStatusAsync(string userId, bool changeStatusTo)
{
var userProfile = GetUserProfileById(userId);
userProfile.IsAdmin = changeStatusTo;
_context.UserProfiles.Update(userProfile);
await _context.SaveChangesAsync();
return userProfile;
}
public async Task<UserProfileGet> PutUserProfileAsync(UserProfilePut userProfile, ClaimsPrincipal identity)
{
var profile = _applicationUserService.UserProfileByApplicationUserAsync(identity);
@ -21,4 +39,11 @@ public class UserProfileService(ApplicationDbContext context,
return new();
}
public DbUserProfile GetUserProfileById(string userId)
{
var userProfile = _context.UserProfiles.FirstOrDefault(x => x.Id.ToString() == userId);
return userProfile ?? throw new NotFoundException();
}
}