Browse Source

Merge pull request #5190 from abpframework/maliming/linkuser

Introduce link user feature of Identity module.
pull/5726/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
2a0267324c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/CurrentTenant.cs
  2. 24
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/CurrentPrincipalAccessorExtensions.cs
  3. 5
      modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs
  4. 1
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs
  5. 20
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityLinkUserRepository.cs
  6. 44
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUser.cs
  7. 17
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserInfo.cs
  8. 86
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLinkManager.cs
  9. 17
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/LinkUserTokenProvider.cs
  10. 39
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs
  11. 5
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs
  12. 4
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs
  13. 16
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
  14. 4
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs
  15. 5
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs
  16. 1
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs
  17. 4
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs
  18. 38
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs
  19. 20
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs
  20. 86
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs
  21. 7
      modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityLinkUserRepository_Tests.cs
  22. 10
      modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityLinkUserRepository_Tests.cs
  23. 20
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs
  24. 58
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityLinkUserRepository_Tests.cs
  25. 7
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/en.json
  26. 5
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/tr.json
  27. 5
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/zh-Hans.json
  28. 3
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs
  29. 151
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs

2
framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/CurrentTenant.cs

@ -1,4 +1,4 @@
using System;
using System;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.MultiTenancy

24
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/CurrentPrincipalAccessorExtensions.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
namespace Volo.Abp.Security.Claims
{
public static class CurrentPrincipalAccessorExtensions
{
public static IDisposable Change(this ICurrentPrincipalAccessor currentPrincipalAccessor, Claim claim)
{
return currentPrincipalAccessor.Change(new[] {claim});
}
public static IDisposable Change(this ICurrentPrincipalAccessor currentPrincipalAccessor, IEnumerable<Claim> claims)
{
return currentPrincipalAccessor.Change(new ClaimsIdentity(claims));
}
public static IDisposable Change(this ICurrentPrincipalAccessor currentPrincipalAccessor, ClaimsIdentity claimsIdentity)
{
return currentPrincipalAccessor.Change(new ClaimsPrincipal(claimsIdentity));
}
}
}

5
modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs

@ -29,11 +29,12 @@ namespace Microsoft.Extensions.DependencyInjection
//AbpRoleStore
services.TryAddScoped<IdentityRoleStore>();
services.TryAddScoped(typeof(IRoleStore<IdentityRole>), provider => provider.GetService(typeof(IdentityRoleStore)));
return services
.AddIdentityCore<IdentityUser>(setupAction)
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>();
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>()
.AddTokenProvider<LinkUserTokenProvider>(LinkUserTokenProvider.LinkUserTokenProviderName);
}
}
}

1
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs

