Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link users enhancements. #7953

Merged
merged 8 commits into from
Mar 17, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public override void PreConfigureServices(ServiceConfigurationContext context)
{
builder
.AddDefaultTokenProviders()
.AddTokenProvider<LinkUserTokenProvider>(LinkUserTokenProviderConsts.LinkUserTokenProviderName)
.AddSignInManager<AbpSignInManager>();
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Volo.Abp.Identity.AspNetCore
{
public class LinkUserTokenProvider : DataProtectorTokenProvider<IdentityUser>
{
public LinkUserTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<DataProtectionTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<IdentityUser>> logger)
: base(dataProtectionProvider, options, logger)
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Volo.Abp.Identity
{
public static class LinkUserTokenProviderConsts
{
public static string LinkUserTokenProviderName { get; set; } = "AbpLinkUser";

public static string LinkUserTokenPurpose { get; set; } = "AbpLinkUserLogin";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ public static IdentityBuilder AddAbpIdentity(this IServiceCollection services, A
return services
.AddIdentityCore<IdentityUser>(setupAction)
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>()
.AddTokenProvider<LinkUserTokenProvider>(LinkUserTokenProvider.LinkUserTokenProviderName);
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -14,6 +14,11 @@ public interface IIdentityLinkUserRepository : IBasicRepository<IdentityLinkUser
CancellationToken cancellationToken = default);

Task<List<IdentityLinkUser>> GetListAsync(
IdentityLinkUserInfo linkUserInfo,
List<IdentityLinkUserInfo> excludes = null,
CancellationToken cancellationToken = default);

Task DeleteAsync(
IdentityLinkUserInfo linkUserInfo,
CancellationToken cancellationToken = default);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
Expand All @@ -20,74 +23,128 @@ public IdentityLinkUserManager(IIdentityLinkUserRepository identityLinkUserRepos
CurrentTenant = currentTenant;
}

public virtual async Task LinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
public async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, bool includeIndirect = false, CancellationToken cancellationToken = default)
{
if (sourceLinkUser.UserId == targetLinkUser.UserId && sourceLinkUser.TenantId == targetLinkUser.TenantId)
using (CurrentTenant.Change(null))
{
return;
}
var users = await IdentityLinkUserRepository.GetListAsync(linkUserInfo, cancellationToken: cancellationToken);
if (includeIndirect == false)
{
return users;
}

if (await IsLinkedAsync(sourceLinkUser, targetLinkUser))
{
return;
var userInfos = new List<IdentityLinkUserInfo>()
{
linkUserInfo
};

var allUsers = new List<IdentityLinkUser>();
allUsers.AddRange(users);

do
{
var nextUsers = new List<IdentityLinkUserInfo>();
foreach (var user in users)
{
if (userInfos.Any(x => x.TenantId != user.SourceTenantId || x.UserId != user.SourceUserId))
{
nextUsers.Add(new IdentityLinkUserInfo(user.SourceUserId, user.SourceTenantId));
}

if (userInfos.Any(x => x.TenantId != user.TargetTenantId || x.UserId != user.TargetUserId))
{
nextUsers.Add(new IdentityLinkUserInfo(user.TargetUserId, user.TargetTenantId));
}
}

users = new List<IdentityLinkUser>();
foreach (var next in nextUsers)
{
users.AddRange(await IdentityLinkUserRepository.GetListAsync(next, userInfos, cancellationToken));
}

userInfos.AddRange(nextUsers);
allUsers.AddRange(users);
} while (users.Any());

return allUsers;
}
}

public virtual async Task LinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser, CancellationToken cancellationToken = default)
{
using (CurrentTenant.Change(null))
{
if (sourceLinkUser.UserId == targetLinkUser.UserId && sourceLinkUser.TenantId == targetLinkUser.TenantId)
{
return;
}

if (await IsLinkedAsync(sourceLinkUser, targetLinkUser, cancellationToken: cancellationToken))
{
return;
}

var userLink = new IdentityLinkUser(
GuidGenerator.Create(),
sourceLinkUser,
targetLinkUser);
await IdentityLinkUserRepository.InsertAsync(userLink, true);
await IdentityLinkUserRepository.InsertAsync(userLink, true, cancellationToken);
}
}

public virtual async Task<bool> IsLinkedAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
public virtual async Task<bool> IsLinkedAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser, bool includeIndirect = false, CancellationToken cancellationToken = default)
{
using (CurrentTenant.Change(null))
{
return await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser) != null;
if (includeIndirect)
{
return (await GetListAsync(sourceLinkUser, true, cancellationToken: cancellationToken))
.Any(x => x.SourceTenantId == targetLinkUser.TenantId && x.SourceUserId == targetLinkUser.UserId ||
x.TargetTenantId == targetLinkUser.TenantId && x.TargetUserId == targetLinkUser.UserId);
}
return await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser, cancellationToken) != null;
}
}

public virtual async Task UnlinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
public virtual async Task UnlinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser, CancellationToken cancellationToken = default)
{
if (!await IsLinkedAsync(sourceLinkUser, targetLinkUser))
{
return;
}

using (CurrentTenant.Change(null))
{
var linkedUser = await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser);
if (!await IsLinkedAsync(sourceLinkUser, targetLinkUser, cancellationToken: cancellationToken))
{
return;
}

var linkedUser = await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser, cancellationToken);
if (linkedUser != null)
{
await IdentityLinkUserRepository.DeleteAsync(linkedUser);
await IdentityLinkUserRepository.DeleteAsync(linkedUser, cancellationToken: cancellationToken);
}
}
}

