line-gestao-api/Controllers/SystemTenantUsersController.cs

227 lines
7.8 KiB
C#

using line_gestao_api.Data;
using line_gestao_api.Dtos;
using line_gestao_api.Models;
using line_gestao_api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Controllers;
[ApiController]
[Route("api/system/tenants/{tenantId:guid}/users")]
[Authorize(Policy = "SystemAdmin")]
public class SystemTenantUsersController : ControllerBase
{
private readonly AppDbContext _db;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole<Guid>> _roleManager;
private readonly ISystemAuditService _systemAuditService;
public SystemTenantUsersController(
AppDbContext db,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole<Guid>> roleManager,
ISystemAuditService systemAuditService)
{
_db = db;
_userManager = userManager;
_roleManager = roleManager;
_systemAuditService = systemAuditService;
}
[HttpPost]
public async Task<ActionResult<SystemTenantUserCreatedDto>> CreateUserForTenant(
[FromRoute] Guid tenantId,
[FromBody] CreateSystemTenantUserRequest request)
{
if (tenantId == Guid.Empty)
{
return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "TenantId inválido.", "invalid_tenant_id");
}
var tenant = await _db.Tenants.AsNoTracking().FirstOrDefaultAsync(t => t.Id == tenantId);
if (tenant == null)
{
return await RejectAsync(tenantId, StatusCodes.Status404NotFound, "Tenant não encontrado.", "tenant_not_found");
}
if (string.IsNullOrWhiteSpace(request.Email))
{
return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Email é obrigatório.", "missing_email");
}
if (string.IsNullOrWhiteSpace(request.Password))
{
return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Password é obrigatória.", "missing_password");
}
if (request.Roles == null || request.Roles.Count == 0)
{
return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Informe ao menos uma role.", "missing_roles");
}
var normalizedRoles = request.Roles
.Where(r => !string.IsNullOrWhiteSpace(r))
.Select(r => r.Trim().ToLowerInvariant())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (normalizedRoles.Count == 0)
{
return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Roles inválidas.", "invalid_roles");
}
var unsupportedRoles = normalizedRoles
.Where(role => !AppRoles.All.Contains(role, StringComparer.OrdinalIgnoreCase))
.ToList();
if (unsupportedRoles.Count > 0)
{
return await RejectAsync(
tenantId,
StatusCodes.Status400BadRequest,
$"Roles não suportadas: {string.Join(", ", unsupportedRoles)}. Use apenas: {string.Join(", ", AppRoles.All)}.",
"unsupported_roles");
}
if (!tenant.IsSystem && normalizedRoles.Contains(SystemTenantConstants.SystemRole))
{
return await RejectAsync(
tenantId,
StatusCodes.Status400BadRequest,
"A role sysadmin só pode ser usada no SystemTenant.",
"invalid_sysadmin_outside_system_tenant");
}
if (tenant.IsSystem && normalizedRoles.Any(r => r != SystemTenantConstants.SystemRole))
{
return await RejectAsync(
tenantId,
StatusCodes.Status400BadRequest,
"No SystemTenant é permitido apenas a role sysadmin.",
"invalid_non_system_role_for_system_tenant");
}
foreach (var role in normalizedRoles)
{
if (!await _roleManager.RoleExistsAsync(role))
{
return await RejectAsync(
tenantId,
StatusCodes.Status400BadRequest,
$"Role inexistente: {role}",
"role_not_found");
}
}
var email = request.Email.Trim().ToLowerInvariant();
var normalizedEmail = _userManager.NormalizeEmail(email);
var alreadyExists = await _userManager.Users
.IgnoreQueryFilters()
.AnyAsync(u => u.TenantId == tenantId && u.NormalizedEmail == normalizedEmail);
if (alreadyExists)
{
return await RejectAsync(tenantId, StatusCodes.Status409Conflict, "Já existe usuário com este email neste tenant.", "email_exists");
}
var name = string.IsNullOrWhiteSpace(request.Name)
? email
: request.Name.Trim();
var user = new ApplicationUser
{
Name = name,
Email = email,
UserName = email,
TenantId = tenantId,
EmailConfirmed = true,
IsActive = true,
LockoutEnabled = true
};
IdentityResult createResult;
try
{
createResult = await _userManager.CreateAsync(user, request.Password);
}
catch (DbUpdateException)
{
return await RejectAsync(
tenantId,
StatusCodes.Status409Conflict,
"Não foi possível criar usuário. Email/username já em uso.",
"db_conflict");
}
if (!createResult.Succeeded)
{
await _systemAuditService.LogAsync(
action: SystemAuditActions.CreateTenantUserRejected,
targetTenantId: tenantId,
metadata: new
{
reason = "identity_create_failed",
email,
errors = createResult.Errors.Select(e => e.Description).ToList()
});
return BadRequest(createResult.Errors.Select(e => e.Description).ToList());
}
var addRolesResult = await _userManager.AddToRolesAsync(user, normalizedRoles);
if (!addRolesResult.Succeeded)
{
await _userManager.DeleteAsync(user);
await _systemAuditService.LogAsync(
action: SystemAuditActions.CreateTenantUserRejected,
targetTenantId: tenantId,
metadata: new
{
reason = "identity_add_roles_failed",
email,
roles = normalizedRoles,
errors = addRolesResult.Errors.Select(e => e.Description).ToList()
});
return BadRequest(addRolesResult.Errors.Select(e => e.Description).ToList());
}
await _systemAuditService.LogAsync(
action: SystemAuditActions.CreateTenantUser,
targetTenantId: tenantId,
metadata: new
{
createdUserId = user.Id,
email,
roles = normalizedRoles
});
var response = new SystemTenantUserCreatedDto
{
UserId = user.Id,
TenantId = tenantId,
Email = email,
Roles = normalizedRoles
};
return StatusCode(StatusCodes.Status201Created, response);
}
private async Task<ActionResult<SystemTenantUserCreatedDto>> RejectAsync(
Guid targetTenantId,
int statusCode,
string message,
string reason)
{
await _systemAuditService.LogAsync(
action: SystemAuditActions.CreateTenantUserRejected,
targetTenantId: targetTenantId,
metadata: new { reason, message });
return StatusCode(statusCode, message);
}
}