@ -7,7 +7,6 @@ using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace Volo.Abp.Identity
{

20
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityLinkUserRepository.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Identity
{
public interface IIdentityLinkUserRepository : IBasicRepository<IdentityLinkUser, Guid>
{
Task<IdentityLinkUser> FindAsync(
IdentityLinkUserInfo sourceLinkUserInfo,
IdentityLinkUserInfo targetLinkUserInfo,
CancellationToken cancellationToken = default);
Task<List<IdentityLinkUser>> GetListAsync(
IdentityLinkUserInfo linkUserInfo,
CancellationToken cancellationToken = default);
}
}

44
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUser.cs

@ -0,0 +1,44 @@
using System;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Identity
{
public class IdentityLinkUser : BasicAggregateRoot<Guid>
{
public virtual Guid SourceUserId { get; protected set; }
public virtual Guid? SourceTenantId { get; protected set; }
public virtual Guid TargetUserId { get; protected set; }
public virtual Guid? TargetTenantId { get; protected set; }
/// <summary>
/// Initializes a new instance of <see cref="IdentityLinkUser"/>.
/// </summary>
protected IdentityLinkUser()
{
}
public IdentityLinkUser(Guid id, IdentityLinkUserInfo sourceUser, IdentityLinkUserInfo targetUser)
: base(id)
{
SourceUserId = sourceUser.UserId;
SourceTenantId = sourceUser.TenantId;
TargetUserId = targetUser.UserId;
TargetTenantId = targetUser.TenantId;
}
public IdentityLinkUser(Guid id, Guid sourceUserId, Guid? sourceTenantId, Guid targetUserId, Guid? targetTenantId)
: base(id)
{
SourceUserId = sourceUserId;
SourceTenantId = sourceTenantId;
TargetUserId = targetUserId;
TargetTenantId = targetTenantId;
}
}
}

17
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserInfo.cs

@ -0,0 +1,17 @@
using System;
namespace Volo.Abp.Identity
{
public class IdentityLinkUserInfo
{
public virtual Guid UserId { get; set; }
public virtual Guid? TenantId { get; set; }
public IdentityLinkUserInfo(Guid userId, Guid? tenantId)
{
UserId = userId;
TenantId = tenantId;
}
}
}

86
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLinkManager.cs

@ -0,0 +1,86 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity
{
public class IdentityLinkUserManager : DomainService
{
protected IIdentityLinkUserRepository IdentityLinkUserRepository { get; }
protected IdentityUserManager UserManager { get; }
protected new ICurrentTenant CurrentTenant { get; }
public IdentityLinkUserManager(IIdentityLinkUserRepository identityLinkUserRepository, IdentityUserManager userManager, ICurrentTenant currentTenant)
{
IdentityLinkUserRepository = identityLinkUserRepository;
UserManager = userManager;
CurrentTenant = currentTenant;
}
public virtual async Task LinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
{
if (sourceLinkUser.UserId == targetLinkUser.UserId && sourceLinkUser.TenantId == targetLinkUser.TenantId)
{
return;
}
if (await IsLinkedAsync(sourceLinkUser, targetLinkUser))
{
return;
}
var userLink = new IdentityLinkUser(
GuidGenerator.Create(),
sourceLinkUser,
targetLinkUser);
await IdentityLinkUserRepository.InsertAsync(userLink, true);
}
public virtual async Task<bool> IsLinkedAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
{
return await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser) != null;
}
public virtual async Task UnlinkAsync(IdentityLinkUserInfo sourceLinkUser, IdentityLinkUserInfo targetLinkUser)
{
if (!await IsLinkedAsync(sourceLinkUser, targetLinkUser))
{
return;
}
var linkedUser = await IdentityLinkUserRepository.FindAsync(sourceLinkUser, targetLinkUser);
if (linkedUser != null)
{
await IdentityLinkUserRepository.DeleteAsync(linkedUser);
}
}
public virtual async Task<string> GenerateLinkTokenAsync(IdentityLinkUserInfo targetLinkUser)
{
using (CurrentTenant.Change(targetLinkUser.TenantId))
{
var user = await UserManager.GetByIdAsync(targetLinkUser.UserId);
return await UserManager.GenerateUserTokenAsync(
user,
LinkUserTokenProvider.LinkUserTokenProviderName,
LinkUserTokenProvider.LinkUserTokenPurpose);
}
}
public virtual async Task<bool> VerifyLinkTokenAsync(IdentityLinkUserInfo targetLinkUser, string token)
{
using (CurrentTenant.Change(targetLinkUser.TenantId))
{
var user = await UserManager.GetByIdAsync(targetLinkUser.UserId);
return await UserManager.VerifyUserTokenAsync(
user,
LinkUserTokenProvider.LinkUserTokenProviderName,
LinkUserTokenProvider.LinkUserTokenPurpose,
token);
}
}
}
}

17
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/LinkUserTokenProvider.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace Volo.Abp.Identity
{
public class LinkUserTokenProvider : TotpSecurityStampBasedTokenProvider<IdentityUser>
{
public const string LinkUserTokenProviderName = "AbpLinkUser";
public const string LinkUserTokenPurpose = "AbpLinkUserLogin";
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<IdentityUser> manager, IdentityUser user)
{
return Task.FromResult(false);
}
}
}

39
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
public class EfCoreIdentityLinkUserRepository : EfCoreRepository<IIdentityDbContext, IdentityLinkUser, Guid>, IIdentityLinkUserRepository
{
public EfCoreIdentityLinkUserRepository(IDbContextProvider<IIdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<IdentityLinkUser> FindAsync(IdentityLinkUserInfo sourceLinkUserInfo, IdentityLinkUserInfo targetLinkUserInfo, CancellationToken cancellationToken = default)
{
return await DbSet.FirstOrDefaultAsync(x =>
x.SourceUserId == sourceLinkUserInfo.UserId && x.SourceTenantId == sourceLinkUserInfo.TenantId &&
x.TargetUserId == targetLinkUserInfo.UserId && x.TargetTenantId == targetLinkUserInfo.TenantId ||
x.TargetUserId == sourceLinkUserInfo.UserId && x.TargetTenantId == sourceLinkUserInfo.TenantId &&
x.SourceUserId == targetLinkUserInfo.UserId && x.SourceTenantId == targetLinkUserInfo.TenantId
, cancellationToken: GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
{
return await DbSet.Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId)
.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}
}
}

5
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.SecurityLog;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
@ -16,6 +15,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
DbSet<OrganizationUnit> OrganizationUnits { get; set; }
DbSet<IdentitySecurityLog> IdentitySecurityLogs { get; set; }
DbSet<IdentitySecurityLog> SecurityLogs { get; set; }
DbSet<IdentityLinkUser> LinkUsers { get; set; }
}
}