public virtual async Task<string> GenerateLinkTokenAsync(IdentityLinkUserInfo targetLinkUser)
public virtual async Task<string> GenerateLinkTokenAsync(IdentityLinkUserInfo targetLinkUser, CancellationToken cancellationToken = default)
{
using (CurrentTenant.Change(targetLinkUser.TenantId))
{
var user = await UserManager.GetByIdAsync(targetLinkUser.UserId);
return await UserManager.GenerateUserTokenAsync(
user,
LinkUserTokenProvider.LinkUserTokenProviderName,
LinkUserTokenProvider.LinkUserTokenPurpose);
LinkUserTokenProviderConsts.LinkUserTokenProviderName,
LinkUserTokenProviderConsts.LinkUserTokenPurpose);
}
}

public virtual async Task<bool> VerifyLinkTokenAsync(IdentityLinkUserInfo targetLinkUser, string token)
public virtual async Task<bool> VerifyLinkTokenAsync(IdentityLinkUserInfo targetLinkUser, string token, CancellationToken cancellationToken = default)
{
using (CurrentTenant.Change(targetLinkUser.TenantId))
{
var user = await UserManager.GetByIdAsync(targetLinkUser.UserId);
return await UserManager.VerifyUserTokenAsync(
user,
LinkUserTokenProvider.LinkUserTokenProviderName,
LinkUserTokenProvider.LinkUserTokenPurpose,
LinkUserTokenProviderConsts.LinkUserTokenProviderName,
LinkUserTokenProviderConsts.LinkUserTokenPurpose,
token);
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -28,13 +28,35 @@ public virtual async Task<IdentityLinkUser> FindAsync(IdentityLinkUserInfo sourc
, cancellationToken: GetCancellationToken(cancellationToken));
}

public virtual async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
public virtual async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, List<IdentityLinkUserInfo> excludes = null,
CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync())
IQueryable<IdentityLinkUser> query = (await GetDbSetAsync())
.Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId);

if (!excludes.IsNullOrEmpty())
{
foreach (var userInfo in excludes)
{
query = query.Where(x =>
(x.SourceTenantId != userInfo.TenantId || x.SourceUserId != userInfo.UserId) &&
(x.TargetTenantId != userInfo.TenantId || x.TargetUserId != userInfo.UserId));
}
}

return await query.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}

public virtual async Task DeleteAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
{
var linkUsers = await (await GetDbSetAsync()).Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId)
.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));

await DeleteManyAsync(linkUsers, cancellationToken: cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
Expand All @@ -28,12 +27,34 @@ public virtual async Task<IdentityLinkUser> FindAsync(IdentityLinkUserInfo sourc
, cancellationToken: GetCancellationToken(cancellationToken));
}

public virtual async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
public virtual async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, List<IdentityLinkUserInfo> excludes = null,
CancellationToken cancellationToken = default)
{
return await (await GetMongoQueryableAsync(cancellationToken)).Where(x =>
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId);

if (!excludes.IsNullOrEmpty())
{
foreach (var userInfo in excludes)
{
query = query.Where(x =>
(x.SourceTenantId != userInfo.TenantId || x.SourceUserId != userInfo.UserId) &&
(x.TargetTenantId != userInfo.TenantId || x.TargetUserId != userInfo.UserId));
}
}

return await query.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}

public virtual async Task DeleteAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
{
var linkUsers = await (await GetMongoQueryableAsync(cancellationToken)).Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId)
.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));

await DeleteManyAsync(linkUsers, cancellationToken: cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;
Expand All @@ -7,14 +8,37 @@ namespace Volo.Abp.Identity.AspNetCore
{
public class LinkUserTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
{
protected IIdentityUserRepository UserRepository { get; }
protected IIdentityLinkUserRepository IdentityLinkUserRepository { get; }
protected IdentityLinkUserManager IdentityLinkUserManager { get; }
protected IdentityTestData TestData { get; }

public LinkUserTokenProvider_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
IdentityLinkUserRepository = GetRequiredService<IIdentityLinkUserRepository>();
IdentityLinkUserManager = GetRequiredService<IdentityLinkUserManager>();
TestData = GetRequiredService<IdentityTestData>();
}

[Fact]
public void LinkUserTokenProvider_Should_Be_Register()
{
var identityOptions = GetRequiredService<IOptions<IdentityOptions>>().Value;

identityOptions.Tokens.ProviderMap.ShouldContain(x =>
x.Key == LinkUserTokenProvider.LinkUserTokenProviderName &&
x.Key == LinkUserTokenProviderConsts.LinkUserTokenProviderName &&
x.Value.ProviderType == typeof(LinkUserTokenProvider));
}

[Fact]
public virtual async Task GenerateAndVerifyLinkTokenAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await IdentityLinkUserManager.GenerateLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId));
(await IdentityLinkUserManager.VerifyLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId), token)).ShouldBeTrue();

(await IdentityLinkUserManager.VerifyLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId), "123123")).ShouldBeFalse();
}
}
}