ConcurrentUserLimit tanımlamalası
This commit is contained in:
parent
c4671f7051
commit
70fb9ae499
17 changed files with 126 additions and 11 deletions
|
|
@ -10,6 +10,7 @@ public class CreateUpdateUserInput
|
|||
public string Email { get; set; }
|
||||
public string PhoneNumber { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string WorkHour { get; set; }
|
||||
public bool? IsActive { get; set; }
|
||||
public bool? LockoutEnabled { get; set; }
|
||||
public string[] RoleNames { get; set; }
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ public static class PlatformSignInResultExtensions
|
|||
{
|
||||
return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_WorkHour;
|
||||
}
|
||||
if (resultP.IsNotAllowed_ConcurrentUserLimit)
|
||||
{
|
||||
return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit;
|
||||
}
|
||||
}
|
||||
/// Added -->
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Volo.Abp;
|
|||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Identity;
|
||||
using Volo.Abp.TenantManagement;
|
||||
using Volo.Abp.Data;
|
||||
|
||||
namespace Sozsoft.Platform.ListForms.DynamicApi;
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
[Authorize(IdentityPermissions.Users.Create)]
|
||||
public async Task PostUserInsertAsync(DynamicApiBaseInput<CreateUpdateUserInput> input)
|
||||
{
|
||||
await identityUserAppService.CreateAsync(new IdentityUserCreateDto
|
||||
var user = new IdentityUserCreateDto
|
||||
{
|
||||
UserName = input.Data.Email,
|
||||
Name = input.Data.Name,
|
||||
|
|
@ -43,8 +44,11 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
IsActive = input.Data.IsActive ?? true,
|
||||
LockoutEnabled = true,
|
||||
Password = input.Data.Password,
|
||||
RoleNames = [] //input.Data.RoleNames,
|
||||
});
|
||||
RoleNames = [], //input.Data.RoleNames,
|
||||
};
|
||||
user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour);
|
||||
|
||||
await identityUserAppService.CreateAsync(user);
|
||||
}
|
||||
|
||||
[Authorize(IdentityPermissions.Users.Update)]
|
||||
|
|
@ -54,7 +58,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
{
|
||||
throw new UserFriendlyException(L["RecordNotFound"]);
|
||||
}
|
||||
var id = Guid.Parse(input.Keys[0].ToString());
|
||||
var id = Guid.Parse(input.Keys[0]!.ToString()!);
|
||||
var entity = await identityUserAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]);
|
||||
var user = new IdentityUserUpdateDto
|
||||
{
|
||||
|
|
@ -71,6 +75,10 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
{
|
||||
user.Password = input.Data.Password;
|
||||
}
|
||||
if (input.Data.WorkHour != null)
|
||||
{
|
||||
user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour);
|
||||
}
|
||||
|
||||
await identityUserAppService.UpdateAsync(id, user);
|
||||
}
|
||||
|
|
@ -94,7 +102,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
{
|
||||
throw new UserFriendlyException(L["RecordNotFound"]);
|
||||
}
|
||||
var id = Guid.Parse(input.Keys[0].ToString());
|
||||
var id = Guid.Parse(input.Keys[0]!.ToString()!);
|
||||
var entity = await identityRoleAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]);
|
||||
var role = new IdentityRoleUpdateDto
|
||||
{
|
||||
|
|
@ -142,7 +150,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
throw new UserFriendlyException(L["RecordNotFound"]);
|
||||
}
|
||||
|
||||
var id = Guid.Parse(input.Keys[0].ToString());
|
||||
var id = Guid.Parse(input.Keys[0]!.ToString()!);
|
||||
var entity = await tenantRepository.GetAsync(id)
|
||||
?? throw new EntityNotFoundException(L["RecordNotFound"]);
|
||||
|
||||
|
|
@ -176,7 +184,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
|
|||
{
|
||||
throw new UserFriendlyException(L["RecordNotFound"]);
|
||||
}
|
||||
var id = Guid.Parse(input.Keys[0].ToString());
|
||||
var id = Guid.Parse(input.Keys[0]!.ToString()!);
|
||||
await tenantRepository.DeleteAsync(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
{
|
||||
"Languages": [
|
||||
{
|
||||
"cultureName": "ar",
|
||||
|
|
@ -3113,6 +3113,12 @@
|
|||
"key": "Abp.Identity.LoginNotAllowed_WorkHour",
|
||||
"en": "You cannot sign in outside of the allowed work hours.",
|
||||
"tr": "İzin verilen iş saatleri dışında giriş yapamazsınız."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "Abp.Identity.ConcurrentUserLimitError",
|
||||
"en": "The maximum number of simultaneous users has been reached. Please try again later.",
|
||||
"tr": "Eş zamanlı kullanıcı limiti dolmuştur. Lütfen daha sonra tekrar deneyiniz."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ public static class PlatformConsts
|
|||
public const string Email = "Email";
|
||||
public const string Website = "Website";
|
||||
public const string MenuGroup = "MenuGroup";
|
||||
public const string MaxConcurrentUsers = "MaxConcurrentUsers";
|
||||
}
|
||||
|
||||
public static class AbpIdentity
|
||||
|
|
@ -107,6 +108,7 @@ public static class PlatformConsts
|
|||
public const string LoginEndDateError = GroupName + ".LoginEndDateError";
|
||||
public const string TenantIsPassive = GroupName + ".TenantIsPassive";
|
||||
public const string LoginNotAllowed_WorkHour = GroupName + ".LoginNotAllowed_WorkHour";
|
||||
public const string ConcurrentUserLimitError = GroupName + ".ConcurrentUserLimitError";
|
||||
public const string CaptchaWrongCode = GroupName + ".CaptchaWrongCode";
|
||||
public const string TwoFactorWrongCode = GroupName + ".TwoFactorWrongCode";
|
||||
public const string SignOut = GroupName + ".SignOut";
|
||||
|
|
@ -450,6 +452,7 @@ public static class PlatformConsts
|
|||
public static string LoginNotAllowed_TenantIsPassive { get; set; } = "UserCannotSignInTenantIsPassive";
|
||||
public static string LoginNotAllowed_TenantNotFound { get; set; } = "UserCannotSignInTenantNotFound";
|
||||
public static string LoginNotAllowed_WorkHour { get; set; } = "UserCannotSignInWorkHour";
|
||||
public static string LoginNotAllowed_ConcurrentUserLimit { get; set; } = "UserCannotSignInConcurrentUserLimit";
|
||||
}
|
||||
|
||||
public static class GridOptions
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ public static class PlatformModuleExtensionConfigurator
|
|||
tenantConfig.ConfigureTenant(entity =>
|
||||
{
|
||||
entity.AddOrUpdateProperty<bool>("IsActive");
|
||||
entity.AddOrUpdateProperty<int?>("MaxConcurrentUsers");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -165,5 +165,14 @@ public static class AbpTenantExtensions
|
|||
{
|
||||
return tenant.GetProperty<string>(PlatformConsts.Tenants.MenuGroup);
|
||||
}
|
||||
|
||||
public static void SetMaxConcurrentUsers(this Tenant tenant, int? maxConcurrentUsers)
|
||||
{
|
||||
tenant.SetProperty(PlatformConsts.Tenants.MaxConcurrentUsers, maxConcurrentUsers.HasValue ? (object)maxConcurrentUsers.Value : null);
|
||||
}
|
||||
public static int? GetMaxConcurrentUsers(this Tenant tenant)
|
||||
{
|
||||
return tenant.GetProperty<int?>(PlatformConsts.Tenants.MaxConcurrentUsers);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ public class PlatformSignInResult : SignInResult
|
|||
|
||||
public bool IsNotAllowed_WorkHour { get; set; }
|
||||
|
||||
public bool IsNotAllowed_ConcurrentUserLimit { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
|
|
@ -40,6 +42,7 @@ public class PlatformSignInResult : SignInResult
|
|||
ShouldChangePasswordPeriodic ? "ShouldChangePasswordPeriodic" :
|
||||
IsNotAllowed_TenantIsPassive ? "NotAllowed_TenantIsPassive" :
|
||||
IsNotAllowed_WorkHour ? "NotAllowed_WorkHour" :
|
||||
IsNotAllowed_ConcurrentUserLimit ? "NotAllowed_ConcurrentUserLimit" :
|
||||
base.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,6 +217,15 @@ public static class PlatformEfCoreEntityExtensionMappings
|
|||
}
|
||||
);
|
||||
|
||||
ObjectExtensionManager.Instance
|
||||
.MapEfCoreProperty<Tenant, int?>(
|
||||
PlatformConsts.Tenants.MaxConcurrentUsers,
|
||||
(entityBuilder, propertyBuilder) =>
|
||||
{
|
||||
propertyBuilder.HasDefaultValue(0);
|
||||
}
|
||||
);
|
||||
|
||||
ObjectExtensionManager.Instance
|
||||
.MapEfCoreProperty<PermissionDefinitionRecord, string>(
|
||||
PlatformConsts.Permissions.MenuGroup,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Sozsoft.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20260426180109_Initial")]
|
||||
[Migration("20260427070853_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -6164,6 +6164,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxConcurrentUsers")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("MenuGroup")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
|
@ -350,6 +350,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
FaxNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||
Founder = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
|
||||
MaxConcurrentUsers = table.Column<int>(type: "int", nullable: true, defaultValue: 0),
|
||||
MenuGroup = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
MobileNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||
OrganizationName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
|
|
@ -6161,6 +6161,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxConcurrentUsers")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("MenuGroup")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ public static class PlatformEventIds
|
|||
|
||||
public static EventId UserCannotSignInWorkHour =
|
||||
new(18, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_WorkHour);
|
||||
|
||||
public static EventId UserCannotSignInConcurrentUserLimit =
|
||||
new(19, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ public class PlatformLoginResult : AbpLoginResult
|
|||
PResult = PlatformLoginResultType.NotAllowedWorkHour;
|
||||
Description = L[PlatformConsts.AbpIdentity.User.LoginNotAllowed_WorkHour];
|
||||
}
|
||||
else if (resultP.IsNotAllowed_ConcurrentUserLimit)
|
||||
{
|
||||
PResult = PlatformLoginResultType.ConcurrentUserLimit;
|
||||
Description = L[PlatformConsts.AbpIdentity.User.ConcurrentUserLimitError];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ public enum PlatformLoginResultType : byte
|
|||
LoginEndDateDue,
|
||||
ShowCaptcha,
|
||||
TenantIsPassive,
|
||||
NotAllowedWorkHour
|
||||
NotAllowedWorkHour,
|
||||
ConcurrentUserLimit
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Volo.Abp.TenantManagement;
|
|||
using Volo.Abp.Timing;
|
||||
using IdentityUser = Volo.Abp.Identity.IdentityUser;
|
||||
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Sozsoft.Platform.Identity;
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
|
|||
private readonly IRepository<WorkHour, Guid> repositoryWorkHour;
|
||||
private readonly ITenantRepository tenantRepository;
|
||||
private readonly IdentityUserManager userManager;
|
||||
private readonly IIdentitySessionRepository identitySessionRepository;
|
||||
|
||||
public PlatformSignInManager(
|
||||
IdentityUserManager userManager,
|
||||
|
|
@ -47,7 +49,8 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
|
|||
IClock clock,
|
||||
IRepository<IpRestriction, Guid> repositoryIp,
|
||||
IRepository<WorkHour, Guid> repositoryWorkHour,
|
||||
ITenantRepository tenantRepository
|
||||
ITenantRepository tenantRepository,
|
||||
IIdentitySessionRepository identitySessionRepository
|
||||
) : base(
|
||||
userManager,
|
||||
contextAccessor,
|
||||
|
|
@ -64,6 +67,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
|
|||
this.repositoryWorkHour = repositoryWorkHour;
|
||||
this.tenantRepository = tenantRepository;
|
||||
this.userManager = userManager;
|
||||
this.identitySessionRepository = identitySessionRepository;
|
||||
}
|
||||
|
||||
public async Task<bool> PreSignInCheckAsync(IdentityUser user)
|
||||
|
|
@ -98,6 +102,10 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
|
|||
{
|
||||
return new PlatformSignInResult() { IsNotAllowed_WorkHour = true };
|
||||
}
|
||||
if (!await CanSignInConcurrentLimitAsync(user))
|
||||
{
|
||||
return new PlatformSignInResult() { IsNotAllowed_ConcurrentUserLimit = true };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -256,5 +264,47 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents login when the tenant's concurrent user limit is reached.
|
||||
/// Counts distinct active users (by UserId) in AbpSessions for the tenant.
|
||||
/// A user who already has an active session is not counted as a new concurrent user.
|
||||
/// </summary>
|
||||
private async Task<bool> CanSignInConcurrentLimitAsync(IdentityUser user)
|
||||
{
|
||||
if (!user.TenantId.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var tenant = await tenantRepository.FindAsync(user.TenantId.Value);
|
||||
if (tenant == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var maxConcurrentUsers = tenant.GetMaxConcurrentUsers();
|
||||
if (!maxConcurrentUsers.HasValue || maxConcurrentUsers.Value <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var sessions = await identitySessionRepository.GetListAsync();
|
||||
var activeUserCount = sessions
|
||||
.Where(s => s.UserId != user.Id)
|
||||
.Select(s => s.UserId)
|
||||
.Distinct()
|
||||
.Count();
|
||||
|
||||
if (activeUserCount >= maxConcurrentUsers.Value)
|
||||
{
|
||||
Logger.LogWarning(PlatformEventIds.UserCannotSignInConcurrentUserLimit,
|
||||
"Tenant {TenantId} concurrent user limit of {Limit} reached. Active users: {ActiveCount}.",
|
||||
user.TenantId, maxConcurrentUsers.Value, activeUserCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const PlatformLoginResultType = {
|
|||
NotAllowedIp: 19, // IpRestriction tablosu, Mesaj: Identity:IpRestrictionError
|
||||
LoginEndDateDue: 20, // LoginEndDate:>Today, Buton: ShowExtendMyLoginButton, Mesaj: Identity:LoginEndDateError, >LoginEndDate
|
||||
ShowCaptcha: 21, // AccessFailedCount>AccountCaptchaMaxFailedAccessAttempts, Sayfa: Show Captcha
|
||||
ConcurrentUserLimit: 22, // MaxConcurrentUsers limiti dolduğunda, Mesaj: Identity:ConcurrentUserLimitError
|
||||
}
|
||||
|
||||
export default PlatformLoginResultType
|
||||
|
|
|
|||
Loading…
Reference in a new issue