4
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs

@ -18,7 +18,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
public DbSet<IdentitySecurityLog> IdentitySecurityLogs { get; set; }
public DbSet<IdentitySecurityLog> SecurityLogs { get; set; }
public DbSet<IdentityLinkUser> LinkUsers { get; set; }
public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
: base(options)

16
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs

@ -233,6 +233,22 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
b.HasIndex(x => new { x.TenantId, x.UserId });
});
builder.Entity<IdentityLinkUser>(b =>
{
b.ToTable(options.TablePrefix + "LinkUsers", options.Schema);
b.ConfigureByConvention();
b.HasIndex(x => new
{
UserId = x.SourceUserId,
TenantId = x.SourceTenantId,
LinkedUserId = x.TargetUserId,
LinkedTenantId = x.TargetTenantId
}).IsUnique();
});
}
}
}

4
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs

@ -15,7 +15,9 @@ namespace Volo.Abp.Identity.MongoDB
public IMongoCollection<OrganizationUnit> OrganizationUnits => Collection<OrganizationUnit>();
public IMongoCollection<IdentitySecurityLog> IdentitySecurityLogs => Collection<IdentitySecurityLog>();
public IMongoCollection<IdentitySecurityLog> SecurityLogs => Collection<IdentitySecurityLog>();
public IMongoCollection<IdentityLinkUser> LinkUsers => Collection<IdentityLinkUser>();
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{

5
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs

@ -41,6 +41,11 @@ namespace Volo.Abp.Identity.MongoDB
{
b.CollectionName = options.CollectionPrefix + "SecurityLogs";
});
builder.Entity<IdentityLinkUser>(b =>
{
b.CollectionName = options.CollectionPrefix + "LinkUsers";
});
}
}
}

1
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs

@ -19,6 +19,7 @@ namespace Volo.Abp.Identity.MongoDB
options.AddRepository<IdentityClaimType, MongoIdentityRoleRepository>();
options.AddRepository<OrganizationUnit, MongoIdentityRoleRepository>();
options.AddRepository<IdentitySecurityLog, MongoIdentitySecurityLogRepository>();
options.AddRepository<IdentityLinkUser, MongoIdentityLinkUserRepository>();
});
}
}

4
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs

@ -15,6 +15,8 @@ namespace Volo.Abp.Identity.MongoDB
IMongoCollection<OrganizationUnit> OrganizationUnits { get; }
IMongoCollection<IdentitySecurityLog> IdentitySecurityLogs { get; }
IMongoCollection<IdentitySecurityLog> SecurityLogs { get; }
IMongoCollection<IdentityLinkUser> LinkUsers { get; }
}
}

