5 Commits

Author SHA1 Message Date
Samuele Locatelli 0b7b86b655 Merge branch 'feature/UserAdminFromGWMS' into develop 2023-05-26 19:06:36 +02:00
Samuele Locatelli 387bff34e2 Ancora spostamento metodi in classe gest user (cache redis) 2023-05-26 19:05:47 +02:00
Samuele Locatelli aff6a89b73 Inizio sistemazione cache redis x utenti 2023-05-26 18:01:35 +02:00
Samuele Locatelli 335557eb7c UserAdmin :
- Completata inclusione pagina da GWMS
- da completare pulizia parte NavManagerHorizontal
2023-05-26 17:00:41 +02:00
Samuele Locatelli f0405cfbe3 Merge branch 'feature/MoveReportRef' into develop 2023-05-26 12:08:55 +02:00
14 changed files with 1481 additions and 126 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>WebDoorCreator - Egalware</i>
<h4>Version: 0.9.2305.2612</h4>
<h4>Version: 0.9.2305.2619</h4>
<br /> Release note:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
0.9.2305.2612
0.9.2305.2619
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>0.9.2305.2612</version>
<version>0.9.2305.2619</version>
<url>http://nexus.steamware.net/repository/SWS/WDC/stable/WDC.UI.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/WDC/stable/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+3
View File
@@ -49,6 +49,9 @@ namespace WebDoorCreator.Core
public const string rKeyOrderStatus = $"{redisBaseAddr}:Cache:OrderStatus";
public const string rKeyRoles = $"{redisBaseAddr}:Cache:Roles";
public const string rKeyUsers = $"{redisBaseAddr}:Cache:Users";
public const string rKeyUsersAll = $"{redisBaseAddr}:Cache:UsersAll";
public const string rKeyUsersDataId = $"{redisBaseAddr}:Cache:UsersData:Id";
public const string rKeyUsersDataSearch = $"{redisBaseAddr}:Cache:UsersData:Search";
public const string rKeyUsersView = $"{redisBaseAddr}:Cache:UsersView";
public const string rKeyVocLemma = $"{redisBaseAddr}:Cache:VocLemma";
public const string rKeyLanguage = $"{redisBaseAddr}:Cache:Languages";
@@ -84,20 +84,6 @@ namespace WebDoorCreator.Data.Services
await Task.Delay(1);
return dbResult;
}
/// <summary>
/// Update company
/// </summary>
/// <param name="currRec"></param>
/// <returns></returns>
public async Task<bool> UserAddMod(AspNetUsers currRec)
{
var dbResult = await dbController.UserAddMod(currRec);
// elimino cache redis...
RedisValue pattern = new RedisValue($"{Constants.rKeyCompany}");
bool answ = await ExecFlushRedisPattern(pattern);
await Task.Delay(1);
return dbResult;
}
/// <summary>
/// Company list filtered by company id
@@ -2067,6 +2053,21 @@ namespace WebDoorCreator.Data.Services
return dbResult;
}
/// <summary>
/// Update company
/// </summary>
/// <param name="currRec"></param>
/// <returns></returns>
public async Task<bool> UserAddMod(AspNetUsers currRec)
{
var dbResult = await dbController.UserAddMod(currRec);
// elimino cache redis...
RedisValue pattern = new RedisValue($"{Constants.rKeyCompany}");
bool answ = await ExecFlushRedisPattern(pattern);
await Task.Delay(1);
return dbResult;
}
/// <summary>
/// Users roles list
/// </summary>
@@ -2279,12 +2280,6 @@ namespace WebDoorCreator.Data.Services
#endregion Public Methods
#region Protected Fields
protected Random rnd = new Random();
#endregion Protected Fields
#region Protected Properties
protected WebDoorCreator.Data.DDF.Converter currDdfConv { get; set; } = null!;
@@ -2294,11 +2289,8 @@ namespace WebDoorCreator.Data.Services
#region Private Fields
private static IConfiguration _configuration = null!;
private static JsonSerializerSettings? JSSettings;
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly IEmailSender _emailSender;
/// <summary>
@@ -2321,6 +2313,8 @@ namespace WebDoorCreator.Data.Services
/// </summary>
private IDatabase redisDb = null!;
private Random rnd = new Random();
#endregion Private Fields
#region Private Properties
+25
View File
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebDoorCreator.Data.User
{
/// <summary>
/// Classe generalizzaizone identity (user + roles + claims) x gestione semplificata in editing
/// </summary>
public class UserData
{
#region Public Properties
public List<System.Security.Claims.Claim> Claims { get; set; } = new List<System.Security.Claims.Claim>();
public IdentityUser Identity { get; set; } = null!;
public List<string> Roles { get; set; } = new List<string>();
#endregion Public Properties
}
}
@@ -24,6 +24,11 @@
<!--Hiding to non-SuperAdmin users the option to manage the application -->
<AuthorizeView Roles="SuperAdmin" Context="MenuHide">
<Authorized>
<div class="nav-item px-3 d-flex flex-warp align-items-center">
<NavLink class="nav-link" href="UserAdmin">
<span class="bi bi-people pe-2" aria-hidden="true"></span> User management
</NavLink>
</div>
<div class="nav-item px-3 d-flex flex-warp align-items-center">
<NavLink class="nav-link" href="SuperAdmin">
<span class="oi oi-list-rich" aria-hidden="true"></span> General management
@@ -37,6 +37,8 @@ namespace WebDoorCreator.UI.Components.Gen
reportVocLemmas();
}
}
[Inject]
public NavigationManager NavManager { get; set; } = default!;
public List<string>? userClaimsList
{
@@ -229,51 +231,57 @@ namespace WebDoorCreator.UI.Components.Gen
// reset preliminare
listRecord = new List<string>();
listCompanies = new List<CompanyModel>();
if (!string.IsNullOrEmpty(userId) && string.IsNullOrEmpty(userRole))
if (!NavManager.Uri.Contains("UserAdmin"))
{
var user = await _userManager.FindByNameAsync(userId);
var roleList = await _userManager.GetRolesAsync(user);
if (roleList != null)
if (!string.IsNullOrEmpty(userId) && string.IsNullOrEmpty(userRole))
{
var role = roleList.FirstOrDefault();
if (role != null)
{
userRole = role;
}
}
var claimsList = await _userManager.GetClaimsAsync(user);
// FARE!!! rivedere con await WDCUsrServ.UserDataGetById(userId);
if (claimsList != null)
{
if (userRole == "SuperAdmin")
var user = await _userManager.FindByNameAsync(userId);
var roleList = await _userManager.GetRolesAsync(user);
if (roleList != null)
{
listCompanies = await WDCService.CompanyGetByKey(0);
}
else
{
if (claimsList.Count > 1)
var role = roleList.FirstOrDefault();
if (role != null)
{
foreach (var item in claimsList)
{
listRecord.Add(item.Value);
var rawList = await WDCService.CompanyGetByKey(int.Parse(item.Value));
CompanyModel firstComp = rawList.FirstOrDefault() ?? new CompanyModel();
listCompanies.Add(firstComp);
}
}
else if (claimsList.Count == 1)
{
var currClaim = claimsList.FirstOrDefault();
if (currClaim != null)
{
listRecord.Add(currClaim.Value);
listCompanies = await WDCService.CompanyGetByKey(int.Parse(currClaim.Value));
}
compToShow = listCompanies?.FirstOrDefault()!;
userRole = role;
}
}
fixSelCompany();
userClaimsList = listRecord;
var claimsList = await _userManager.GetClaimsAsync(user);
if (claimsList != null)
{
if (userRole == "SuperAdmin")
{
listCompanies = await WDCService.CompanyGetByKey(0);
}
else
{
if (claimsList.Count > 1)
{
foreach (var item in claimsList)
{
listRecord.Add(item.Value);
var rawList = await WDCService.CompanyGetByKey(int.Parse(item.Value));
CompanyModel firstComp = rawList.FirstOrDefault() ?? new CompanyModel();
listCompanies.Add(firstComp);
}
}
else if (claimsList.Count == 1)
{
var currClaim = claimsList.FirstOrDefault();
if (currClaim != null)
{
listRecord.Add(currClaim.Value);
listCompanies = await WDCService.CompanyGetByKey(int.Parse(currClaim.Value));
}
compToShow = listCompanies?.FirstOrDefault()!;
}
}
fixSelCompany();
userClaimsList = listRecord;
}
}
}
}
+74
View File
@@ -0,0 +1,74 @@
namespace WebDoorCreator.UI.Data
{
public class MessageService
{
#region Public Events
public event Action EA_HideSearch = null!;
public event Action EA_SearchUpdated = null!;
public event Action EA_ShowSearch = null!;
#endregion Public Events
#region Public Properties
public string SearchVal
{
get => _searchVal;
set
{
if (_searchVal != value)
{
_searchVal = value;
if (EA_SearchUpdated != null)
{
EA_SearchUpdated?.Invoke();
}
}
}
}
public bool ShowSearch
{
get => _showSearch;
set
{
if (_showSearch != value)
{
_showSearch = value;
if (_showSearch)
{
if (EA_ShowSearch != null)
{
EA_ShowSearch?.Invoke();
}
}
else
{
if (EA_HideSearch != null)
{
EA_HideSearch?.Invoke();
}
}
}
}
}
#endregion Public Properties
#region Private Fields
private bool _showSearch;
#endregion Private Fields
#region Private Properties
private string _searchVal { get; set; } = "";
#endregion Private Properties
}
}
+382 -61
View File
@@ -1,35 +1,61 @@
namespace WebDoorCreator.UI.Data
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System.Diagnostics;
using WebDoorCreator.Core;
using WebDoorCreator.Data.User;
namespace WebDoorCreator.UI.Data
{
public class WDCUserService
{
#region Public Constructors
public WDCUserService(IConnectionMultiplexer redisConnMult, UserManager<IdentityUser> userManager)
{
_userManager = userManager;
// Conf cache
redisConn = redisConnMult;
redisDb = this.redisConn.GetDatabase();
// json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/
JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
// chiudo log
Log.Info("Avviata classe WDCUserService");
}
#endregion Public Constructors
#region Public Events
public event Action EA_UserId = null!;
public event Action EA_UserClaims = null!;
public event Action EA_UserRole = null!;
public event Action EA_UserCurrCompany = null!;
public event Action EA_CurrLanguage = null!;
public event Action EA_UserClaims = null!;
public event Action EA_UserCurrCompany = null!;
public event Action EA_UserId = null!;
public event Action EA_UserRole = null!;
#endregion Public Events
#region Public Properties
public Dictionary<string, string> UserPref { get; set; } = new Dictionary<string, string>();
public string userId
public string? currLanguage
{
get => _userId;
get => _currLanguage;
set
{
if (_userId != value)
if (_currLanguage != value)
{
_userId = value;
reportUserId();
_currLanguage = value;
reportCurrentLanguage();
}
}
}
@@ -47,6 +73,34 @@
}
}
public int userCurrComp
{
get => _userCurrComp;
set
{
if (_userCurrComp != value)
{
_userCurrComp = value;
reportUserCurrCompany();
}
}
}
public string userId
{
get => _userId;
set
{
if (_userId != value)
{
_userId = value;
reportUserId();
}
}
}
public Dictionary<string, string> UserPref { get; set; } = new Dictionary<string, string>();
public string userRole
{
get => _userRole;
@@ -60,39 +114,205 @@
}
}
public int userCurrComp
{
get => _userCurrComp;
set
{
if (_userCurrComp != value)
{
_userCurrComp = value;
reportUserCurrCompany();
}
}
}
public string? currLanguage
{
get => _currLanguage;
set
{
if (_currLanguage != value)
{
_currLanguage = value;
reportCurrentLanguage();
}
}
}
#endregion Public Properties
#region Public Methods
/// <summary>
/// Dati utente (TUTTI) da REDIS o DB
/// </summary>
/// <returns></returns>
public async Task<List<IdentityUser>> UserDataGetAll()
{
string source = "DB";
List<IdentityUser> dataResult = new List<IdentityUser>();
// cerco da cache
string currKey = $"{Constants.rKeyUsersAll}";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
string? rawData = await redisDb.StringGetAsync(currKey);
if (!string.IsNullOrEmpty(rawData))
{
source = "REDIS";
var tempResult = JsonConvert.DeserializeObject<List<IdentityUser>>(rawData);
if (tempResult == null)
{
dataResult = new List<IdentityUser>();
}
else
{
dataResult = tempResult;
}
}
else
{
dataResult = await _userManager.Users.ToListAsync();
rawData = JsonConvert.SerializeObject(dataResult, JSSettings);
await redisDb.StringSetAsync(currKey, rawData, LongCache);
}
if (dataResult == null)
{
dataResult = new List<IdentityUser>();
}
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Debug($"UserDataGetAll | {source} in: {ts.TotalMilliseconds} ms");
return dataResult;
}
/// <summary>
/// Dati utente (singolo x UserId)
/// </summary>
/// <param name="UserId"></param>
/// <returns></returns>
public async Task<UserData> UserDataGetById(string UserId)
{
string source = "DB";
UserData dataResult = new UserData();
// cerco da cache
string currKey = $"{Constants.rKeyUsersDataId}:{UserId}";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
string? rawData = await redisDb.StringGetAsync(currKey);
if (!string.IsNullOrEmpty(rawData))
{
source = "REDIS";
var tempResult = JsonConvert.DeserializeObject<UserData>(rawData);
if (tempResult == null)
{
dataResult = new UserData();
}
else
{
dataResult = tempResult;
}
}
else
{
// Collezione dati raw
var userRaw = await _userManager.FindByIdAsync(UserId);
if (userRaw != null)
{
var userIdent = new IdentityUser
{
Id = userRaw.Id,
UserName = userRaw.UserName,
Email = userRaw.Email,
PhoneNumber = userRaw.PhoneNumber,
PasswordHash = "*****",
EmailConfirmed = userRaw.EmailConfirmed
};
// cerco ruoli & claims
var UserRoles = await _userManager.GetRolesAsync(userIdent);
var UserClaims = await _userManager.GetClaimsAsync(userIdent);
// compongo output
dataResult = new UserData()
{
Identity = userIdent,
Roles = UserRoles.ToList(),
Claims = UserClaims.ToList()
};
rawData = JsonConvert.SerializeObject(dataResult, JSSettings);
await redisDb.StringSetAsync(currKey, rawData, FastCache);
}
}
if (dataResult == null)
{
dataResult = new UserData();
}
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Debug($"UserDataGetById | {source} in: {ts.TotalMilliseconds} ms");
return dataResult;
}
/// <summary>
/// Dati utente (filtrati da UserId)
/// </summary>
/// <param name="searchVal"></param>
/// <returns></returns>
public async Task<List<UserData>> UserDataGetFilt(string searchVal)
{
string source = "DB";
List<UserData> dataResult = new List<UserData>();
// cerco da cache
string currKey = $"{Constants.rKeyUsersDataSearch}:{searchVal}";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
string? rawData = await redisDb.StringGetAsync(currKey);
if (!string.IsNullOrEmpty(rawData))
{
source = "REDIS";
var tempResult = JsonConvert.DeserializeObject<List<UserData>>(rawData);
if (tempResult == null)
{
dataResult = new List<UserData>();
}
else
{
dataResult = tempResult;
}
}
else
{
// Collezione dati raw
List<IdentityUser> RawList = new List<IdentityUser>();
//List<UserData> UsersList = new List<UserData>();
var allData = await UserDataGetAll();
if (!string.IsNullOrEmpty(searchVal))
{
RawList = allData.Where(x => x.NormalizedEmail.Contains(searchVal.ToUpper()) || x.NormalizedUserName.Contains(searchVal.ToUpper())).ToList();
}
else
{
RawList = allData;
}
var user = RawList.Select(x => new IdentityUser
{
Id = x.Id,
UserName = x.UserName,
Email = x.Email,
PhoneNumber = x.PhoneNumber,
PasswordHash = "*****",
EmailConfirmed = x.EmailConfirmed
}).ToList();
foreach (var item in user)
{
var UserRoles = await _userManager.GetRolesAsync(item);
var UserClaims = await _userManager.GetClaimsAsync(item);
var newItem = new UserData()
{
Identity = item,
Roles = UserRoles.ToList(),
Claims = UserClaims.ToList()
};
dataResult.Add(newItem);
}
rawData = JsonConvert.SerializeObject(dataResult, JSSettings);
await redisDb.StringSetAsync(currKey, rawData, FastCache);
}
if (dataResult == null)
{
dataResult = new List<UserData>();
}
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Debug($"UserDataGetFilt | {source} in: {ts.TotalMilliseconds} ms");
return dataResult;
}
#endregion Public Methods
#region Protected Methods
protected void reportUserId()
protected void reportCurrentLanguage()
{
if (EA_UserId != null)
if (EA_CurrLanguage != null)
{
EA_UserId?.Invoke();
EA_CurrLanguage?.Invoke();
}
}
@@ -104,6 +324,22 @@
}
}
protected void reportUserCurrCompany()
{
if (EA_UserCurrCompany != null)
{
EA_UserCurrCompany?.Invoke();
}
}
protected void reportUserId()
{
if (EA_UserId != null)
{
EA_UserId?.Invoke();
}
}
protected void reportUserRole()
{
if (EA_UserRole != null)
@@ -112,30 +348,115 @@
}
}
protected void reportUserCurrCompany()
{
if (EA_UserCurrCompany != null)
{
EA_UserCurrCompany?.Invoke();
}
}
protected void reportCurrentLanguage()
{
if (EA_CurrLanguage != null)
{
EA_CurrLanguage?.Invoke();
}
}
#endregion Protected Methods
#region Private Fields
private static JsonSerializerSettings? JSSettings;
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly UserManager<IdentityUser> _userManager;
/// <summary>
/// Durata cache lunga IN SECONDI
/// </summary>
private int cacheTtlLong = 60 * 5;
/// <summary>
/// Durata cache breve IN SECONDI
/// </summary>
private int cacheTtlShort = 60 * 1;
/// <summary>
/// Oggetto per connessione a REDIS
/// </summary>
private IConnectionMultiplexer redisConn;
/// <summary>
/// Oggetto DB redis da impiegare x chiamate R/W
/// </summary>
private IDatabase redisDb = null!;
private Random rnd = new Random();
#endregion Private Fields
#region Private Properties
private string _userId { get; set; } = "";
private List<string>? _userClaims { get; set; } = null;
private string _userRole { get; set; } = "";
private int _userCurrComp { get; set; } //= -1;
private string? _currLanguage { get; set; } = null;
private List<string>? _userClaims { get; set; } = null;
private int _userCurrComp { get; set; }
private string _userId { get; set; } = "";
private string _userRole { get; set; } = "";
/// <summary>
/// Durata cache breve (1 min circa + perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache molto breve (10 sec circa + perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan UltraFastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Private Properties
#region Private Methods
/// <summary>
/// Esegue flush memoria redis dato pattern
/// </summary>
/// <param name="pattern"></param>
/// <returns></returns>
private async Task<bool> ExecFlushRedisPattern(RedisValue pattern)
{
bool answ = false;
var listEndpoints = redisConn.GetEndPoints();
foreach (var endPoint in listEndpoints)
{
//var server = redisConnAdmin.GetServer(listEndpoints[0]);
var server = redisConn.GetServer(endPoint);
if (server != null)
{
var keyList = server.Keys(redisDb.Database, pattern);
foreach (var item in keyList)
{
await redisDb.KeyDeleteAsync(item);
}
answ = true;
}
}
return answ;
}
#endregion Private Methods
//= -1;
}
}
+205
View File
@@ -0,0 +1,205 @@
@page "/UserAdmin"
@if (ShowPopup)
{
<div class="modal" tabindex="-1" style="display:block" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Edit User</h3>
<!-- Button to close the popup -->
<button type="button" class="close" @onclick="ClosePopup">
<span aria-hidden="true">X</span>
</button>
</div>
<!-- Edit form for the current user -->
<div class="modal-body">
<div class="row">
<div class="col-6">
@if (objUser.Id != "")
{
<p>@objUser.Id</p>
}
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="far fa-envelope"></i></span>
</div>
<input class="form-control" type="text" placeholder="Email" @bind="objUser.Email" />
<div class="input-group-append">
<div class="input-group-text">
<input class="form-control2" type="checkbox" title="Email Confirmed" @bind="objUser.EmailConfirmed" />
</div>
</div>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
<input class="form-control" type="password" placeholder="Password" @bind="objUser.PasswordHash" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user-tag"></i></span>
</div>
<select class="form-control" @bind="@CurrentUserRole">
@foreach (var option in RolesList)
{
<option value="@option">
@option
</option>
}
</select>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-tag"></i></span>
</div>
<select class="form-control" @bind="@CurrentUserClaimType">
@foreach (var option in ClaimsList)
{
<option value="@option">
@option
</option>
}
</select>
<select class="form-control" @bind="@CurrentUserClaimVal">
<option value="0">--- Selezionare ---</option>
@foreach (var option in ClaimValList)
{
<option value="@option.Key">
@option.Value
</option>
}
</select>
</div>
<span style="color:red">@strError</span>
<hr />
<div class="row">
<div class="col-6">
<button class="btn btn-block btn-success" @onclick="SaveUser" title="Save"><i class="fas fa-check"></i> Save</button>
</div>
<div class="col-6">
<button class="btn btn-block btn-primary" @onclick="ClosePopup"><i class="far fa-window-close"></i> Close</button>
</div>
</div>
</div>
<div class="col-6 text-right">
<!-- Only show Id if not a new user -->
@if (objUser.Id != "")
{
<QrCodeDisplay rawCode="@qrCodeVal"></QrCodeDisplay>
}
<CopyToClipboard Text="@qrCodeVal"></CopyToClipboard>
</div>
</div>
</div>
</div>
</div>
</div>
}
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-4 col-lg-6 col-xl-8 text-truncate">
<h3>User Administration</h3>
</div>
<div class="col-4 col-lg-3 col-xl-2 text-right">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user-tag"></i></span>
</div>
<select class="form-control" @bind="@FiltUserRole">
<option value="0">--- Tutti ---</option>
@foreach (var option in RolesList)
{
<option value="@option">
@option
</option>
}
</select>
</div>
</div>
<div class="col-4 col-lg-3 col-xl-2 text-right">
<AuthorizeView>
<Authorized>
@if (@context.User.IsInRole("SuperAdmin"))
{
<button class="btn btn-success btn-block" @onclick="AddNewUser">Add User</button>
}
</Authorized>
</AuthorizeView>
</div>
</div>
</div>
<div class="card-body px-1">
<table class="table table-sm table-striped table-responsive-md">
<thead>
<tr>
<th></th>
<th>Id</th>
<th>User / Email</th>
<th>Ruolo</th>
<th>Permessi</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var user in UsersList)
{
<tr>
<td>
@if (!ShowPopup)
{
<button class="btn btn-sm btn-primary" @onclick="(() => EditUser(user.Identity))" title="Edit">
<i class="fas fa-pen"></i>
</button>
}
else
{
<button class="btn btn-sm btn-secondary disabled" title="Edit">
<i class="fas fa-pen"></i>
</button>
}
</td>
<td>@user.Identity.Id.Substring(0, 8)...</td>
<td>
@if (user.Identity.EmailConfirmed)
{
<span class="badge badge-pill badge-success" title="Email validata"><span class="oi oi-check" aria-hidden="true"></span></span>
}
else
{
<span class="badge badge-pill badge-danger" title="Email NON ancora validata!"><span class="oi oi-check" aria-hidden="true"></span></span>
}
&nbsp;@user.Identity.Email
</td>
<td>
@ShowRoles(user.Roles)
</td>
<td>
@ShowClaims(user.Claims)
</td>
<td>
@if (!ShowPopup)
{
<button class="btn btn-sm btn-danger" @onclick="(() => DeleteUser(user.Identity))" title="Delete">
<i class="fas fa-trash"></i>
</button>
}
else
{
<button class="btn btn-sm btn-secondary disabled" title="Delete">
<i class="fas fa-trash"></i>
</button>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="card-footer p-1">
<DataPager PageSize="numRecord" currPage="currPage" numRecordChanged="ForceReload" numPageChanged="ForceReloadPage" totalCount="totalCount" showLoading="isLoading" />
</div>
</div>
+719
View File
@@ -0,0 +1,719 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.JSInterop;
using WebDoorCreator.Data.DbModels;
using WebDoorCreator.Data.Services;
using WebDoorCreator.Data.User;
using WebDoorCreator.UI.Data;
namespace WebDoorCreator.UI.Pages
{
[Authorize(Roles = "SuperAdmin, Admin")]
public partial class UserAdmin : ComponentBase, IDisposable
{
#region Public Fields
public const string ADMIN_ROLE = "SuperAdmin";
public const string STD_CLAIM = "None";
public const string STD_CLAIM_VAL = "0";
public const string UNDEF_ROLE = "Undef";
#endregion Public Fields
#region Public Properties
public string searchVal
{
get
{
return AppMService.SearchVal;
}
}
#endregion Public Properties
#region Public Methods
public List<string> ConvertClaim(List<System.Security.Claims.Claim> ClaimList)
{
List<string> answ = new List<string>();
var pUpd = Task.Run(async () =>
{
await refreshLists();
});
pUpd.Wait();
// ciclo sui claims
foreach (var claim in ClaimList)
{
string claimStr = claim.ToString();
// verifico che in OGNI claim ci siaun valore ammissibile...
if (claimStr.Contains(":"))
{
// splitto chiave/valore
var splitClaim = claimStr.Split(':');
switch (splitClaim[0])
{
case "CompanyId":
if (CompanyList != null)
{
var compRec = CompanyList
.Where(x => $"{x.CompanyId}" == splitClaim[1].Trim())
.FirstOrDefault();
if (compRec != null)
{
answ.Add($"{splitClaim[0]}: {compRec.CompanyName}");
}
else
{
answ.Add($"{claim}");
}
}
break;
#if false
case "SupplierId":
if (suppList != null)
{
var plantRec = suppList
.Where(x => x.SupplierId.ToString() == splitClaim[1].Trim())
.FirstOrDefault();
if (plantRec != null)
{
answ.Add($"{splitClaim[0]}: {plantRec.SupplierDesc}");
}
else
{
answ.Add($"{claim}");
}
}
break;
case "TransporterId":
if (transpList != null)
{
var plantRec = transpList
.Where(x => x.TransporterId.ToString() == splitClaim[1].Trim())
.FirstOrDefault();
if (plantRec != null)
{
answ.Add($"{splitClaim[0]}: {plantRec.TransporterDesc}");
}
else
{
answ.Add($"{claim}");
}
}
break;
#endif
case "None":
default:
break;
}
}
}
return answ;
}
public void Dispose()
{
AppMService.EA_SearchUpdated -= OnSeachUpdated;
}
/// <summary>
/// Recupera elenco utenti
/// </summary>
public async Task GetUsers()
{
// clear any error messages
strError = "";
UsersAll = await WDCUsrServ.UserDataGetFilt(searchVal);
// filtro visualizzazione x tipo SE richeisto
if (FiltUserRole != "0")
{
UsersAll = UsersAll.Where(x => x.Roles.Contains(FiltUserRole)).ToList();
}
UsersList = UsersAll
.Skip(numRecord * (currPage - 1)).Take(numRecord)
.ToList();
}
public async void OnSeachUpdated()
{
await GetUsers();
StateHasChanged();
}
public string ShowClaims(List<System.Security.Claims.Claim> ClaimList)
{
//string answ = string.Join(",", ClaimList);
string answ = string.Join(",", ConvertClaim(ClaimList));
return answ;
}
public string ShowRoles(List<string> RoleList)
{
string answ = string.Join(",", RoleList);
return answ;
}
#endregion Public Methods
#region Protected Properties
[Inject]
protected RoleManager<IdentityRole> _RoleManager { get; set; } = null!;
[Inject]
protected UserManager<IdentityUser> _UserManager { get; set; } = null!;
[Inject]
protected MessageService AppMService { get; set; } = null!;
[Inject]
protected AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!;
protected List<CompanyModel>? CompanyList { get; set; } = null;
[Inject]
protected IJSRuntime JSRuntime { get; set; } = null!;
protected int totalCount
{
get
{
int answ = 0;
if (UsersAll != null)
{
answ = UsersAll.Count;
}
return answ;
}
}
[Inject]
protected WebDoorCreatorService WDCService { get; set; } = null!;
[Inject]
protected WDCUserService WDCUsrServ { get; set; } = null!;
#endregion Protected Properties
#if false
protected List<GWMS.Data.DatabaseModels.SupplierModel>? suppList { get; set; } = null;
protected List<GWMS.Data.DatabaseModels.TransporterModel>? transpList { get; set; } = null;
#endif
#region Protected Methods
protected void ForceReload(int newNum)
{
numRecord = newNum;
}
protected void ForceReloadPage(int newNum)
{
currPage = newNum;
}
protected override async Task OnInitializedAsync()
{
AppMService.ShowSearch = true;
AppMService.EA_SearchUpdated += OnSeachUpdated;
// lettura dati
await GetUsers();
#if false
await CheckSuperAdmin();
#endif
}
#endregion Protected Methods
#region Private Fields
/// <summary>
/// Elenco CLAIMS da mostrare durante editing
/// </summary>
private List<string> ClaimsList = new List<string>() { STD_CLAIM, "CompanyId", "SupplierId", "TransporterId" };
private Dictionary<string, string> ClaimValList = new Dictionary<string, string>();
/// <summary>
/// User corrente
/// </summary>
private IdentityUser objUser = new IdentityUser();
private string qrCodeVal = "";
/// <summary>
/// Collezione utenti
/// </summary>
private List<IdentityUser> RawList = new List<IdentityUser>();
/// <summary>
/// Elenco ROLES da mostrare in dropdown durante editing (1 solo? usare DB?!?)
/// </summary>
private List<string> RolesList = new List<string>() { "Undef", "User", "CompUser", "CompAdmin", "Admin", "SuperAdmin" };
// To enable showing the Popup
private bool ShowPopup = false;
// To hold any possible errors
private string strError = "";
/// <summary>
/// Collezione utenti (totale)
/// </summary>
private List<UserData> UsersAll = new List<UserData>();
/// <summary>
/// Collezione utenti
/// </summary>
private List<UserData> UsersList = new List<UserData>();
#endregion Private Fields
#region Private Properties
private string _CurrClaimType { get; set; } = "None";
private int _currPage { get; set; } = 1;
private string _filtUserRole { get; set; } = "0";
private int _numRecord { get; set; } = 10;
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; } = null!;
[Inject]
private IConfiguration Configuration { get; set; } = null!;
private System.Security.Claims.Claim CurrentUserClaim
{
get => new System.Security.Claims.Claim(CurrentUserClaimType, CurrentUserClaimVal);
}
/// <summary>
/// Claim di default (Tipo)
/// </summary>
private string CurrentUserClaimType
{
get
{
return _CurrClaimType;
}
set
{
_CurrClaimType = value;
var pUpd = Task.Run(async () => await refreshClaimVal(value));
pUpd.Wait();
}
}
/// <summary>
/// Claim di default (valore)
/// </summary>
private string CurrentUserClaimVal { get; set; } = "0";
/// <summary>
/// Ruolo di default (User!!!)
/// </summary>
private string CurrentUserRole { get; set; } = "User";
private int currPage
{
get => _currPage;
set
{
if (_currPage != value)
{
_currPage = value;
var pUpd = Task.Run(async () => await ReloadData());
pUpd.Wait();
}
}
}
/// <summary>
/// Gestione filtraggio dati
/// </summary>
private string FiltUserRole
{
get
{
return _filtUserRole;
}
set
{
if (_filtUserRole != value)
{
_filtUserRole = value;
var pUpd = Task.Run(async () => await ReloadData());
pUpd.Wait();
}
}
}
private bool isLoading { get; set; } = false;
private int numRecord
{
get => _numRecord;
set
{
if (_numRecord != value)
{
_numRecord = value;
var pUpd = Task.Run(async () => await ReloadData());
pUpd.Wait();
}
}
}
#endregion Private Properties
#region Private Methods
private void AddNewUser()
{
// Make new user
objUser = new IdentityUser();
objUser.PasswordHash = "*****";
// Set Id to blank so we know it is a new record
objUser.Id = "";
// Open the Popup
ShowPopup = true;
}
private async Task CheckSuperAdmin()
{
// se non ci fosse --> creo samuele come superadmin
string superUser = "samuele@steamware.net";
string superPwd = "viaDante16!";
var user = await _UserManager.FindByEmailAsync(superUser);
if (user == null)
{
// Insert new user
var NewUser =
new IdentityUser
{
UserName = superUser,
Email = superUser,
EmailConfirmed = true
};
var CreateResult = await _UserManager.CreateAsync(NewUser, superPwd);
if (CreateResult.Succeeded)
{
user = await _UserManager.FindByEmailAsync(superUser);
}
}
// verifico ruoli...
if (user != null)
{
// Gestione salvataggio ruoli... SE VARIATO...
var UserRoles = await _UserManager.GetRolesAsync(user);
if (UserRoles != null && UserRoles.Count > 0)
{
var oldRole = UserRoles.Where(x => x == ADMIN_ROLE).FirstOrDefault();
if (oldRole == null)
{
// aggiungo a ruolo admin
await _UserManager.AddToRoleAsync(user, ADMIN_ROLE);
}
}
}
}
private async Task ClosePopup()
{
// Close the Popup
ShowPopup = false;
// Refresh Users
await GetUsers();
}
private async Task DeleteUser(IdentityUser _IdentityUser)
{
// Close the Popup
ShowPopup = false;
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sicuro di voler eliminare l'utente selezionato??"))
return;
// Get the user
var user = await _UserManager.FindByIdAsync(_IdentityUser.Id);
if (user != null)
{
// Delete the user
await _UserManager.DeleteAsync(user);
}
// Refresh Users
await GetUsers();
}
private void EditUser(IdentityUser _IdentityUser)
{
// selezione come current user
objUser = _IdentityUser;
var pUpd = Task.Run(async () =>
{
var user = await WDCUsrServ.UserDataGetById(objUser.Id);
#if false
// Get the user
var user = await _UserManager.FindByIdAsync(objUser.Id);
#endif
if (user != null)
{
// salvo role
if (user.Roles != null)
{
CurrentUserRole = user.Roles.FirstOrDefault();
}
else
{
CurrentUserRole = UNDEF_ROLE;
}
// salvo claim
if (user.Claims != null && user.Claims.Count > 0)
{
var CurrentUserClaim = user.Claims.FirstOrDefault();
CurrentUserClaimType = CurrentUserClaim.Type;
CurrentUserClaimVal = CurrentUserClaim.Value;
}
else
{
CurrentUserClaimType = STD_CLAIM;
CurrentUserClaimVal = "0";
}
string baseUrl = Configuration["BaseUrl"];
string baseAppPath = Configuration["BaseAppPath"];
string redirPage = Configuration["QrRedirPage"];
if (!string.IsNullOrEmpty(baseAppPath))
{
if (baseUrl.EndsWith("/"))
{
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
}
baseUrl = $"{baseUrl}{baseAppPath}";
}
qrCodeVal = $"{baseUrl}Identity/Account/LogIn?uid={user.Identity.Id}&uem={user.Identity.Email}&pag={redirPage}";
}
});
pUpd.Wait();
// Open the Popup
ShowPopup = true;
}
private async Task refreshClaimVal(string newType)
{
// init vuoto
ClaimValList = new Dictionary<string, string>();
// aggiorno elenco ClaimValList
switch (newType)
{
case "CompanyId":
// elenco company (tutte, ID==0) --> to dictionary!
var compList = await WDCService.CompanyGetByKey(0);
if (compList != null)
{
ClaimValList = compList
.ToDictionary(x => $"{x.CompanyId}", x => x.CompanyName);
}
break;
#if false
case "SupplierId":
// elenco plant --> to dictionary!
var suppList = await WDCService.SuppliersGetAll();
if (suppList != null)
{
ClaimValList = suppList
.ToDictionary(x => $"{x.SupplierId}", x => x.SupplierDesc);
}
break;
case "TransporterId":
// elenco plant --> to dictionary!
var transpList = await WDCService.TransportersGetAll();
if (transpList != null)
{
ClaimValList = transpList
.ToDictionary(x => $"{x.TransporterId}", x => x.TransporterDesc);
}
break;
#endif
case "None":
default:
break;
}
}
private async Task refreshLists()
{
// effettuo refresh valori cache Company/...
if (CompanyList == null)
{
CompanyList = await WDCService.CompanyGetByKey(0);
}
#if false
if (suppList == null)
{
suppList = await WDCService.SuppliersGetAll();
}
if (transpList == null)
{
transpList = await WDCService.TransportersGetAll();
}
#endif
}
private async Task ReloadData()
{
isLoading = true;
await GetUsers();
isLoading = false;
}
private async Task SaveUser()
{
try
{
// Is this an existing user?
if (objUser.Id != "")
{
// Get the user
var user = await _UserManager.FindByIdAsync(objUser.Id);
// Update Email + check email
user.Email = objUser.Email;
user.EmailConfirmed = objUser.EmailConfirmed;
// Update the user
await _UserManager.UpdateAsync(user);
// Only update password if the current value is not the default value
if (objUser.PasswordHash != "*****")
{
var resetToken =
await _UserManager.GeneratePasswordResetTokenAsync(user);
var passworduser =
await _UserManager.ResetPasswordAsync(
user,
resetToken,
objUser.PasswordHash);
if (!passworduser.Succeeded)
{
if (passworduser.Errors.FirstOrDefault() != null)
{
strError =
passworduser
.Errors
.FirstOrDefault()
.Description;
}
else
{
strError = "Password error";
}
// Keep the popup opened
return;
}
}
// Gestione salvataggio ruoli... SE VARIATO...
var UserRoles = await _UserManager.GetRolesAsync(user);
if (UserRoles != null && UserRoles.Count > 0)
{
var oldRole = UserRoles.FirstOrDefault();
if (!CurrentUserRole.Equals(oldRole))
{
// recupero il ruolo attuale e lo rimuovo
await _UserManager.RemoveFromRoleAsync(user, oldRole);
}
// aggiungo il nuovo ruolo
await _UserManager.AddToRoleAsync(user, CurrentUserRole);
}
else
{
// aggiungo il nuovo ruolo
await _UserManager.AddToRoleAsync(user, CurrentUserRole);
}
// gestione salvataggio Claims
var UserClaims = await _UserManager.GetClaimsAsync(user);
if (UserClaims != null && UserClaims.Count > 0)
{
var oldClaim = UserClaims.FirstOrDefault();
if (!oldClaim.Equals(CurrentUserClaim))
{
// recupero il ruolo attuale e lo rimuovo
await _UserManager.RemoveClaimAsync(user, oldClaim);
}
// aggiungo il nuovo ruolo
await _UserManager.AddClaimAsync(user, CurrentUserClaim);
}
else
{
// aggiungo il nuovo ruolo
await _UserManager.AddClaimAsync(user, CurrentUserClaim);
}
}
else
{
// Insert new user
var NewUser =
new IdentityUser
{
UserName = objUser.Email,
Email = objUser.Email
};
var CreateResult = await _UserManager.CreateAsync(NewUser, objUser.PasswordHash);
if (!CreateResult.Succeeded)
{
if (CreateResult
.Errors
.FirstOrDefault() != null)
{
strError =
CreateResult
.Errors
.FirstOrDefault()
.Description;
}
else
{
strError = "Create error";
}
// Keep the popup opened
return;
}
else
{
// aggiungo a ruolo undef
await _UserManager.AddToRoleAsync(NewUser, UNDEF_ROLE);
// aggiungo claim undef...
await _UserManager.AddClaimAsync(NewUser, CurrentUserClaim);
}
}
// Close the Popup
ShowPopup = false;
// Refresh Users
await GetUsers();
}
catch (Exception ex)
{
strError = ex.GetBaseException().Message;
}
}
#endregion Private Methods
}
}
+1
View File
@@ -55,6 +55,7 @@ builder.Services.AddScoped<WDCUserService>();
builder.Services.AddSingleton<WDCRefreshService>();
builder.Services.AddSingleton<WDCVocabularyService>();
builder.Services.AddSingleton<QueueDataService>();
builder.Services.AddScoped<MessageService>();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IConnectionMultiplexer>(redisMultiplexer);
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Version>0.9.2305.2612</Version>
<Version>0.9.2305.2619</Version>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-WebDoorCreator.UI-dfe95fed-1398-4144-bd43-8b3a765d6608</UserSecretsId>
</PropertyGroup>