Add/refactor services for permissions authorization model.

Rename some fields in DB.permission.
This commit is contained in:
Фролов 2021-12-11 16:46:04 +05:00
parent 1ec22744c3
commit 551c60c4ff
26 changed files with 3458 additions and 179 deletions

View File

@ -1,9 +1,7 @@
namespace AsbCloudApp.Data
{
public class PermissionDto
public class PermissionDto : PermissionBaseDto
{
public int IdUserRole { get; set; }
public int IdPermission { get; set; }
public int PermissionValue { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace AsbCloudApp.Data
{
public class PermissionBaseDto
{
public int IdPermissionInfo { get; set; }
public string PermissionName { get; set; }
public int Value { get; set; }
}
}

View File

@ -1,19 +0,0 @@
namespace AsbCloudApp.Data
{
public class UserBaseDto
{
public string Login { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Patronymic { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Position { get; set; }
}
}

View File

@ -1,13 +1,16 @@
namespace AsbCloudApp.Data
{
public class UserDto : UserBaseDto, IId
public class UserDto: IId
{
public int Id { get; set; }
public string Login { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Patronymic { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Position { get; set; }
public int? IdCompany { get; set; }
public string Password { get; set; }
public CompanyDto Company { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace AsbCloudApp.Data
{
public class UserExtendedDto : UserDto
{
public IEnumerable<string> RoleNames { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace AsbCloudApp.Data
{
public class UserRegistrationDto : UserDto
{
public string Password { get; set; }
}
}

View File

@ -9,7 +9,7 @@ namespace AsbCloudApp.Data
public string Caption { get; set; }
public int? IdParent { get; set; }
public int IdType { get; set; }
public IEnumerable<PermissionDto> Permissions { get; set; }
public IEnumerable<PermissionBaseDto> Permissions { get; set; }
[JsonIgnore]
public virtual ICollection<UserDto> Users { get; set; }
}

View File

@ -2,12 +2,9 @@
namespace AsbCloudApp.Data
{
public class UserTokenDto : UserBaseDto
public class UserTokenDto : UserExtendedDto
{
public int Id { get; set; }
public string CompanyName { get; set; }
public IEnumerable<string> RoleNames { get; set; }
public IDictionary<string, int> Permissions { get; set; }
public IEnumerable<PermissionBaseDto> Permissions { get; set; }
public string Token { get; set; }
}
}

View File

@ -13,6 +13,6 @@ namespace AsbCloudApp.Services
string password, CancellationToken token = default);
string Refresh(ClaimsPrincipal user);
int Register(UserDto userDto);
int Register(UserRegistrationDto userDto);
}
}

View File

@ -1,8 +1,15 @@
using AsbCloudApp.Data;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
public interface IUserRoleService: ICrudService<UserRoleDto>
public interface IUserRoleService : ICrudService<UserRoleDto>
{
Task<UserRoleDto> GetByNameAsync(string name, CancellationToken token = default);
List<UserRoleDto> GetNestedById(int id, int counter = 10);
IEnumerable<PermissionBaseDto> GetNestedPermissions(IEnumerable<UserRoleDto> roles);
bool HasPermission(IEnumerable<int> rolesIds, string permissionName, int permissionMask = 0);
}
}

View File

@ -0,0 +1,15 @@
using AsbCloudApp.Data;
using System.Collections.Generic;
namespace AsbCloudApp.Services
{
public interface IUserService : ICrudService<UserExtendedDto>
{
IUserRoleService RoleService { get; }
IEnumerable<PermissionBaseDto> GetNestedPermissions(int idUser);
IEnumerable<UserRoleDto> GetRolesByIdUser(int idUser);
bool HasAnyRoleOf(int idUser, IEnumerable<string> roleNames);
bool HasAnyRoleOf(int idUser, IEnumerable<int> roleIds);
public bool HasPermission(int idUser, string permissionName, int permissionMask = 0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace AsbCloudDb.Migrations
{
public partial class Rename_Permissions_fields : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_t_permission_t_permission_info_id_permission",
table: "t_permission");
migrationBuilder.RenameColumn(
name: "permission_value",
table: "t_permission",
newName: "value");
migrationBuilder.RenameColumn(
name: "id_permission",
table: "t_permission",
newName: "id_permission_info");
migrationBuilder.RenameIndex(
name: "IX_t_permission_id_permission",
table: "t_permission",
newName: "IX_t_permission_id_permission_info");
migrationBuilder.AddForeignKey(
name: "FK_t_permission_t_permission_info_id_permission_info",
table: "t_permission",
column: "id_permission_info",
principalTable: "t_permission_info",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_t_permission_t_permission_info_id_permission_info",
table: "t_permission");
migrationBuilder.RenameColumn(
name: "value",
table: "t_permission",
newName: "permission_value");
migrationBuilder.RenameColumn(
name: "id_permission_info",
table: "t_permission",
newName: "id_permission");
migrationBuilder.RenameIndex(
name: "IX_t_permission_id_permission_info",
table: "t_permission",
newName: "IX_t_permission_id_permission");
migrationBuilder.AddForeignKey(
name: "FK_t_permission_t_permission_info_id_permission",
table: "t_permission",
column: "id_permission",
principalTable: "t_permission_info",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -687,17 +687,17 @@ namespace AsbCloudDb.Migrations
.HasColumnType("integer")
.HasColumnName("id_user_role");
b.Property<int>("IdPermission")
b.Property<int>("IdPermissionInfo")
.HasColumnType("integer")
.HasColumnName("id_permission");
.HasColumnName("id_permission_info");
b.Property<int>("PermissionValue")
b.Property<int>("Value")
.HasColumnType("integer")
.HasColumnName("permission_value");
.HasColumnName("value");
b.HasKey("IdUserRole", "IdPermission");
b.HasKey("IdUserRole", "IdPermissionInfo");
b.HasIndex("IdPermission");
b.HasIndex("IdPermissionInfo");
b.ToTable("t_permission");
@ -2605,7 +2605,7 @@ namespace AsbCloudDb.Migrations
{
b.HasOne("AsbCloudDb.Model.PermissionInfo", "PermissionInfo")
.WithMany("Permissions")
.HasForeignKey("IdPermission")
.HasForeignKey("IdPermissionInfo")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();

View File

@ -256,7 +256,7 @@ namespace AsbCloudDb.Model
modelBuilder.Entity<Permission>(entity =>
{
entity.HasKey(e => new { e.IdUserRole, e.IdPermission });
entity.HasKey(e => new { e.IdUserRole, e.IdPermissionInfo });
});
FillData(modelBuilder);
@ -456,13 +456,6 @@ namespace AsbCloudDb.Model
select well;
}
public IQueryable<User> GetUsersByLogin(string login)
=> Users
.Include(e => e.RelationUsersUserRoles)
.ThenInclude(r => r.UserRole)
.Include(e => e.Company)
.Where(e => e.Login == login);
public async Task<(DateTime From, DateTime To)> GetDatesRangeAsync<TEntity>(int idTelemetry,
CancellationToken token = default)
where TEntity : class, ITelemetryData

View File

@ -51,7 +51,6 @@ namespace AsbCloudDb.Model
DbSet<TEntity> Set<TEntity>() where TEntity : class;
IQueryable<Well> GetWellsForCompany(int idCompany);
IQueryable<User> GetUsersByLogin(string login);
Task<(DateTime From, DateTime To)> GetDatesRangeAsync<T>(int idTelemetry, CancellationToken token) where T : class, ITelemetryData;
Task<IEnumerable<(double? MinDepth, double? MaxDepth, DateTime BeginPeriodDate)>> GetDepthToIntervalAsync(int telemetryId,
int intervalHoursTimestamp, int workStartTimestamp, double timezoneOffset, CancellationToken token);

View File

@ -9,17 +9,17 @@ namespace AsbCloudDb.Model
[Column("id_user_role")]
public int IdUserRole { get; set; }
[Column("id_permission")]
public int IdPermission { get; set; }
[Column("id_permission_info")]
public int IdPermissionInfo { get; set; }
[Column("permission_value")]
public int PermissionValue { get; set; }
[Column("value")]
public int Value { get; set; }
[ForeignKey(nameof(IdUserRole))]
[InverseProperty(nameof(Model.UserRole.Permissions))]
public virtual UserRole UserRole { get; set; }
[ForeignKey(nameof(IdPermission))]
[ForeignKey(nameof(IdPermissionInfo))]
[InverseProperty(nameof(Model.PermissionInfo.Permissions))]
public virtual PermissionInfo PermissionInfo { get; set; }
}

View File

@ -1,4 +1,16 @@
# Миграции
## EF tools
https://docs.microsoft.com/ru-ru/ef/core/cli/dotnet
Установка:
```
dotnet tool install --global dotnet-ef
```
Обновление:
```
dotnet tool update --global dotnet-ef
```
## Создать миграцию
```
dotnet ef migrations add <MigrationName> --project AsbCloudDb

View File

@ -56,6 +56,7 @@ namespace AsbCloudInfrastructure
services.AddTransient<ITelemetryService, TelemetryService>();
services.AddTransient<ITelemetryUserService, TelemetryUserService>();
services.AddTransient<ITimeZoneService, TimeZoneService>();
services.AddTransient<IUserService, UserService>();
services.AddTransient<IUserRoleService, UserRoleService>();
services.AddTransient<IWellService, WellService>();
services.AddTransient<IWellCompositeService, WellCompositeService>();
@ -64,7 +65,6 @@ namespace AsbCloudInfrastructure
// admin crud services:
services.AddTransient<ICrudService<WellDto>, CrudServiceBase<WellDto, Well>>();
services.AddTransient<ICrudService<UserDto>, CrudServiceBase<UserDto, User>>();
services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>();
services.AddTransient<ICrudService<PermissionInfoDto>, CrudServiceBase<PermissionInfoDto, PermissionInfo>>();
services.AddTransient<ICrudService<DrillParamsDto>, DrillParamsService>();

View File

@ -13,16 +13,14 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
namespace AsbCloudInfrastructure.Services
{
public class AuthService : IAuthService
{
private readonly IAsbCloudDbContext db;
private readonly CacheTable<UserRole> cacheUserRoles;
private readonly CacheTable<RelationUserUserRole> cacheUsersUserRoles;
private readonly CacheTable<PermissionInfo> cachePermissions;
private readonly CacheTable<Permission> cacheUserRolesPermissions;
private readonly IUserService userService;
public const string issuer = "a";
public const string audience = "a";
@ -37,13 +35,10 @@ namespace AsbCloudInfrastructure.Services
private readonly HashAlgorithm hashAlgorithm;
private readonly Random rnd;
public AuthService(IAsbCloudDbContext db, CacheDb cacheDb)
public AuthService(IAsbCloudDbContext db, CacheDb cacheDb, IUserService userService)
{
this.db = db;
cacheUserRoles = cacheDb.GetCachedTable<UserRole>((AsbCloudDbContext)db);
cacheUsersUserRoles = cacheDb.GetCachedTable<RelationUserUserRole>((AsbCloudDbContext)db);
cachePermissions = cacheDb.GetCachedTable<PermissionInfo>((AsbCloudDbContext)db);
cacheUserRolesPermissions = cacheDb.GetCachedTable<Permission>((AsbCloudDbContext)db);
this.userService = userService;
hashAlgorithm = SHA384.Create();
rnd = new Random((int)(DateTime.Now.Ticks % 2147480161));
}
@ -57,22 +52,13 @@ namespace AsbCloudInfrastructure.Services
if (identity == default || user.State == 0)
return null;
var userRoles = GetUserRoles(user.Id);
var roleNames = userRoles.Select(r => r.Caption);
var userDto = await userService.GetAsync(user.Id, token)
.ConfigureAwait(false);
return new UserTokenDto
{
Id = user.Id,
Name = user.Name,
CompanyName = user.Company.Caption,
Login = user.Login,
Patronymic = user.Patronymic,
RoleNames = roleNames,
Permissions = GetUserPermissions(userRoles),
Surname = user.Surname,
Token = MakeToken(identity.Claims),
};
var userTokenDto = userDto.Adapt<UserTokenDto>();
userTokenDto.Permissions = userService.GetNestedPermissions(userDto.Id);
userTokenDto.Token = MakeToken(identity.Claims);
return userTokenDto;
}
public string Refresh(ClaimsPrincipal user)
@ -80,7 +66,7 @@ namespace AsbCloudInfrastructure.Services
return MakeToken(user.Claims);
}
public int Register(UserDto userDto)
public int Register(UserRegistrationDto userDto)
{
if (userDto.Login is null || userDto.Login.Length is < 3 or > 50)
return -1;
@ -177,33 +163,12 @@ namespace AsbCloudInfrastructure.Services
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
private IEnumerable<UserRole> GetUserRoles(int idUser)
{
var userRolesIds = cacheUsersUserRoles.Where(r =>
r.IdUser == idUser).Select(r => r.IdUserRole);
return cacheUserRoles.Where(r => userRolesIds.Contains(r.Id));
}
private IDictionary<string, int> GetUserPermissions(IEnumerable<UserRole> userRoles)
{
var rolesIds = userRoles.Select(r => r.Id);
var userPermissionsInfo = cacheUserRolesPermissions.Where(p =>
rolesIds.Contains(p.IdUserRole))
.Select(perm => new { perm.IdPermission, perm.PermissionValue });
return userPermissionsInfo.Select(p => new
{
PermissionName = cachePermissions.FirstOrDefault(c => c.Id == p.IdPermission)?.Name,
p.PermissionValue
}).ToDictionary(k => k.PermissionName, v => v.PermissionValue);
}
private async Task<(ClaimsIdentity Identity, User User)> GetClaimsUserAsync(string login,
string password, CancellationToken token = default)
{
var user = await db
.GetUsersByLogin(login)
var user = await db.Users
.Include(e => e.Company)
.Where(e => e.Login == login)
.AsNoTracking()
.FirstOrDefaultAsync(token)
.ConfigureAwait(false);
@ -220,6 +185,10 @@ namespace AsbCloudInfrastructure.Services
new (ClaimsIdentity.DefaultNameClaimType, user.Login),
new (claimNameIdCompany, user.IdCompany.ToString()),
};
var roles = userService.GetRolesByIdUser(user.Id);
if (roles is not null)
foreach (var role in roles)
claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, role.Caption));
var claimsIdentity = new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
return (claimsIdentity, user);

View File

@ -31,10 +31,10 @@ namespace AsbCloudInfrastructure.Services
return dto;
}
public Task<int> InsertRangeAsync(IEnumerable<PermissionDto> dtos, CancellationToken token)
public async Task<int> InsertRangeAsync(IEnumerable<PermissionDto> dtos, CancellationToken token)
{
var entities = dtos.Select(Convert);
return cachePermission.InsertAsync(entities, token);
return (await cachePermission.InsertAsync(entities, token))?.Count()??0;
}
public async Task<int> UpdateAsync(PermissionDto dto, CancellationToken token)
@ -47,7 +47,7 @@ namespace AsbCloudInfrastructure.Services
public Task<int> DeleteAsync(int idUserRole, int idPermission, CancellationToken token)
{
bool predicate(Permission p) => p.IdUserRole == idUserRole && p.IdPermission == idPermission;
bool predicate(Permission p) => p.IdUserRole == idUserRole && p.IdPermissionInfo == idPermission;
return DeleteAsync(predicate, token);
}
@ -75,7 +75,9 @@ namespace AsbCloudInfrastructure.Services
public PermissionDto Convert(Permission src)
{
var dto = src.Adapt<PermissionDto>();
dto.PermissionName = src.PermissionInfo?.Name;
return dto;
}
}
}

View File

@ -8,22 +8,42 @@ using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using AsbCloudApp.Services;
using System;
namespace AsbCloudInfrastructure.Services
{
public class UserRoleService : IUserRoleService
{
private readonly CacheTable<UserRole> cacheUserRoles;
private readonly CacheTable<PermissionInfo> cachePermissionInfo;
private readonly IPermissionService permissionService;
public List<string> Includes { get; } = new();
public UserRoleService(IAsbCloudDbContext context, CacheDb cacheDb, IPermissionService permissionService)
{
cacheUserRoles = cacheDb.GetCachedTable<UserRole>((AsbCloudDbContext)context, new [] { nameof(UserRole.Permissions) });
cachePermissionInfo = cacheDb.GetCachedTable<PermissionInfo>((AsbCloudDbContext)context);
this.permissionService = permissionService;
}
public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token = default)
{
var entity = dto.Adapt<UserRole>();
var updatedEntity = await cacheUserRoles.InsertAsync(entity, token)
.ConfigureAwait(false);
await UpdatePermissionsAsync(dto, token);
await cacheUserRoles.RefreshAsync(true, token)
.ConfigureAwait(false);
return updatedEntity?.Id ?? 0;
}
public Task<int> InsertRangeAsync(IEnumerable<UserRoleDto> dtos, CancellationToken token = default)
{
throw new NotImplementedException();
//var entities = dtos.Adapt<UserRole>();
//return await cacheUserRoles.InsertAsync(entities, token).ConfigureAwait(false);
}
public async Task<IEnumerable<UserRoleDto>> GetAllAsync(CancellationToken token = default)
{
var entities = await cacheUserRoles.WhereAsync(token)
@ -40,60 +60,98 @@ namespace AsbCloudInfrastructure.Services
return dto;
}
public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token = default)
public async Task<UserRoleDto> GetByNameAsync(string name, CancellationToken token = default)
{
var entity = dto.Adapt<UserRole>();
var updatedEntity = await cacheUserRoles.InsertAsync(entity, token).ConfigureAwait(false);
if (dto.Permissions?.Any() != true)
return updatedEntity?.Id ?? 0;
foreach (var permission in dto.Permissions)
permission.IdUserRole = updatedEntity.Id;
await permissionService.InsertRangeAsync(dto.Permissions, token).ConfigureAwait(false);
await cacheUserRoles.RefreshAsync(true, token)
var entity = await cacheUserRoles.FirstOrDefaultAsync(r => r.Caption == name, token)
.ConfigureAwait(false);
return updatedEntity?.Id ?? 0;
}
public async Task<int> InsertRangeAsync(IEnumerable<UserRoleDto> dtos, CancellationToken token = default)
{
var entities = dtos.Adapt<UserRole>();
return await cacheUserRoles.InsertAsync(entities, token).ConfigureAwait(false);
var dto = entity?.Adapt<UserRoleDto>();
return dto;
}
public async Task<int> UpdateAsync(int id, UserRoleDto dto, CancellationToken token = default)
{
dto.Id = id;
var entity = dto.Adapt<UserRole>();
entity.Id = id;
await UpdatePermissionsAsync(dto, token);
await cacheUserRoles.UpsertAsync(entity, token)
.ConfigureAwait(false);
if(dto.Permissions is not null)
{
await permissionService.DeleteAllByRoleAsync(id, token)
.ConfigureAwait(false);
if (dto.Permissions.Any())
{
foreach (var permission in dto.Permissions)
permission.IdUserRole = id;
await permissionService.InsertRangeAsync(dto.Permissions, token)
.ConfigureAwait(false);
}
await cacheUserRoles.RefreshAsync(true, token)
.ConfigureAwait(false);
}
return id;
}
private IEnumerable<Permission> GetNestedPermissions(UserRole role, int counter = 10)
public List<UserRoleDto> GetNestedById(int id, int recursionLevel = 7)
{
var role = cacheUserRoles.FirstOrDefault(r => r.Id == id);
if (role is null)
return null;
var dto = role.Adapt<UserRoleDto>();
if (role.IdParent is null || recursionLevel == 0)
return new List<UserRoleDto> { dto };
var parentRoles = GetNestedById((int)role.IdParent, --recursionLevel) ??
new List<UserRoleDto>();
parentRoles.Add(dto);
return parentRoles;
}
public IEnumerable<PermissionBaseDto> GetNestedPermissions(IEnumerable<UserRoleDto> roles)
{
var permissions = new Dictionary<int, PermissionBaseDto>(16);
foreach (var roleDto in roles)
{
var role = cacheUserRoles.FirstOrDefault(r => r.Id == roleDto.Id);
var rolePermissions = GetNestedPermissions(role, 10);
if ((rolePermissions?.Any()) != true)
continue;
foreach (var newPermission in rolePermissions)
{
if (permissions.ContainsKey(newPermission.IdPermissionInfo))
{
permissions[newPermission.IdPermissionInfo].Value |= newPermission.Value;
}
else
{
permissions.Add(newPermission.IdPermissionInfo,
new PermissionBaseDto
{
IdPermissionInfo = newPermission.IdPermissionInfo,
PermissionName = newPermission.PermissionInfo?.Name ??
cachePermissionInfo.FirstOrDefault(p => p.Id == newPermission.IdPermissionInfo).Name,
Value = newPermission.Value,
});
}
}
}
return permissions.Values;
}
private async Task UpdatePermissionsAsync(UserRoleDto roleDto, CancellationToken token)
{
await permissionService.DeleteAllByRoleAsync(roleDto.Id, token)
.ConfigureAwait(false);
if (!roleDto.Permissions.Any())
return;
var newPermissions = roleDto.Permissions.Select(p => new PermissionDto
{
IdPermissionInfo = p.IdPermissionInfo,
IdUserRole = roleDto.Id,
PermissionName = p.PermissionName,
Value = p.Value,
});
await permissionService.InsertRangeAsync(newPermissions, token)
.ConfigureAwait(false);
}
private IEnumerable<Permission> GetNestedPermissions(UserRole role, int recursionLevel = 7)
{
var permissions = role.Permissions.ToList();
if (role.IdParent is null)
return permissions;
if (counter == 0)
if (recursionLevel == 0)
{
Trace.WriteLine($"User role with id: {role.Id} has more than 10 nested childs");
return permissions;
@ -103,29 +161,9 @@ namespace AsbCloudInfrastructure.Services
if (parentRole is null)
return permissions;
var parentPermissions = GetNestedPermissions(parentRole, --counter);
Merge(ref permissions, parentPermissions);
return permissions;
}
private static void Merge(ref List<Permission> permissions, IEnumerable<Permission> newPermissions)
{
foreach (var newPermission in newPermissions)
{
var permissionIndex = permissions.FindIndex(p => p.IdPermission == newPermission.IdPermission);
if (permissionIndex == -1)
permissions.Add(newPermission);
else
{
var permission = permissions[permissionIndex];
permissions[permissionIndex] = new Permission
{
IdPermission = permission.IdPermission,
IdUserRole = permission.IdUserRole,
PermissionValue = permission.PermissionValue | newPermission.PermissionValue,
};
}
}
var parentPermissions = GetNestedPermissions(parentRole, --recursionLevel);
return permissions.Union(parentPermissions);
}
public Task<int> DeleteAsync(int id, CancellationToken token = default)
@ -133,5 +171,35 @@ namespace AsbCloudInfrastructure.Services
public Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token = default)
=> cacheUserRoles.RemoveAsync(r => ids.Contains(r.Id), token);
public bool HasPermission(IEnumerable<int> rolesIds, string permissionName, int permissionMask = 0)
{
var permissionInfo = cachePermissionInfo.FirstOrDefault(p => p.Name.ToLower() == permissionName.ToLower());
if (permissionInfo is null)
return false;
if (permissionMask == 0)
permissionMask = -1;
var idPermissionInfo = permissionInfo.Id;
var roles = cacheUserRoles.Where(r => rolesIds.Contains(r.Id));
foreach (var role in roles)
if (HasPermission(role, idPermissionInfo, permissionMask))
return true;
return false;
}
private bool HasPermission(UserRole userRole, int idPermissionInfo, int permissionMask, int recursionLevel = 7)
{
if (userRole.Permissions.Any(p => p.IdPermissionInfo == idPermissionInfo && (p.Value & permissionMask) > 0))
return true;
if (userRole.IdParent is not null && recursionLevel > 0)
{
var parentRole = cacheUserRoles.FirstOrDefault(p => p.Id == userRole.IdParent);
return HasPermission(parentRole, idPermissionInfo, permissionMask, --recursionLevel);
}
return false;
}
}
}

View File

@ -0,0 +1,166 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services
{
public class UserService : IUserService
{
private readonly CacheTable<User> cacheUsers;
private readonly CacheTable<RelationUserUserRole> cacheRelationUserToRoles;
public List<string> Includes { get; }
public IUserRoleService RoleService { get; }
public UserService(IAsbCloudDbContext context, CacheDb cacheDb, IUserRoleService roleService)
{
var db = (AsbCloudDbContext)context;
cacheUsers = cacheDb.GetCachedTable<User>(
db,
new[] {
nameof(User.RelationUsersUserRoles),
nameof(User.Company),
});
cacheRelationUserToRoles = cacheDb.GetCachedTable<RelationUserUserRole>(
db,
new[] {
nameof(RelationUserUserRole.User),
nameof(RelationUserUserRole.UserRole),
});
RoleService = roleService;
}
public async Task<int> InsertAsync(UserExtendedDto dto, CancellationToken token = default)
{
var entity = dto.Adapt<User>();
var updatedEntity = await cacheUsers.InsertAsync(entity, token).ConfigureAwait(false);
await UpdateRolesCacheForUserAsync((int)updatedEntity.Id, dto.RoleNames, token);
return updatedEntity?.Id ?? 0;
}
public Task<int> InsertRangeAsync(IEnumerable<UserExtendedDto> newItems, CancellationToken token = default)
{
throw new NotImplementedException();
}
public async Task<IEnumerable<UserExtendedDto>> GetAllAsync(CancellationToken token = default)
{
var entities = (await cacheUsers.WhereAsync(token).ConfigureAwait(false))
.ToList();
if (entities.Count == 0)
return null;
var dtos = entities.Adapt<UserExtendedDto>().ToList();
for (var i = 0; i < dtos.Count; i++)
dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id);
return dtos;
}
public async Task<UserExtendedDto> GetAsync(int id, CancellationToken token = default)
{
var entity = await cacheUsers.FirstOrDefaultAsync(u=>u.Id == id, token).ConfigureAwait(false);
var dto = entity.Adapt<UserExtendedDto>();
dto.RoleNames = GetRolesNamesByIdUser(dto.Id);
return dto;
}
public async Task<int> UpdateAsync(int id, UserExtendedDto dto, CancellationToken token = default)
{
var entity = dto.Adapt<User>();
await UpdateRolesCacheForUserAsync(id, dto.RoleNames, token);
var result = await cacheUsers.UpsertAsync(entity, token)
.ConfigureAwait(false);
return result;
}
public Task<int> DeleteAsync(int id, CancellationToken token = default)
=> cacheUsers.RemoveAsync(r => r.Id == id, token);
public Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token = default)
=> cacheUsers.RemoveAsync(r => ids.Contains(r.Id), token);
private IEnumerable<string> GetRolesNamesByIdUser(int idUser)
=> GetRolesByIdUser(idUser)
?.Select(r => r.Caption)
.Distinct();
public IEnumerable<UserRoleDto> GetRolesByIdUser(int idUser)
{
var roles = cacheRelationUserToRoles.Where(r => r.IdUser == idUser);
if (roles?.Any() != true)
return null;
return roles.SelectMany(r => RoleService.GetNestedById(r.IdUserRole));
}
public IEnumerable<PermissionBaseDto> GetNestedPermissions(int idUser)
{
var roles = GetRolesByIdUser(idUser);
return RoleService.GetNestedPermissions(roles);
}
private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable<string> newRoleNames, CancellationToken token)
{
if (newRoleNames?.Any() != true)
return;
var relatrions = new List<RelationUserUserRole>(newRoleNames.Count());
foreach (var roleName in newRoleNames)
{
var role = await RoleService.GetByNameAsync(roleName, token)
.ConfigureAwait(false);
if (role != null)
relatrions.Add(new()
{
IdUser = idUser,
IdUserRole = role.Id,
});
}
await cacheRelationUserToRoles.RemoveAsync(r => r.IdUser == idUser, token)
.ConfigureAwait(false);
await cacheRelationUserToRoles.InsertAsync(relatrions, token)
.ConfigureAwait(false);
}
public bool HasAnyRoleOf(int idUser, IEnumerable<string> roleNames)
{
if(!roleNames.Any())
return true;
var userRoleNames = GetRolesNamesByIdUser(idUser);
foreach (var roleName in userRoleNames)
if (roleNames.Contains(roleName))
return true;
return false;
}
public bool HasAnyRoleOf(int idUser, IEnumerable<int> roleIds)
{
if (!roleIds.Any())
return true;
var userRoles = GetRolesByIdUser(idUser);
foreach (var role in userRoles)
if (roleIds.Contains(role.Id))
return true;
return false;
}
public bool HasPermission(int idUser, string permissionName, int permissionMask = 0)
{
var relationsToRoles = cacheRelationUserToRoles.Where(r=>r.IdUser == idUser);
if (relationsToRoles is null)
return false;
return RoleService.HasPermission(relationsToRoles.Select(r => r.IdUserRole),
permissionName,
permissionMask);
}
}
}

View File

@ -8,12 +8,12 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/user")]
[ApiController]
[Authorize]
public class AdminUserController : CrudController<UserDto, ICrudService<UserDto>>
public class AdminUserController : CrudController<UserExtendedDto, ICrudService<UserExtendedDto>>
{
public AdminUserController(ICrudService<UserDto> service)
: base(service)
{
public AdminUserController(IUserService service)
:base(service)
{}
}
}
}

View File

@ -59,7 +59,7 @@ namespace AsbCloudWebApi.Controllers
/// <param name="user">Информация о новом пользователе</param>
/// <returns code="200">Ок</returns>
[HttpPost]
public IActionResult Register(UserDto user)
public IActionResult Register(UserRegistrationDto user)
{
var code = authService.Register(user);
return code switch

View File

@ -1,8 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Middlewares