mirror of https://github.com/abpframework/abp.git
20 changed files with 781 additions and 40 deletions
@ -0,0 +1,270 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Data; |
|||
|
|||
public static class DataFilterExtensions |
|||
{ |
|||
private sealed class CompositeDisposable : IDisposable |
|||
{ |
|||
private readonly IDisposable[] _disposables; |
|||
private bool _disposed; |
|||
|
|||
public CompositeDisposable(IDisposable[] disposables) |
|||
{ |
|||
_disposables = disposables; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_disposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_disposed = true; |
|||
|
|||
foreach (var disposable in _disposables) |
|||
{ |
|||
disposable?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3, T4>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>(), |
|||
filter.Disable<T4>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3, T4, T5>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>(), |
|||
filter.Disable<T4>(), |
|||
filter.Disable<T5>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3, T4, T5, T6>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>(), |
|||
filter.Disable<T4>(), |
|||
filter.Disable<T5>(), |
|||
filter.Disable<T6>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3, T4, T5, T6, T7>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
where T7 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>(), |
|||
filter.Disable<T4>(), |
|||
filter.Disable<T5>(), |
|||
filter.Disable<T6>(), |
|||
filter.Disable<T7>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Disable<T1, T2, T3, T4, T5, T6, T7, T8>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
where T7 : class |
|||
where T8 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Disable<T1>(), |
|||
filter.Disable<T2>(), |
|||
filter.Disable<T3>(), |
|||
filter.Disable<T4>(), |
|||
filter.Disable<T5>(), |
|||
filter.Disable<T6>(), |
|||
filter.Disable<T7>(), |
|||
filter.Disable<T8>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3, T4>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>(), |
|||
filter.Enable<T4>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3, T4, T5>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>(), |
|||
filter.Enable<T4>(), |
|||
filter.Enable<T5>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3, T4, T5, T6>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>(), |
|||
filter.Enable<T4>(), |
|||
filter.Enable<T5>(), |
|||
filter.Enable<T6>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3, T4, T5, T6, T7>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
where T7 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>(), |
|||
filter.Enable<T4>(), |
|||
filter.Enable<T5>(), |
|||
filter.Enable<T6>(), |
|||
filter.Enable<T7>() |
|||
}); |
|||
} |
|||
|
|||
public static IDisposable Enable<T1, T2, T3, T4, T5, T6, T7, T8>(this IDataFilter filter) |
|||
where T1 : class |
|||
where T2 : class |
|||
where T3 : class |
|||
where T4 : class |
|||
where T5 : class |
|||
where T6 : class |
|||
where T7 : class |
|||
where T8 : class |
|||
{ |
|||
return new CompositeDisposable(new[] |
|||
{ |
|||
filter.Enable<T1>(), |
|||
filter.Enable<T2>(), |
|||
filter.Enable<T3>(), |
|||
filter.Enable<T4>(), |
|||
filter.Enable<T5>(), |
|||
filter.Enable<T6>(), |
|||
filter.Enable<T7>(), |
|||
filter.Enable<T8>() |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; |
|||
|
|||
public interface IAbpEfCoreCompiledQueryCacheKeyProvider |
|||
{ |
|||
string? GetCompiledQueryCacheKey(); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.MultiTenancy; |
|||
|
|||
public enum TenantUserSharingStrategy |
|||
{ |
|||
Isolated = 0, |
|||
|
|||
Shared = 1 |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
[Serializable] |
|||
public class IdentityUserPasswordChangedEto : IMultiTenant |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
} |
|||
@ -1,66 +1,206 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.Identity.Localization; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Identity |
|||
{ |
|||
public class AbpIdentityUserValidator : IUserValidator<IdentityUser> |
|||
{ |
|||
protected IStringLocalizer<IdentityResource> Localizer { get; } |
|||
protected IdentityErrorDescriber ErrorDescriber { get; } |
|||
protected IOptions<AbpMultiTenancyOptions> MultiTenancyOptions { get; } |
|||
protected IAbpDistributedLock DistributedLock { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IDataFilter<IMultiTenant> TenantFilter { get; } |
|||
protected IIdentityUserRepository UserRepository { get; } |
|||
protected IUserValidator<IdentityUser> DefaultUserValidator { get; } |
|||
|
|||
public AbpIdentityUserValidator(IStringLocalizer<IdentityResource> localizer) |
|||
public AbpIdentityUserValidator( |
|||
IdentityErrorDescriber errorDescriber, |
|||
IOptions<AbpMultiTenancyOptions> multiTenancyOptions, |
|||
IAbpDistributedLock distributedLock, |
|||
ICurrentTenant currentTenant, |
|||
IDataFilter<IMultiTenant> tenantFilter, |
|||
IIdentityUserRepository userRepository) |
|||
{ |
|||
Localizer = localizer; |
|||
ErrorDescriber = errorDescriber; |
|||
MultiTenancyOptions = multiTenancyOptions; |
|||
DistributedLock = distributedLock; |
|||
CurrentTenant = currentTenant; |
|||
TenantFilter = tenantFilter; |
|||
UserRepository = userRepository; |
|||
DefaultUserValidator = new UserValidator<IdentityUser>(ErrorDescriber); |
|||
} |
|||
|
|||
public virtual async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var describer = new IdentityErrorDescriber(); |
|||
|
|||
Check.NotNull(manager, nameof(manager)); |
|||
Check.NotNull(user, nameof(user)); |
|||
|
|||
return MultiTenancyOptions.Value.UserSharingStrategy == TenantUserSharingStrategy.Isolated |
|||
? await ValidateIsolatedUserAsync(manager, user) |
|||
: await ValidateSharedUserAsync(manager, user); |
|||
} |
|||
|
|||
protected virtual async Task<IdentityResult> ValidateIsolatedUserAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var errors = new List<IdentityError>(); |
|||
|
|||
var defaultValidationResult = await DefaultUserValidator.ValidateAsync(manager, user); |
|||
if (!defaultValidationResult.Succeeded) |
|||
{ |
|||
return defaultValidationResult; |
|||
} |
|||
|
|||
var userName = await manager.GetUserNameAsync(user); |
|||
if (userName == null) |
|||
{ |
|||
errors.Add(describer.InvalidUserName(null)); |
|||
errors.Add(ErrorDescriber.InvalidUserName(null)); |
|||
} |
|||
else |
|||
{ |
|||
var owner = await manager.FindByEmailAsync(userName); |
|||
if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) |
|||
{ |
|||
errors.Add(new IdentityError |
|||
{ |
|||
Code = "InvalidUserName", |
|||
Description = Localizer["Volo.Abp.Identity:InvalidUserName", userName] |
|||
}); |
|||
errors.Add(ErrorDescriber.InvalidUserName(userName)); |
|||
} |
|||
} |
|||
|
|||
var email = await manager.GetEmailAsync(user); |
|||
if (email == null) |
|||
{ |
|||
errors.Add(describer.InvalidEmail(null)); |
|||
errors.Add(ErrorDescriber.InvalidEmail(null)); |
|||
} |
|||
else |
|||
{ |
|||
var owner = await manager.FindByNameAsync(email); |
|||
if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) |
|||
{ |
|||
errors.Add(new IdentityError |
|||
errors.Add(ErrorDescriber.InvalidEmail(email)); |
|||
} |
|||
} |
|||
|
|||
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; |
|||
} |
|||
|
|||
protected virtual async Task<IdentityResult> ValidateSharedUserAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var errors = new List<IdentityError>(); |
|||
|
|||
var defaultValidationResult = await BuiltInValidateAsync(manager, user); |
|||
if (!defaultValidationResult.Succeeded) |
|||
{ |
|||
return defaultValidationResult; |
|||
} |
|||
|
|||
await using var handle = await DistributedLock.TryAcquireAsync(nameof(AbpIdentityUserValidator), TimeSpan.FromMinutes(1)); |
|||
if (handle == null) |
|||
{ |
|||
throw new AbpException("Could not acquire distributed lock for validating user uniqueness for shared user sharing strategy!"); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
using (TenantFilter.Disable()) |
|||
{ |
|||
var owner = await manager.FindByIdAsync(user.Id.ToString()); |
|||
var normalizedUserName = manager.NormalizeName(user.UserName); |
|||
var normalizedEmail = manager.NormalizeEmail(user.Email); |
|||
var users = await UserRepository.GetUsersByNormalizedUserNamesAsync([normalizedUserName!, normalizedEmail], true); |
|||
users.RemoveAll(x => x.Id == user.Id); |
|||
if (owner != null) |
|||
{ |
|||
Code = "InvalidEmail", |
|||
Description = Localizer["Volo.Abp.Identity:InvalidEmail", email] |
|||
}); |
|||
users.RemoveAll(x => x.NormalizedUserName == user.NormalizedUserName || x.NormalizedEmail == user.NormalizedEmail); |
|||
} |
|||
if (users.Any()) |
|||
{ |
|||
var userNames = users.Select(u => u.UserName).ToList(); |
|||
errors.Add(userNames.Contains(user.UserName) ? ErrorDescriber.InvalidUserName(user.UserName!) : ErrorDescriber.InvalidEmail(user.Email!)); |
|||
} |
|||
|
|||
users = await UserRepository.GetUsersByNormalizedEmailsAsync([normalizedUserName!, normalizedEmail], true); |
|||
users.RemoveAll(x => x.Id == user.Id); |
|||
if (owner != null) |
|||
{ |
|||
users.RemoveAll(x => x.NormalizedUserName == user.NormalizedUserName || x.NormalizedEmail == user.NormalizedEmail); |
|||
} |
|||
if (users.Any()) |
|||
{ |
|||
var emails = users.Select(u => u.Email).ToList(); |
|||
errors.Add(emails.Contains(user.Email) ? ErrorDescriber.InvalidEmail(user.Email!) : ErrorDescriber.InvalidUserName(user.UserName!)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; |
|||
} |
|||
|
|||
public virtual async Task<IdentityResult> BuiltInValidateAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var errors = await ValidateUserName(manager, user); |
|||
if (manager.Options.User.RequireUniqueEmail) |
|||
{ |
|||
errors.AddRange(await ValidateEmail(manager, user)); |
|||
} |
|||
return errors?.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; |
|||
} |
|||
|
|||
private async Task<List<IdentityError>> ValidateUserName(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var errors = new List<IdentityError>(); |
|||
var userName = await manager.GetUserNameAsync(user); |
|||
if (string.IsNullOrWhiteSpace(userName)) |
|||
{ |
|||
errors.Add(ErrorDescriber.InvalidUserName(userName)); |
|||
} |
|||
else if (!string.IsNullOrEmpty(manager.Options.User.AllowedUserNameCharacters) && |
|||
userName.Any(c => !manager.Options.User.AllowedUserNameCharacters.Contains(c))) |
|||
{ |
|||
errors.Add(ErrorDescriber.InvalidUserName(userName)); |
|||
} |
|||
else |
|||
{ |
|||
var owner = await manager.FindByNameAsync(userName); |
|||
if (owner != null && |
|||
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)) && |
|||
owner.TenantId == user.TenantId) |
|||
{ |
|||
errors.Add(ErrorDescriber.DuplicateUserName(userName)); |
|||
} |
|||
} |
|||
|
|||
return errors; |
|||
} |
|||
|
|||
// make sure email is not empty, valid, and unique
|
|||
private async Task<List<IdentityError>> ValidateEmail(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var errors = new List<IdentityError>(); |
|||
var email = await manager.GetEmailAsync(user); |
|||
if (string.IsNullOrWhiteSpace(email)) |
|||
{ |
|||
errors.Add(ErrorDescriber.InvalidEmail(email)); |
|||
return errors; |
|||
} |
|||
if (!new EmailAddressAttribute().IsValid(email)) |
|||
{ |
|||
errors.Add(ErrorDescriber.InvalidEmail(email)); |
|||
return errors; |
|||
} |
|||
var owner = await manager.FindByEmailAsync(email); |
|||
if (owner != null && |
|||
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)) && |
|||
owner.TenantId == user.TenantId) |
|||
{ |
|||
errors.Add(ErrorDescriber.DuplicateEmail(email)); |
|||
} |
|||
return errors; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Users; |
|||
|
|||
[Serializable] |
|||
[EventName("Volo.Abp.Users.InviteUserToTenantRequested")] |
|||
public class InviteUserToTenantRequestedEto : IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
} |
|||
Loading…
Reference in new issue