Protect distribution APIs with permissions and scope checks
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/orders")]
|
||||
[Authorize]
|
||||
public class OrderController(OrderService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "orders.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "orders.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,17 +22,23 @@ public class OrderController(OrderService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("store/{storeId:guid}")]
|
||||
public async Task<IActionResult> GetByStore(Guid storeId) =>
|
||||
Ok(await service.GetByStoreIdAsync(storeId));
|
||||
[Authorize(Policy = "orders.view")]
|
||||
public async Task<IActionResult> GetByStore(Guid storeId)
|
||||
{
|
||||
var result = await service.GetByStoreIdAsync(storeId);
|
||||
return result is null ? Forbid() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "orders.create")]
|
||||
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
return result is null ? Forbid() : CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "orders.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -36,6 +46,7 @@ public class OrderController(OrderService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "orders.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
using Distribution.Infra.Auth;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
public class OrderService(IOrderRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
|
||||
private static OrderResponse ToResponse(Models.Order o, List<Models.OrderItem> items) =>
|
||||
new(o.Id, o.OrderDate, o.FK_StoreId, o.TotalAmount,
|
||||
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity)).ToList(),
|
||||
o.IsActive, o.CreatedAt);
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
|
||||
private async Task<bool> HasStoreScopeAsync(Guid storeId)
|
||||
{
|
||||
if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope;
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>> GetAllAsync()
|
||||
{
|
||||
var orders = (await repo.GetAllAsync()).Where(o => o.IsActive).ToList();
|
||||
if (!HasGlobalScope && currentUser.UserId is not null)
|
||||
{
|
||||
var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet();
|
||||
orders = orders.Where(o => accessibleStoreIds.Contains(o.FK_StoreId)).ToList();
|
||||
}
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
@@ -25,12 +41,14 @@ public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return null;
|
||||
var items = await repo.GetItemsByOrderIdAsync(id);
|
||||
return ToResponse(order, items);
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>> GetByStoreIdAsync(Guid storeId)
|
||||
public async Task<List<OrderResponse>?> GetByStoreIdAsync(Guid storeId)
|
||||
{
|
||||
if (!await HasStoreScopeAsync(storeId)) return null;
|
||||
var orders = await repo.GetByStoreIdAsync(storeId);
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
@@ -41,8 +59,10 @@ public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OrderResponse> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
|
||||
public async Task<OrderResponse?> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (!await HasStoreScopeAsync(request.StoreId)) return null;
|
||||
|
||||
var order = new Models.Order
|
||||
{
|
||||
FK_StoreId = request.StoreId,
|
||||
@@ -75,6 +95,7 @@ public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return null;
|
||||
order.OrderDate = request.OrderDate;
|
||||
order.TotalAmount = request.TotalAmount;
|
||||
await uow.BeginAsync(ct);
|
||||
@@ -96,6 +117,7 @@ public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return false;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/regions")]
|
||||
[Authorize]
|
||||
public class RegionController(RegionService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "regions.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "regions.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +22,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "regions.create")]
|
||||
public async Task<IActionResult> Create(CreateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +30,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "regions.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +38,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "regions.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/routes")]
|
||||
[Authorize]
|
||||
public class RouteController(RouteService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "routes.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "routes.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +22,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "routes.create")]
|
||||
public async Task<IActionResult> Create(CreateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +30,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "routes.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +38,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "routes.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/stores")]
|
||||
[Authorize]
|
||||
public class StoreController(StoreService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +22,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "stores.create")]
|
||||
public async Task<IActionResult> Create(CreateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +30,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "stores.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +38,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "stores.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
using Distribution.Infra.Auth;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow)
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
|
||||
private static StoreResponse ToResponse(Models.Store s) =>
|
||||
new(s.Id, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount, s.IsActive, s.CreatedAt);
|
||||
|
||||
public async Task<List<StoreResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(s => s.IsActive)
|
||||
.Select(ToResponse)
|
||||
.ToList();
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
|
||||
private async Task<bool> HasStoreScopeAsync(Guid storeId)
|
||||
{
|
||||
if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope;
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
public async Task<List<StoreResponse>> GetAllAsync()
|
||||
{
|
||||
var stores = (await repo.GetAllAsync()).Where(s => s.IsActive).ToList();
|
||||
if (!HasGlobalScope && currentUser.UserId is not null)
|
||||
{
|
||||
var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet();
|
||||
stores = stores.Where(s => accessibleStoreIds.Contains(s.Id)).ToList();
|
||||
}
|
||||
return stores.Select(ToResponse).ToList();
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
@@ -50,6 +67,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
store.OwnerName = request.OwnerName;
|
||||
store.StoreName = request.StoreName;
|
||||
store.StoreAddress = request.StoreAddress;
|
||||
@@ -75,6 +93,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return false;
|
||||
if (!await HasStoreScopeAsync(id)) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/vehicles")]
|
||||
[Authorize]
|
||||
public class VehicleController(VehicleService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "vehicles.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "vehicles.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +22,7 @@ public class VehicleController(VehicleService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "vehicles.create")]
|
||||
public async Task<IActionResult> Create(CreateVehicleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +30,7 @@ public class VehicleController(VehicleService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "vehicles.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateVehicleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +38,7 @@ public class VehicleController(VehicleService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "vehicles.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/warehouses")]
|
||||
[Authorize]
|
||||
public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "warehouses.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "warehouses.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +22,7 @@ public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "warehouses.create")]
|
||||
public async Task<IActionResult> Create(CreateWarehouseRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +30,7 @@ public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "warehouses.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateWarehouseRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +38,7 @@ public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "warehouses.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
Reference in New Issue
Block a user