diff --git a/aspnet-core/LINGYUN.MicroService.All.slnx b/aspnet-core/LINGYUN.MicroService.All.slnx
index d70ae4710..60c1adda7 100644
--- a/aspnet-core/LINGYUN.MicroService.All.slnx
+++ b/aspnet-core/LINGYUN.MicroService.All.slnx
@@ -396,6 +396,7 @@
+
diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.slnx b/aspnet-core/LINGYUN.MicroService.SingleProject.slnx
index 2c995b9b7..ee04e035f 100644
--- a/aspnet-core/LINGYUN.MicroService.SingleProject.slnx
+++ b/aspnet-core/LINGYUN.MicroService.SingleProject.slnx
@@ -274,6 +274,7 @@
+
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
index 98eb4313f..f91aea66c 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
@@ -62,9 +62,9 @@ namespace LINGYUN.Abp.MicroService.AuthServer;
typeof(AbpOpenIddictSmsModule),
typeof(AbpOpenIddictWeChatModule),
typeof(AbpOpenIddictLinkUserModule),
- typeof(AbpOpenIddictImpersonationModule),
typeof(AbpOpenIddictPortalModule),
typeof(AbpOpenIddictWeChatWorkModule),
+ typeof(AbpOpenIddictImpersonationModule),
typeof(AbpIdentityOrganizaztionUnitsModule),
typeof(AbpPermissionManagementDomainIdentityModule),
typeof(AuthServerMigrationsEntityFrameworkCoreModule),
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
new file mode 100644
index 000000000..b749b83d7
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
@@ -0,0 +1,36 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using LINGYUN.Abp.MicroService.AuthServer.Pages.Abp.MultiTenancy
+@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
+@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization
+@using static LINGYUN.Abp.MicroService.AuthServer.Pages.Abp.MultiTenancy.TenantSwitchModalModel
+@model TenantSwitchModalModel
+@inject IHtmlLocalizer L
+@{
+ Layout = null;
+}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
new file mode 100644
index 000000000..83b6b574f
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
@@ -0,0 +1,118 @@
+using LINGYUN.Platform.Portal;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.AspNetCore.MultiTenancy;
+using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
+using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization;
+using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
+using Volo.Abp.MultiTenancy;
+
+#nullable enable
+namespace LINGYUN.Abp.MicroService.AuthServer.Pages.Abp.MultiTenancy;
+
+public class TenantSwitchModalModel : AbpPageModel
+{
+ [BindProperty]
+ public TenantInfoModel Input { get; set; } = default!;
+
+ public List AvailableTenants { get; set; } = new();
+
+ protected ITenantStore TenantStore { get; }
+ protected ITenantNormalizer TenantNormalizer { get; }
+ protected AbpAspNetCoreMultiTenancyOptions Options { get; }
+ protected IEnterpriseRepository EnterpriseRepository { get; }
+ public TenantSwitchModalModel(
+ ITenantStore tenantStore,
+ ITenantNormalizer tenantNormalizer,
+ IOptions options,
+ IEnterpriseRepository enterpriseRepository)
+ {
+ TenantStore = tenantStore;
+ TenantNormalizer = tenantNormalizer;
+ Options = options.Value;
+ EnterpriseRepository = enterpriseRepository;
+
+ LocalizationResourceType = typeof(AbpUiMultiTenancyResource);
+ }
+
+ public async virtual Task OnGetAsync()
+ {
+ await LoadAvailableTenants();
+ if (AvailableTenants.Count > 0)
+ {
+ Input = new SelectTenantInfoModel
+ {
+ AvailableTenants = AvailableTenants
+ };
+ }
+ else
+ {
+ Input = new TenantInfoModel();
+ }
+
+ if (CurrentTenant.IsAvailable)
+ {
+ var tenant = await TenantStore.FindAsync(CurrentTenant.GetId());
+ Input.Name = tenant?.Name;
+ }
+ }
+
+ public virtual async Task OnPostAsync()
+ {
+ Guid? tenantId = null;
+ if (!Input.Name.IsNullOrEmpty())
+ {
+ var tenant = await TenantStore.FindAsync(TenantNormalizer.NormalizeName(Input.Name!)!);
+ if (tenant == null && Guid.TryParse(Input.Name, out var id))
+ {
+ tenant = await TenantStore.FindAsync(id);
+ }
+ if (tenant == null)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotExist", Input.Name!]);
+ }
+
+ if (!tenant.IsActive)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotAvailable", Input.Name!]);
+ }
+
+ tenantId = tenant.Id;
+ }
+
+ AbpMultiTenancyCookieHelper.SetTenantCookie(HttpContext, tenantId, Options.TenantKey);
+ }
+ private async Task LoadAvailableTenants()
+ {
+ var enterprises = await EnterpriseRepository.GetEnterprisesInTenantListAsync(maxResultCount: 25);
+ if (enterprises.Count > 0)
+ {
+ AvailableTenants = enterprises
+ .Select(enterprise =>
+ new SelectListItem(
+ enterprise.Name, enterprise.TenantId?.ToString()))
+ .Union([new SelectListItem("Default", "")])
+ .ToList();
+ }
+ }
+
+ public class TenantInfoModel
+ {
+ public virtual string? Name { get; set; }
+ }
+
+ public class SelectTenantInfoModel : TenantInfoModel
+ {
+ public List AvailableTenants { get; set; } = new();
+
+ [SelectItems(nameof(AvailableTenants))]
+ public override string? Name { get; set; }
+ }
+}
+#nullable disable
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml
new file mode 100644
index 000000000..225780c2c
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
\ No newline at end of file
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs
index 90d83c431..7beb47dde 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs
@@ -182,7 +182,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(PasswordLoginInput.ReturnUrl, PasswordLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public override async Task OnPostPhoneNumberLogin(string action)
@@ -237,7 +237,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(PhoneLoginInput.ReturnUrl, PhoneLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public override async Task OnPostQrCodeLogin(string action)
@@ -305,7 +305,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(QrCodeLoginInput.ReturnUrl, QrCodeLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
}
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml
index 943d86377..4be781478 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml
@@ -20,7 +20,7 @@
-
@L["LogInUsingYourProviderAccount", Model.LinkTenantAndUserName] →
+
@L["LogInUsingYourProviderAccount", @Model.LinkTenantAndUserName] →
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/IdentitySessionDto.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/IdentitySessionDto.cs
index 77f8b7d63..da79e346a 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/IdentitySessionDto.cs
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/IdentitySessionDto.cs
@@ -2,7 +2,7 @@
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.Identity;
-public class IdentitySessionDto : EntityDto
+public class IdentitySessionDto : ExtensibleEntityDto
{
public string SessionId { get; set; }
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore/LINGYUN/Abp/Identity/AspNetCore/TotpService.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore/LINGYUN/Abp/Identity/AspNetCore/TotpService.cs
deleted file mode 100644
index 6c6b38c89..000000000
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore/LINGYUN/Abp/Identity/AspNetCore/TotpService.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Net;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace LINGYUN.Abp.Identity.AspNetCore;
-
-///
-/// 参考
-/// Microsoft: https://github.com/dotnet/dotnet/blob/main/src/aspnetcore/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
-///
-#nullable enable
-internal class TotpService
-{
- private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
- private static readonly Encoding _encoding = new UTF8Encoding(false, true);
-#if NETSTANDARD2_0 || NETFRAMEWORK
- private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-#endif
-
- internal static int ComputeTotp(
-#if NET6_0_OR_GREATER
- byte[] key,
-#else
- HashAlgorithm hashAlgorithm,
-#endif
- ulong timestepNumber,
- byte[]? modifierBytes)
- {
- // # of 0's = length of pin
- const int Mod = 1000000;
-
- // See https://tools.ietf.org/html/rfc4226
- // We can add an optional modifier
-#if NET6_0_OR_GREATER
- Span timestepAsBytes = stackalloc byte[sizeof(long)];
- var res = BitConverter.TryWriteBytes(timestepAsBytes, IPAddress.HostToNetworkOrder((long)timestepNumber));
- Debug.Assert(res);
-#else
- var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
-#endif
-
-#if NET6_0_OR_GREATER
- Span modifierCombinedBytes = timestepAsBytes;
- if (modifierBytes is not null)
- {
- modifierCombinedBytes = ApplyModifier(timestepAsBytes, modifierBytes);
- }
- Span hash = stackalloc byte[HMACSHA1.HashSizeInBytes];
- res = HMACSHA1.TryHashData(key, modifierCombinedBytes, hash, out var written);
- Debug.Assert(res);
- Debug.Assert(written == hash.Length);
-#else
- var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifierBytes));
-#endif
-
- // Generate DT string
- var offset = hash[hash.Length - 1] & 0xf;
- Debug.Assert(offset + 4 < hash.Length);
- var binaryCode = (hash[offset] & 0x7f) << 24
- | (hash[offset + 1] & 0xff) << 16
- | (hash[offset + 2] & 0xff) << 8
- | (hash[offset + 3] & 0xff);
-
- return binaryCode % Mod;
- }
-
- private static byte[] ApplyModifier(Span input, byte[] modifierBytes)
- {
- var combined = new byte[checked(input.Length + modifierBytes.Length)];
- input.CopyTo(combined);
- Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
- return combined;
- }
-
- // More info: https://tools.ietf.org/html/rfc6238#section-4
- private static ulong GetCurrentTimeStepNumber()
- {
-#if NETSTANDARD2_0 || NETFRAMEWORK
- var delta = DateTime.UtcNow - _unixEpoch;
-#else
- var delta = DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch;
-#endif
- return (ulong)(delta.Ticks / _timestep.Ticks);
- }
-
- public static int GenerateCode(byte[] securityToken, string? modifier = null)
- {
- ArgumentNullException.ThrowIfNull(securityToken);
-
- // Allow a variance of no greater than 9 minutes in either direction
- var currentTimeStep = GetCurrentTimeStepNumber();
-
- var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
-#if NET6_0_OR_GREATER
- return ComputeTotp(securityToken, currentTimeStep, modifierBytes);
-#else
- using (var hashAlgorithm = new HMACSHA1(securityToken))
- {
- return ComputeTotp(hashAlgorithm, currentTimeStep, modifierBytes);
- }
-#endif
- }
-
- public static bool ValidateCode(byte[] securityToken, int code, string? modifier = null)
- {
- ArgumentNullException.ThrowIfNull(securityToken);
-
- // Allow a variance of no greater than 9 minutes in either direction
- var currentTimeStep = GetCurrentTimeStepNumber();
-
-#if !NET6_0_OR_GREATER
- using (var hashAlgorithm = new HMACSHA1(securityToken))
-#endif
- {
- var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
- for (var i = -2; i <= 2; i++)
- {
-#if NET6_0_OR_GREATER
- var computedTotp = ComputeTotp(securityToken, (ulong)((long)currentTimeStep + i), modifierBytes);
-#else
- var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifierBytes);
-#endif
- if (computedTotp == code)
- {
- return true;
- }
- }
- }
-
- // No match
- return false;
- }
-}
-
-#nullable disable
diff --git a/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
index 0e15f1592..9ec4e4262 100644
--- a/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
+++ b/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
@@ -2,7 +2,6 @@
using Elsa.Options;
using LINGYUN.Abp.Aliyun.Localization;
using LINGYUN.Abp.BackgroundTasks;
-using LINGYUN.Abp.DataProtectionManagement;
using LINGYUN.Abp.ExceptionHandling;
using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.Exporter.MiniExcel;
@@ -43,8 +42,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi;
-using Microsoft.OpenApi.Models;
-using MiniExcelLibs.Attributes;
using OpenIddict.Server;
using OpenIddict.Server.AspNetCore;
using Quartz;
diff --git a/aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs b/aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs
index 9d80a45fd..1b66dd7c6 100644
--- a/aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs
+++ b/aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs
@@ -1,5 +1,5 @@
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.MultiTenancy;
@@ -22,7 +22,7 @@ public class TenantHeaderParamter : IOperationFilter
{
if (_multiTenancyOptions.IsEnabled)
{
- operation.Parameters = operation.Parameters ?? new List();
+ operation.Parameters ??= new List();
operation.Parameters.Add(new OpenApiParameter
{
Name = _aspNetCoreMultiTenancyOptions.TenantKey,
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile b/aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile
index aecb0c1af..886b6e334 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile
@@ -10,6 +10,7 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
EXPOSE 80/tcp
VOLUME [ "./app/blobs" ]
+VOLUME [ "./app/certs" ]
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
index fb0ab2aec..247c750a9 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
@@ -188,6 +188,7 @@
+
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
index 318442de3..7325f337f 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
@@ -1,6 +1,8 @@
using LINGYUN.Abp.BlobManagement.MimeCheck;
+using LINGYUN.Abp.Identity.AspNetCore;
using LINGYUN.Abp.Identity.Jobs;
using LINGYUN.Abp.Notifications.Calendar;
+using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
namespace LY.MicroService.Applications.Single;
@@ -13,6 +15,8 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpSerilogEnrichersUniqueIdModule),
// Serilog模块
typeof(AbpAspNetCoreSerilogModule),
+ // 身份认证模块 扩展UserToken验证
+ typeof(AbpIdentityAspNetCoreModule),
// 身份认证模块 会话管理集成
typeof(AbpIdentityAspNetCoreSessionModule),
// 身份认证模块 会话中间件
@@ -398,6 +402,7 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpAspNetCoreMvcIdempotentWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpHttpClientIdentityModelWebModule),
+ typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpSwashbuckleModule),
typeof(AbpMailKitModule),
typeof(AbpAutofacModule),
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
new file mode 100644
index 000000000..1555af5b6
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
@@ -0,0 +1,36 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using LY.MicroService.Applications.Single.Pages.Abp.MultiTenancy
+@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
+@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization
+@using static LY.MicroService.Applications.Single.Pages.Abp.MultiTenancy.TenantSwitchModalModel
+@model TenantSwitchModalModel
+@inject IHtmlLocalizer L
+@{
+ Layout = null;
+}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
new file mode 100644
index 000000000..a4a9302c5
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
@@ -0,0 +1,112 @@
+using LINGYUN.Platform.Portal;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.Extensions.Options;
+using Volo.Abp.AspNetCore.MultiTenancy;
+using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
+using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization;
+using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
+
+#nullable enable
+namespace LY.MicroService.Applications.Single.Pages.Abp.MultiTenancy;
+
+public class TenantSwitchModalModel : AbpPageModel
+{
+ [BindProperty]
+ public TenantInfoModel Input { get; set; } = default!;
+
+ public List AvailableTenants { get; set; } = new();
+
+ protected ITenantStore TenantStore { get; }
+ protected ITenantNormalizer TenantNormalizer { get; }
+ protected AbpAspNetCoreMultiTenancyOptions Options { get; }
+ protected IEnterpriseRepository EnterpriseRepository { get; }
+ public TenantSwitchModalModel(
+ ITenantStore tenantStore,
+ ITenantNormalizer tenantNormalizer,
+ IOptions options,
+ IEnterpriseRepository enterpriseRepository)
+ {
+ TenantStore = tenantStore;
+ TenantNormalizer = tenantNormalizer;
+ Options = options.Value;
+ EnterpriseRepository = enterpriseRepository;
+
+ LocalizationResourceType = typeof(AbpUiMultiTenancyResource);
+ }
+
+ public async virtual Task OnGetAsync()
+ {
+ await LoadAvailableTenants();
+ if (AvailableTenants.Count > 0)
+ {
+ Input = new SelectTenantInfoModel
+ {
+ AvailableTenants = AvailableTenants
+ };
+ }
+ else
+ {
+ Input = new TenantInfoModel();
+ }
+
+ if (CurrentTenant.IsAvailable)
+ {
+ var tenant = await TenantStore.FindAsync(CurrentTenant.GetId());
+ Input.Name = tenant?.Name;
+ }
+ }
+
+ public virtual async Task OnPostAsync()
+ {
+ Guid? tenantId = null;
+ if (!Input.Name.IsNullOrEmpty())
+ {
+ var tenant = await TenantStore.FindAsync(TenantNormalizer.NormalizeName(Input.Name!)!);
+ if (tenant == null && Guid.TryParse(Input.Name, out var id))
+ {
+ tenant = await TenantStore.FindAsync(id);
+ }
+ if (tenant == null)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotExist", Input.Name!]);
+ }
+
+ if (!tenant.IsActive)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotAvailable", Input.Name!]);
+ }
+
+ tenantId = tenant.Id;
+ }
+
+ AbpMultiTenancyCookieHelper.SetTenantCookie(HttpContext, tenantId, Options.TenantKey);
+ }
+ private async Task LoadAvailableTenants()
+ {
+ var enterprises = await EnterpriseRepository.GetEnterprisesInTenantListAsync(maxResultCount: 25);
+ if (enterprises.Count > 0)
+ {
+ AvailableTenants = enterprises
+ .Select(enterprise =>
+ new SelectListItem(
+ enterprise.Name, enterprise.TenantId?.ToString()))
+ .Union([new SelectListItem("Default", "")])
+ .ToList();
+ }
+ }
+
+ public class TenantInfoModel
+ {
+ public virtual string? Name { get; set; }
+ }
+
+ public class SelectTenantInfoModel : TenantInfoModel
+ {
+ public List AvailableTenants { get; set; } = new();
+
+ [SelectItems(nameof(AvailableTenants))]
+ public override string? Name { get; set; }
+ }
+}
+#nullable disable
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/_ViewImports.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/_ViewImports.cshtml
new file mode 100644
index 000000000..225780c2c
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Abp/MultiTenancy/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
\ No newline at end of file
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs
index 70c4577c3..7e406a7f9 100644
--- a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs
+++ b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs
@@ -1,8 +1,10 @@
using DotNetCore.CAP;
using LINGYUN.Abp.AspNetCore.MultiTenancy;
+using LINGYUN.Abp.Identity;
using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.LocalizationManagement;
using LINGYUN.Abp.OpenIddict.AspNetCore.Session;
+using LINGYUN.Abp.OpenIddict.Impersonation;
using LINGYUN.Abp.OpenIddict.LinkUser;
using LINGYUN.Abp.OpenIddict.Portal;
using LINGYUN.Abp.OpenIddict.Sms;
@@ -399,6 +401,12 @@ public partial class AuthServerModule
options.RefreshTokenReuseLeeway = lifetime.GetValue("RefreshTokenReuseLeeway", options.RefreshTokenReuseLeeway);
options.UserCodeLifetime = lifetime.GetValue("UserCode", options.UserCodeLifetime);
});
+
+ Configure(options =>
+ {
+ options.ImpersonationPermission = IdentityPermissions.Users.Impersonation;
+ options.ImpersonationTenantPermission = "AbpSaas.Tenants.Impersonation";
+ });
}
private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false)
{
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs
index 86093fdb6..b0eb45961 100644
--- a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs
+++ b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs
@@ -17,6 +17,7 @@ using LINGYUN.Abp.Identity.OrganizaztionUnits;
using LINGYUN.Abp.Identity.Session.AspNetCore;
using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.OpenIddict.AspNetCore.Session;
+using LINGYUN.Abp.OpenIddict.Impersonation;
using LINGYUN.Abp.OpenIddict.LinkUser;
using LINGYUN.Abp.OpenIddict.Portal;
using LINGYUN.Abp.OpenIddict.Sms;
@@ -27,6 +28,7 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using LINGYUN.Abp.Sms.Platform;
using LINGYUN.Abp.Telemetry.OpenTelemetry;
using LINGYUN.Abp.Telemetry.SkyWalking;
+using LINGYUN.Platform.EntityFrameworkCore;
using LY.MicroService.AuthServer.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -66,6 +68,7 @@ namespace LY.MicroService.AuthServer;
typeof(AbpOpenIddictLinkUserModule),
typeof(AbpOpenIddictPortalModule),
typeof(AbpOpenIddictWeChatWorkModule),
+ typeof(AbpOpenIddictImpersonationModule),
typeof(AbpIdentityOrganizaztionUnitsModule),
typeof(AbpPermissionManagementDomainIdentityModule),
typeof(AuthServerMigrationsEntityFrameworkCoreModule),
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj b/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj
index cdc357f83..f3b2708a7 100644
--- a/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj
+++ b/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj
@@ -72,6 +72,7 @@
+
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
new file mode 100644
index 000000000..4be97216f
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml
@@ -0,0 +1,36 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using LY.MicroService.AuthServer.Pages.Abp.MultiTenancy
+@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
+@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization
+@using static LY.MicroService.AuthServer.Pages.Abp.MultiTenancy.TenantSwitchModalModel
+@model TenantSwitchModalModel
+@inject IHtmlLocalizer L
+@{
+ Layout = null;
+}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
new file mode 100644
index 000000000..812e734a0
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs
@@ -0,0 +1,118 @@
+using LINGYUN.Platform.Portal;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.AspNetCore.MultiTenancy;
+using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
+using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization;
+using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
+using Volo.Abp.MultiTenancy;
+
+#nullable enable
+namespace LY.MicroService.AuthServer.Pages.Abp.MultiTenancy;
+
+public class TenantSwitchModalModel : AbpPageModel
+{
+ [BindProperty]
+ public TenantInfoModel Input { get; set; } = default!;
+
+ public List AvailableTenants { get; set; } = new();
+
+ protected ITenantStore TenantStore { get; }
+ protected ITenantNormalizer TenantNormalizer { get; }
+ protected AbpAspNetCoreMultiTenancyOptions Options { get; }
+ protected IEnterpriseRepository EnterpriseRepository { get; }
+ public TenantSwitchModalModel(
+ ITenantStore tenantStore,
+ ITenantNormalizer tenantNormalizer,
+ IOptions options,
+ IEnterpriseRepository enterpriseRepository)
+ {
+ TenantStore = tenantStore;
+ TenantNormalizer = tenantNormalizer;
+ Options = options.Value;
+ EnterpriseRepository = enterpriseRepository;
+
+ LocalizationResourceType = typeof(AbpUiMultiTenancyResource);
+ }
+
+ public async virtual Task OnGetAsync()
+ {
+ await LoadAvailableTenants();
+ if (AvailableTenants.Count > 0)
+ {
+ Input = new SelectTenantInfoModel
+ {
+ AvailableTenants = AvailableTenants
+ };
+ }
+ else
+ {
+ Input = new TenantInfoModel();
+ }
+
+ if (CurrentTenant.IsAvailable)
+ {
+ var tenant = await TenantStore.FindAsync(CurrentTenant.GetId());
+ Input.Name = tenant?.Name;
+ }
+ }
+
+ public virtual async Task OnPostAsync()
+ {
+ Guid? tenantId = null;
+ if (!Input.Name.IsNullOrEmpty())
+ {
+ var tenant = await TenantStore.FindAsync(TenantNormalizer.NormalizeName(Input.Name!)!);
+ if (tenant == null && Guid.TryParse(Input.Name, out var id))
+ {
+ tenant = await TenantStore.FindAsync(id);
+ }
+ if (tenant == null)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotExist", Input.Name!]);
+ }
+
+ if (!tenant.IsActive)
+ {
+ throw new UserFriendlyException(L["GivenTenantIsNotAvailable", Input.Name!]);
+ }
+
+ tenantId = tenant.Id;
+ }
+
+ AbpMultiTenancyCookieHelper.SetTenantCookie(HttpContext, tenantId, Options.TenantKey);
+ }
+ private async Task LoadAvailableTenants()
+ {
+ var enterprises = await EnterpriseRepository.GetEnterprisesInTenantListAsync(maxResultCount: 25);
+ if (enterprises.Count > 0)
+ {
+ AvailableTenants = enterprises
+ .Select(enterprise =>
+ new SelectListItem(
+ enterprise.Name, enterprise.TenantId?.ToString()))
+ .Union([new SelectListItem("Default", "")])
+ .ToList();
+ }
+ }
+
+ public class TenantInfoModel
+ {
+ public virtual string? Name { get; set; }
+ }
+
+ public class SelectTenantInfoModel : TenantInfoModel
+ {
+ public List AvailableTenants { get; set; } = new();
+
+ [SelectItems(nameof(AvailableTenants))]
+ public override string? Name { get; set; }
+ }
+}
+#nullable disable
diff --git a/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml
new file mode 100644
index 000000000..225780c2c
--- /dev/null
+++ b/aspnet-core/services/LY.MicroService.AuthServer/Pages/Abp/MultiTenancy/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
\ No newline at end of file