38
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
namespace Volo.Abp.Identity.MongoDB
{
public class MongoIdentityLinkUserRepository : MongoDbRepository<IAbpIdentityMongoDbContext, IdentityLinkUser, Guid>, IIdentityLinkUserRepository
{
public MongoIdentityLinkUserRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public async Task<IdentityLinkUser> FindAsync(IdentityLinkUserInfo sourceLinkUserInfo, IdentityLinkUserInfo targetLinkUserInfo, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().FirstOrDefaultAsync(x =>
x.SourceUserId == sourceLinkUserInfo.UserId && x.SourceTenantId == sourceLinkUserInfo.TenantId &&
x.TargetUserId == targetLinkUserInfo.UserId && x.TargetTenantId == targetLinkUserInfo.TenantId ||
x.TargetUserId == sourceLinkUserInfo.UserId && x.TargetTenantId == sourceLinkUserInfo.TenantId &&
x.SourceUserId == targetLinkUserInfo.UserId && x.SourceTenantId == targetLinkUserInfo.TenantId
, cancellationToken: GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityLinkUser>> GetListAsync(IdentityLinkUserInfo linkUserInfo, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().Where(x =>
x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId ||
x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId)
.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}
}
}

20
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;
namespace Volo.Abp.Identity.AspNetCore
{
public class LinkUserTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
{
[Fact]
public void LinkUserTokenProvider_Should_Be_Register()
{
var identityOptions = GetRequiredService<IOptions<IdentityOptions>>().Value;
identityOptions.Tokens.ProviderMap.ShouldContain(x =>
x.Key == LinkUserTokenProvider.LinkUserTokenProviderName &&
x.Value.ProviderType == typeof(LinkUserTokenProvider));
}
}
}

86
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs

@ -0,0 +1,86 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.Identity
{
public class IdentityLinkUserManager_Tests : AbpIdentityDomainTestBase
{
protected IIdentityUserRepository UserRepository { get; }
protected IIdentityLinkUserRepository IdentityLinkUserRepository { get; }
protected IdentityLinkUserManager IdentityLinkUserManager { get; }
protected IdentityTestData TestData { get; }
public IdentityLinkUserManager_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
IdentityLinkUserRepository = GetRequiredService<IIdentityLinkUserRepository>();
IdentityLinkUserManager = GetRequiredService<IdentityLinkUserManager>();
TestData = GetRequiredService<IdentityTestData>();
}
[Fact]
public virtual async Task LinkAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var neo = await UserRepository.GetAsync(TestData.UserNeoId);
(await IdentityLinkUserRepository.FindAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId))).ShouldBeNull();
await IdentityLinkUserManager.LinkAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId));
var linkUser = await IdentityLinkUserRepository.FindAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId));
linkUser.ShouldNotBeNull();
linkUser.SourceUserId.ShouldBe(john.Id);
linkUser.SourceTenantId.ShouldBe(john.TenantId);
linkUser.TargetUserId.ShouldBe(neo.Id);
linkUser.TargetTenantId.ShouldBe(neo.TenantId);
}
[Fact]
public virtual async Task UnlinkAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var david = await UserRepository.GetAsync(TestData.UserDavidId);
(await IdentityLinkUserRepository.FindAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId))).ShouldNotBeNull();
await IdentityLinkUserManager.UnlinkAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId));
(await IdentityLinkUserRepository.FindAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId))).ShouldBeNull();
}
[Fact]
public virtual async Task IsLinkedAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var david = await UserRepository.GetAsync(TestData.UserDavidId);
var neo = await UserRepository.GetAsync(TestData.UserNeoId);
(await IdentityLinkUserManager.IsLinkedAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId))).ShouldBeTrue();
(await IdentityLinkUserManager.IsLinkedAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId))).ShouldBeFalse();
}
[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();
}
}
}

7
modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityLinkUserRepository_Tests.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Identity.EntityFrameworkCore
{
public class IdentityLinkUserRepository_Tests : IdentityLinkUserRepository_Tests<AbpIdentityEntityFrameworkCoreTestModule>
{
}
}

10
modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityLinkUserRepository_Tests.cs

@ -0,0 +1,10 @@
using Xunit;
namespace Volo.Abp.Identity.MongoDB
{
[Collection(MongoTestCollection.Name)]
public class IdentityLinkUserRepository_Tests : IdentityLinkUserRepository_Tests<AbpIdentityMongoDbTestModule>
{
}
}

20
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs

