sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs
2026-04-28 00:29:03 +03:00

322 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Extensions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.TenantManagement;
using Volo.Abp.Timing;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace Sozsoft.Platform.Identity;
public interface IPlatformSignInManager
{
Task<bool> PreSignInCheckAsync(IdentityUser user);
Task<bool> CheckConcurrentLimitAsync(IdentityUser user);
}
public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
{
private readonly IClock clock;
private readonly IRepository<IpRestriction, Guid> repositoryIp;
private readonly IRepository<WorkHour, Guid> repositoryWorkHour;
private readonly ITenantRepository tenantRepository;
private readonly IdentityUserManager userManager;
private readonly IIdentitySessionRepository identitySessionRepository;
private readonly ICurrentTenant currentTenant;
public PlatformSignInManager(
IdentityUserManager userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<IdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<IdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<IdentityUser> confirmation,
IOptions<AbpIdentityOptions> options,
ISettingProvider settingProvider,
IClock clock,
IRepository<IpRestriction, Guid> repositoryIp,
IRepository<WorkHour, Guid> repositoryWorkHour,
ITenantRepository tenantRepository,
IIdentitySessionRepository identitySessionRepository,
ICurrentTenant currentTenant
) : base(
userManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation,
options,
settingProvider)
{
this.clock = clock;
this.repositoryIp = repositoryIp;
this.repositoryWorkHour = repositoryWorkHour;
this.tenantRepository = tenantRepository;
this.userManager = userManager;
this.identitySessionRepository = identitySessionRepository;
this.currentTenant = currentTenant;
}
public async Task<bool> PreSignInCheckAsync(IdentityUser user)
{
var result = await PreSignInCheck(user);
return result?.Succeeded ?? true;
}
protected override async Task<SignInResult> PreSignInCheck(IdentityUser user)
{
var result = await base.PreSignInCheck(user);
if (result == null)
{
if (!await CanSignInVerifiedAsync(user))
{
return new PlatformSignInResult() { IsNotAllowed_NotVerified = true };
}
if (!CanSignInEndDate(user))
{
return new PlatformSignInResult() { IsNotAllowed_LoginEndDateDue = true };
}
if (!await CanSignInIpAsync(user))
{
return new PlatformSignInResult() { IsNotAllowed_NotAllowedIp = true };
}
if (!await CanSignInTenantActiveAsync(user))
{
return new PlatformSignInResult() { IsNotAllowed_TenantIsPassive = true };
}
if (!await CanSignInWorkHourAsync(user))
{
return new PlatformSignInResult() { IsNotAllowed_WorkHour = true };
}
if (!await CheckConcurrentLimitAsync(user))
{
return new PlatformSignInResult() { IsNotAllowed_ConcurrentUserLimit = true };
}
}
else
{
// Bu kontrol modules\identity\src\Volo.Abp.Identity.AspNetCore\Volo\Abp\Identity\AspNetCore\AbpSignInManager.cs
// içinde zaten yapılıyor fakat her senaryoya NotAllowed diyor, bizim ayrıt etmemiz gerektiği için tekrar yapıyoruz.
if (user.ShouldChangePasswordOnNextLogin)
{
return new PlatformSignInResult() { ShouldChangePasswordOnNextLogin = true };
}
if (await userManager.ShouldPeriodicallyChangePasswordAsync(user))
{
return new PlatformSignInResult() { ShouldChangePasswordPeriodic = true };
}
}
return result;
}
/// <summary>
/// Used to prevent login outside of defined work hours.
/// </summary>
private async Task<bool> CanSignInWorkHourAsync(IdentityUser user)
{
var workHourName = user.GetWorkHour();
if (string.IsNullOrWhiteSpace(workHourName))
{
return true;
}
var workHours = await repositoryWorkHour.GetListAsync(a => a.Name == workHourName);
if (workHours.IsNullOrEmpty())
{
return true;
}
var now = clock.Now;
var currentTime = now.TimeOfDay;
var dayOfWeek = now.DayOfWeek;
var isAllowed = workHours.Any(wh =>
{
var dayMatches = dayOfWeek switch
{
DayOfWeek.Monday => wh.Monday == true,
DayOfWeek.Tuesday => wh.Tuesday == true,
DayOfWeek.Wednesday => wh.Wednesday == true,
DayOfWeek.Thursday => wh.Thursday == true,
DayOfWeek.Friday => wh.Friday == true,
DayOfWeek.Saturday => wh.Saturday == true,
DayOfWeek.Sunday => wh.Sunday == true,
_ => false
};
if (!dayMatches) return false;
return currentTime >= wh.StartTime.TimeOfDay && currentTime <= wh.EndTime.TimeOfDay;
});
if (!isAllowed)
{
Logger.LogWarning(PlatformEventIds.UserCannotSignInWorkHour, "User cannot sign in outside work hours.");
return false;
}
return true;
}
/// <summary>
/// Tenant IsActive
/// </summary>
private async Task<bool> CanSignInTenantActiveAsync(IdentityUser user)
{
if (!user.TenantId.HasValue)
{
return true;
}
var tenant = await tenantRepository.FindAsync(user.TenantId.Value);
if (tenant == null)
{
return false;
}
if (!tenant.GetIsActive())
{
Logger.LogWarning(PlatformEventIds.UserCannotSignInTenantIsPassive, "Tenant is inactive.");
return false;
}
return true;
}
/// <summary>
/// Used to prevent login for additional scenarios;
/// Account Not Verified
/// </summary>
private async Task<bool> CanSignInVerifiedAsync(IdentityUser user)
{
var rva = await SettingProvider.GetAsync<bool>(PlatformConsts.AbpIdentity.Profile.General.RequireVerifiedAccount);
if (rva && !user.GetIsVerified())
{
Logger.LogWarning(PlatformEventIds.UserCannotSignInWithoutVerifiedAccount, "User cannot sign in without a verified account.");
return false;
}
return true;
}
/// <summary>
/// Used to prevent login if end date is due
/// </summary>
private bool CanSignInEndDate(IdentityUser user)
{
var endDate = user.GetLoginEndDate();
if (endDate != null && clock.Now > endDate)
{
//izin yok
Logger.LogWarning(PlatformEventIds.UserCannotSignInLoginEndDateDue, "User cannot sign in, login end date is due.");
return false;
}
//izin ver
return true;
}
/// <summary>
/// Used to prevent login for allowed IPs;
/// IP Not Allowed
/// </summary>
private async Task<bool> CanSignInIpAsync(IdentityUser user)
{
// User, Role, Global Restrictions
var roles = await UserManager.GetRolesAsync(user);
//var roleIds = roleManager.Roles.Where(a => roles.Contains(a.Name)).Select(a => a.Id).ToArray();
var restrictions = await repositoryIp.GetListAsync(a =>
(a.ResourceType == "User" && a.ResourceId == user.UserName) ||
(a.ResourceType == "Role" && roles.Contains(a.ResourceId)) ||
(a.ResourceType == "Global"));
if (!restrictions.IsNullOrEmpty())
{
var ip = Context.GetRemoteIPAddress();
var ipString = ip?.ToString();
if (ip == null || (ip != null && !restrictions.Select(a => a.IP).Any(a => a == ipString)))
{
//izin yok
Logger.LogWarning(PlatformEventIds.UserCannotSignInWithoutAllowedIp, "User cannot sign in without an allowed IP.");
return false;
}
}
//izin ver
return true;
}
/// <summary>
/// Tenant'a tanımlı MaxConcurrentUsers limitini aşmamak için login'i engeller.
/// AbpSessions tablosundaki aktif oturumları sayar (farklı UserId'lere göre distinct).
/// Kullanıcının kendisinin zaten oturumu varsa (refresh senaryosu) yeni concurrent user sayılmaz.
/// </summary>
public async Task<bool> CheckConcurrentLimitAsync(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;
}
// Tenant bağlamını explicit olarak set et — hem password hem refresh token akışlarında
// doğru tenant izolasyonu için güvenli bir şekilde değiştirilir.
using (currentTenant.Change(user.TenantId))
{
var sessions = await identitySessionRepository.GetListAsync();
// Bu kullanıcı hariç diğer distinct aktif kullanıcı sayısını hesapla.
// Kullanıcının kendi oturumu varsa (refresh) o kişi tekrar sayılmaz.
var otherActiveUserCount = sessions
.Where(s => s.UserId != user.Id)
.Select(s => s.UserId)
.Distinct()
.Count();
if (otherActiveUserCount >= maxConcurrentUsers.Value)
{
Logger.LogWarning(PlatformEventIds.UserCannotSignInConcurrentUserLimit,
"Tenant {TenantId} concurrent user limit of {Limit} reached. Active users: {ActiveCount}.",
user.TenantId, maxConcurrentUsers.Value, otherActiveUserCount);
return false;
}
}
return true;
}
}