167 lines
5.3 KiB
C#
167 lines
5.3 KiB
C#
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<ApplicationUser<Guid>> _userStore;
|
|
private readonly ILogger<ExtensionGrantValidator> _logger;
|
|
private readonly AppUserManager _appUserManager;
|
|
private readonly IUserExternalRepository _userExternalRepository;
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
private readonly ExternalProvidersConfig _externalProvidersConfig;
|
|
private readonly IEnumerable<IExternalProviderValidator> _externalProviderValidators;
|
|
|
|
public ExtensionGrantValidator(
|
|
ILogger<ExtensionGrantValidator> logger,
|
|
IUserStore<ApplicationUser<Guid>> userStore,
|
|
AppUserManager appUserManager,
|
|
IUserExternalRepository userExternalRepository,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
IOptions<ExternalProvidersConfig> externalProvidersConfig,
|
|
IEnumerable<IExternalProviderValidator> 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<GrantValidationResult> 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<ApplicationUser<Guid>?> CreateUser(string email, bool isEmailConfirmed, string? fullName)
|
|
{
|
|
var user = new ApplicationUser<Guid>
|
|
{
|
|
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<Guid> 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<Claim>
|
|
{
|
|
new(JwtClaimTypes.Id, userIdStr),
|
|
new(JwtClaimTypes.Subject, userIdStr),
|
|
new(JwtClaimTypes.Email, email)
|
|
};
|
|
|
|
var grantValidationResult = new GrantValidationResult(userIdStr, provider, claims);
|
|
|
|
return grantValidationResult;
|
|
}
|
|
} |