@ -19,6 +19,8 @@ namespace Volo.Abp.Identity
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IdentityTestData _testData;
private readonly OrganizationUnitManager _organizationUnitManager;
private readonly IIdentityLinkUserRepository _identityLinkUserRepository;
private readonly IdentityLinkUserManager _identityLinkUserManager;
private IdentityRole _adminRole;
private IdentityRole _moderatorRole;
@ -36,7 +38,9 @@ namespace Volo.Abp.Identity
IIdentitySecurityLogRepository identitySecurityLogRepository,
ILookupNormalizer lookupNormalizer,
IdentityTestData testData,
OrganizationUnitManager organizationUnitManager)
OrganizationUnitManager organizationUnitManager,
IIdentityLinkUserRepository identityLinkUserRepository,
IdentityLinkUserManager identityLinkUserManager)
{
_guidGenerator = guidGenerator;
_userRepository = userRepository;
@ -46,6 +50,8 @@ namespace Volo.Abp.Identity
_testData = testData;
_organizationUnitRepository = organizationUnitRepository;
_organizationUnitManager = organizationUnitManager;
_identityLinkUserRepository = identityLinkUserRepository;
_identityLinkUserManager = identityLinkUserManager;
_identitySecurityLogRepository = identitySecurityLogRepository;
}
@ -54,6 +60,7 @@ namespace Volo.Abp.Identity
await AddRoles();
await AddOrganizationUnits();
await AddUsers();
await AddLinkUsers();
await AddClaimTypes();
await AddSecurityLogs();
}
@ -128,6 +135,17 @@ namespace Volo.Abp.Identity
await _userRepository.InsertAsync(neo);
}
private async Task AddLinkUsers()
{
var john = await _userRepository.GetAsync(_testData.UserJohnId);
var david = await _userRepository.GetAsync(_testData.UserDavidId);
var neo = await _userRepository.GetAsync(_testData.UserNeoId);
await _identityLinkUserManager.LinkAsync(new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId));
await _identityLinkUserManager.LinkAsync(new IdentityLinkUserInfo(david.Id, david.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId));
}
private async Task AddClaimTypes()
{

58
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityLinkUserRepository_Tests.cs

@ -0,0 +1,58 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.Identity
{
public abstract class IdentityLinkUserRepository_Tests<TStartupModule> : AbpIdentityTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected IIdentityUserRepository UserRepository { get; }
protected IIdentityLinkUserRepository IdentityLinkUserRepository { get; }
protected IdentityTestData TestData { get; }
public IdentityLinkUserRepository_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
IdentityLinkUserRepository = GetRequiredService<IIdentityLinkUserRepository>();
TestData = GetRequiredService<IdentityTestData>();
}
[Fact]
public async Task FindAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var david = await UserRepository.GetAsync(TestData.UserDavidId);
var neo = await UserRepository.GetAsync(TestData.UserNeoId);
var johnAndDavidLinkUser = await IdentityLinkUserRepository.FindAsync(
new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(david.Id, david.TenantId));
johnAndDavidLinkUser.ShouldNotBeNull();
johnAndDavidLinkUser.SourceUserId.ShouldBe(john.Id);
johnAndDavidLinkUser.SourceTenantId.ShouldBe(john.TenantId);
johnAndDavidLinkUser.TargetUserId.ShouldBe(david.Id);
johnAndDavidLinkUser.TargetTenantId.ShouldBe(david.TenantId);
(await IdentityLinkUserRepository.FindAsync(
new IdentityLinkUserInfo(john.Id, john.TenantId),
new IdentityLinkUserInfo(neo.Id, neo.TenantId))).ShouldBeNull();
}
[Fact]
public async Task GetListAsync()
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var david = await UserRepository.GetAsync(TestData.UserDavidId);
var neo = await UserRepository.GetAsync(TestData.UserNeoId);
var davidLinkUsers = await IdentityLinkUserRepository.GetListAsync(new IdentityLinkUserInfo(david.Id, david.TenantId));
davidLinkUsers.ShouldNotBeNull();
davidLinkUsers.ShouldContain(x => x.SourceUserId == john.Id && x.SourceTenantId == john.TenantId);
davidLinkUsers.ShouldContain(x => x.TargetUserId == neo.Id && x.TargetTenantId == neo.TenantId);
}
}
}

7
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/en.json

@ -1,4 +1,4 @@
{
{
"culture": "en",
"texts": {
"Volo.IdentityServer:DuplicateIdentityResourceName": "Identity Resource name already exist: {Name}",
@ -7,6 +7,7 @@
"UserLockedOut": "The user account has been locked out due to invalid login attempts. Please wait a while and try again.",
"InvalidUserNameOrPassword": "Invalid username or password!",
"LoginIsNotAllowed": "You are not allowed to login! You need to confirm your email/phone number.",
"InvalidUsername": "Invalid username or password!"
"InvalidUsername": "Invalid username or password!",
"TheTargetUserIsNotLinkedToYou": "The target user is not linked to you!"
}
}
}

5
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/tr.json

@ -7,6 +7,7 @@
"UserLockedOut": "Kullanıcı hesabı hatalı giriş denemeleri nedeniyle kilitlenmiştir. Lütfen bir süre bekleyip tekrar deneyin.",
"InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!",
"LoginIsNotAllowed": "Giriş yapamazsınız! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.",
"InvalidUsername": "Kullanıcı adı ya da şifre geçersiz!"
"InvalidUsername": "Kullanıcı adı ya da şifre geçersiz!",
"TheTargetUserIsNotLinkedToYou": "Hedef kullanıcı sizinle bağlantılı değil!"
}
}
}

