namespace MyOffice.Web.Identity; using MyOffice.Data.Models.Users; using Domain; using Data.Repositories.Users; using Repositories; using IdentityModel; using IdentityServer4.Models; using IdentityServer4.Validation; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using System.Security.Claims; using Core.Extensions; using Core.Identity; public class ExtensionGrantValidator : IExtensionGrantValidator { private readonly IUserStore> _userStore; private readonly ILogger _logger; private readonly AppUserManager _appUserManager; private readonly IUserExternalRepository _userExternalRepository; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ExternalProvidersConfig _externalProvidersConfig; private readonly IEnumerable _externalProviderValidators; public ExtensionGrantValidator( ILogger logger, IUserStore> userStore, AppUserManager appUserManager, IUserExternalRepository userExternalRepository, IHttpContextAccessor httpContextAccessor, IOptions externalProvidersConfig, IEnumerable externalProviderValidators ) { if (externalProvidersConfig == null) throw new ArgumentNullException(nameof(externalProvidersConfig)); _userStore = userStore ?? throw new ArgumentNullException(nameof(userStore)); _appUserManager = appUserManager ?? throw new ArgumentNullException(nameof(appUserManager)); _userExternalRepository = userExternalRepository ?? throw new ArgumentNullException(nameof(userExternalRepository)); _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); _externalProvidersConfig = externalProvidersConfig.Value; _externalProviderValidators = externalProviderValidators; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public string GrantType => "external"; public async Task ValidateAsync(ExtensionGrantValidationContext context) { var provider = context.Request.Raw["provider"]; if (provider == null) throw new ArgumentNullException("provider not found"); var validator = _externalProviderValidators.FirstOrDefault(x => x.Provider.EqualsIgnoreCase(provider)); if (validator == null || !validator.IsConfigured) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, $"Provider not supported: {provider}"); return; } var token = context.Request.Raw["token"]; if (token == null) throw new ArgumentNullException("token not found"); var result = await validator.ValidateAsync(token); if (!result.IsSuccessed) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, $"Token not valid"); return; } context.Result = await ProcessAsync(result.Email!, result.ExternalId!, provider, result.EmailVerified, result.FullName); } private readonly object _lock = new(); private async Task ProcessAsync( string email, string externlaId, string provider, bool isEmailConfirmed, string? fullName ) { // connect to exists user if (_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated == true) { return new GrantValidationResult(TokenRequestErrors.InvalidRequest, $"Authentication failed"); } else { var user = await _appUserManager.FindByEmailAsync(email); if (user == null) { if (!isEmailConfirmed) return new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Email not confirmed"); user = await CreateUser(email, isEmailConfirmed, fullName); if (user == null) return new GrantValidationResult(TokenRequestErrors.InvalidRequest); } var claim = await _userExternalRepository.GetByUserIdAsync(user.Id, provider); if (claim == null) claim = AddClaim(user, email, externlaId, provider); return ClaimToGrantValidationResult(claim.UserId, "", ""); } } private async Task?> CreateUser(string email, bool isEmailConfirmed, string? fullName) { var user = new ApplicationUser { Id = Guid.NewGuid(), UserName = email, Email = email, IsEmailConfirmed = isEmailConfirmed, FullName = fullName }; var password = "Qq1!_" + Guid.NewGuid(); var result = await _appUserManager.CreateAsync(user, password); if (!result.Succeeded) return null; return user; } private UserExternal AddClaim(ApplicationUser user, string email, string externalId, string provider) { lock (_lock) { var claim = _userExternalRepository.GetByUserId(user.Id, provider); if (claim != null) return claim; claim = new UserExternal { UserId = user.Id, CreatedOn = DateTime.UtcNow, Provider = provider.ToLower(), ExternalId = externalId, Email = email }; _userExternalRepository.AddUserExternal(claim); return claim; } } private GrantValidationResult ClaimToGrantValidationResult(Guid userId, string provider, string email) { var userIdStr = userId.ToString(); var claims = new List { new(JwtClaimTypes.Id, userIdStr), new(JwtClaimTypes.Subject, userIdStr), new(JwtClaimTypes.Email, email) }; var grantValidationResult = new GrantValidationResult(userIdStr, provider, claims); return grantValidationResult; } }