using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Sozsoft.Platform.DbStartup; using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; namespace Sozsoft.Platform.Identity; public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDependency { private const string SetupPathPrefix = "/api/setup"; private const string SetupStatusPath = "/api/setup/application-status"; private readonly IIdentitySessionRepository sessionRepository; private readonly ICurrentTenant currentTenant; private readonly IUnitOfWorkManager unitOfWorkManager; private readonly IConfiguration configuration; public PlatformSessionValidationMiddleware( IIdentitySessionRepository sessionRepository, ICurrentTenant currentTenant, IUnitOfWorkManager unitOfWorkManager, IConfiguration configuration) { this.sessionRepository = sessionRepository; this.currentTenant = currentTenant; this.unitOfWorkManager = unitOfWorkManager; this.configuration = configuration; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (ShouldSkipSessionValidation(context)) { await next(context); return; } if (context.User.Identity?.IsAuthenticated != true) { await next(context); return; } var userIdValue = context.User.FindFirst(AbpClaimTypes.UserId)?.Value; var sessionId = context.User.FindFirst(PlatformClaimTypes.SessionId.Key)?.Value; if (!Guid.TryParse(userIdValue, out var userId) || sessionId.IsNullOrWhiteSpace()) { await next(context); return; } var tenantId = ParseTenantId(context.User.FindFirst(AbpClaimTypes.TenantId)?.Value); using (currentTenant.Change(tenantId)) { try { using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false); var sessions = await sessionRepository.GetListAsync(userId: userId); var isCurrentSession = sessions.Any(session => session.SessionId == sessionId); await uow.CompleteAsync(); if (!isCurrentSession) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } } catch (Exception ex) when (IsDatabaseUnavailableException(ex)) { context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; return; } } await next(context); } /// /// Kurulum endpointleri için oturum doğrulaması ne zaman atlanır. /// /// - Durum sorgusu: anonim erişime açık, oturum bilgisine hiç ihtiyaç duymaz. /// - Diğer /api/setup istekleri: yalnızca veritabanı erişilemezken atlanır. O durumda /// oturum zaten doğrulanamaz (kayıtlar veritabanında) ve doğrulamaya çalışmak, tam da /// veritabanını onaracak endpoint'i 503 ile bloke eder. Güvenlik kaybı yoktur: veritabanı /// düşükken migration yalnızca kurulum parolasıyla çalışır, oturum yetki vermez /// (bkz. SetupController.AuthorizeMigrateRequestAsync). /// /// Veritabanı ayaktayken kurulum endpointleri de normal oturum doğrulamasından geçer; /// böylece iptal edilmiş bir oturum migration tetikleyemez. /// private bool ShouldSkipSessionValidation(HttpContext context) { var path = context.Request.Path; if (!path.StartsWithSegments(SetupPathPrefix)) { return false; } return path.StartsWithSegments(SetupStatusPath) || !DatabaseReadinessChecker.GetStatus(configuration).IsReady; } private static Guid? ParseTenantId(string value) { return !string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null; } private static bool IsDatabaseUnavailableException(Exception exception) { return exception is SqlException || exception is TimeoutException || exception is TaskCanceledException || exception is InvalidOperationException && exception.InnerException != null && IsDatabaseUnavailableException(exception.InnerException); } }