5
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/zh-Hans.json

@ -7,6 +7,7 @@
"UserLockedOut": "登录失败,用户账户已被锁定.请稍后再试.",
"InvalidUserNameOrPassword": "用户名或密码错误!",
"LoginIsNotAllowed": "无法登录!你需要验证邮箱地址/手机号.",
"InvalidUsername": "用户名或密码错误!"
"InvalidUsername": "用户名或密码错误!",
"TheTargetUserIsNotLinkedToYou": "目标用户未和你有关联!"
}
}
}

3
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs

@ -9,6 +9,7 @@ using Volo.Abp.Caching;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.AspNetIdentity;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.IdentityResources;
@ -94,6 +95,8 @@ namespace Volo.Abp.IdentityServer
identityServerBuilder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources"));
identityServerBuilder.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources"));
}
identityServerBuilder.AddExtensionGrantValidator<LinkLoginExtensionGrantValidator>();
}
public override void PostConfigureServices(ServiceConfigurationContext context)

151
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Volo.Abp.IdentityServer.AspNetIdentity
{
public class LinkLoginExtensionGrantValidator : IExtensionGrantValidator
{
public const string ExtensionGrantType = "LinkLogin";
public string GrantType => ExtensionGrantType;
protected ITokenValidator TokenValidator { get; }
protected IdentityLinkUserManager IdentityLinkUserManager { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICurrentUser CurrentUser { get; }
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; }
protected IdentityUserManager UserManager { get; }
protected IdentitySecurityLogManager IdentitySecurityLogManager { get; }
protected ILogger<LinkLoginExtensionGrantValidator> Logger { get; }
protected IStringLocalizer<AbpIdentityServerResource> Localizer { get; }
public LinkLoginExtensionGrantValidator(
ITokenValidator tokenValidator,
IdentityLinkUserManager identityLinkUserManager,
ICurrentTenant currentTenant,
ICurrentUser currentUser,
IdentityUserManager userManager,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IdentitySecurityLogManager identitySecurityLogManager,
ILogger<LinkLoginExtensionGrantValidator> logger,
IStringLocalizer<AbpIdentityServerResource> localizer)
{
TokenValidator = tokenValidator;
IdentityLinkUserManager = identityLinkUserManager;
CurrentTenant = currentTenant;
CurrentUser = currentUser;
UserManager = userManager;
CurrentPrincipalAccessor = currentPrincipalAccessor;
IdentitySecurityLogManager = identitySecurityLogManager;
Logger = logger;
Localizer = localizer;
}
public virtual async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var accessToken = context.Request.Raw["access_token"];
if (accessToken.IsNullOrWhiteSpace())
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_access_token"
};
return;
}
var result = await TokenValidator.ValidateAccessTokenAsync(accessToken);
if (result.IsError)
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = result.Error,
ErrorDescription = result.ErrorDescription
};
return;
}
using (CurrentPrincipalAccessor.Change(result.Claims))
{
if (!Guid.TryParse(context.Request.Raw["LinkUserId"], out var linkUserId))
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_link_user_id"
};
return;
}
Guid? linkTenantId = null;
if (!context.Request.Raw["LinkTenantId"].IsNullOrWhiteSpace())
{
if (!Guid.TryParse(context.Request.Raw["LinkTenantId"], out var parsedGuid))
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_link_tenant_id"
};
return;
}
linkTenantId = parsedGuid;
}
var isLinked = await IdentityLinkUserManager.IsLinkedAsync(
new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id),
new IdentityLinkUserInfo(linkUserId, linkTenantId));
if (isLinked)
{
using (CurrentTenant.Change(linkTenantId))
{
var user = await UserManager.GetByIdAsync(linkUserId);
var sub = await UserManager.GetUserIdAsync(user);
var additionalClaims = new List<Claim>();
await AddCustomClaimsAsync(additionalClaims, user, context);
context.Result = new GrantValidationResult(
sub,
GrantType,
additionalClaims.ToArray()
);
}
}
else
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = Localizer["TheTargetUserIsNotLinkedToYou"]
};
}
}
}
protected virtual Task AddCustomClaimsAsync(List<Claim> customClaims, IdentityUser user, ExtensionGrantValidationContext context)
{
if (user.TenantId.HasValue)
{
customClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString()));
}
return Task.CompletedTask;
}
}
}
Loading…
Cancel
Save