diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx
index bf4fcb03f..e4a7bf1c3 100644
--- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx
+++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx
@@ -401,6 +401,7 @@
+
@@ -411,6 +412,8 @@
+
+
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/AdminServiceMigrationsEntityFrameworkCoreModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/AdminServiceMigrationsEntityFrameworkCoreModule.cs
index 7c9944faa..3fd95bc16 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/AdminServiceMigrationsEntityFrameworkCoreModule.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/AdminServiceMigrationsEntityFrameworkCoreModule.cs
@@ -15,6 +15,7 @@ using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.MessageService;
using LINGYUN.Abp.Notifications;
using LINGYUN.Abp.OpenIddict;
+using LINGYUN.Abp.PermissionManagement.EntityFrameworkCore;
using LINGYUN.Abp.ProjectManagement;
using LINGYUN.Abp.RulesEngineManagement;
using LINGYUN.Abp.Saas;
@@ -32,7 +33,6 @@ using Volo.Abp.EntityFrameworkCore.PostgreSql;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
-using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
namespace LINGYUN.Abp.MicroService.AdminService;
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore.csproj
index 9f44a4cad..feeef0c8d 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore.csproj
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AdminService.EntityFrameworkCore.csproj
@@ -18,7 +18,6 @@
-
@@ -38,6 +37,7 @@
+
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeFeatureDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeFeatureDefinitionManager.cs
index 1bcefc35c..e7c395b1f 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeFeatureDefinitionManager.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeFeatureDefinitionManager.cs
@@ -1,8 +1,7 @@
using System.Collections.Immutable;
using Volo.Abp.DependencyInjection;
-using Volo.Abp.Features;
-namespace LINGYUN.Abp.MicroService.AdminService.Overrides;
+namespace Volo.Abp.Features;
[Dependency(ReplaceServices = true)]
public class MergeFeatureDefinitionManager : FeatureDefinitionManager
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergePermissionDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergePermissionDefinitionManager.cs
index 5d3f92222..f224a1dd1 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergePermissionDefinitionManager.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergePermissionDefinitionManager.cs
@@ -1,9 +1,8 @@
using System.Collections.Immutable;
-using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
-namespace LINGYUN.Abp.MicroService.AdminService.Overrides;
+namespace Volo.Abp.Authorization.Permissions;
[Dependency(ReplaceServices = true)]
public class MergePermissionDefinitionManager : PermissionDefinitionManager
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeSettingDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeSettingDefinitionManager.cs
index 7eb4ee257..6ed3c40de 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeSettingDefinitionManager.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeSettingDefinitionManager.cs
@@ -1,8 +1,7 @@
using System.Collections.Immutable;
using Volo.Abp.DependencyInjection;
-using Volo.Abp.Settings;
-namespace LINGYUN.Abp.MicroService.AdminService.Overrides;
+namespace Volo.Abp.Settings;
[Dependency(ReplaceServices = true)]
public class MergeSettingDefinitionManager : SettingDefinitionManager
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeTemplateDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeTemplateDefinitionManager.cs
index 9fe58573a..467966c11 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeTemplateDefinitionManager.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AdminService/Overrides/MergeTemplateDefinitionManager.cs
@@ -2,7 +2,7 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.TextTemplating;
-namespace LINGYUN.Abp.MicroService.AdminService.Overrides;
+namespace Volo.Abp.TextTemplatin;
[Dependency(ReplaceServices = true)]
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs
index 29398ecb0..6bd44bf85 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs
@@ -480,7 +480,8 @@ public class OpenIddictDataSeeder : OpenIddictDataSeedContributorBase, ITransien
OpenIddictConstants.GrantTypes.Password,
OpenIddictConstants.GrantTypes.RefreshToken,
// TODO: 引用项目?
- "link_user"],
+ "link_user",
+ "impersonation"],
[OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Roles,
@@ -544,7 +545,8 @@ public class OpenIddictDataSeeder : OpenIddictDataSeedContributorBase, ITransien
[OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.RefreshToken,
// TODO: 引用项目?
- "link_user"],
+ "link_user",
+ "impersonation"],
[OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Roles,
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.Configure.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.Configure.cs
index e281f164c..cce2f4351 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.Configure.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.Configure.cs
@@ -1,9 +1,11 @@
using DotNetCore.CAP;
using LINGYUN.Abp.AspNetCore.MultiTenancy;
+using LINGYUN.Abp.Identity;
using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.LocalizationManagement;
using LINGYUN.Abp.MicroService.AuthServer.Ui.Branding;
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;
@@ -200,6 +202,7 @@ public partial class AuthServerModule
{
Configure(options =>
{
+ options.SaveStaticFeaturesToDatabase = false;
options.IsDynamicFeatureStoreEnabled = true;
});
}
@@ -209,6 +212,7 @@ public partial class AuthServerModule
Configure(options =>
{
options.SaveStaticPermissionsToDatabase = false;
+ options.IsDynamicPermissionStoreEnabled = true;
});
}
@@ -216,6 +220,7 @@ public partial class AuthServerModule
{
Configure(options =>
{
+ options.SaveStaticSettingsToDatabase = false;
options.IsDynamicSettingStoreEnabled = true;
});
}
@@ -295,6 +300,7 @@ public partial class AuthServerModule
options.PersistentSessionGrantTypes.Add(SmsTokenExtensionGrantConsts.GrantType);
options.PersistentSessionGrantTypes.Add(PortalTokenExtensionGrantConsts.GrantType);
options.PersistentSessionGrantTypes.Add(LinkUserTokenExtensionGrantConsts.GrantType);
+ options.PersistentSessionGrantTypes.Add(ImpersonationTokenExtensionGrantConsts.GrantType);
options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.OfficialGrantType);
options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.MiniProgramGrantType);
options.PersistentSessionGrantTypes.Add(AbpWeChatWorkGlobalConsts.GrantType);
@@ -393,6 +399,13 @@ 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/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
index 521ef2220..98eb4313f 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/AuthServerModule.cs
@@ -18,6 +18,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;
@@ -61,6 +62,7 @@ namespace LINGYUN.Abp.MicroService.AuthServer;
typeof(AbpOpenIddictSmsModule),
typeof(AbpOpenIddictWeChatModule),
typeof(AbpOpenIddictLinkUserModule),
+ typeof(AbpOpenIddictImpersonationModule),
typeof(AbpOpenIddictPortalModule),
typeof(AbpOpenIddictWeChatWorkModule),
typeof(AbpIdentityOrganizaztionUnitsModule),
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/LINGYUN.Abp.MicroService.AuthServer.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/LINGYUN.Abp.MicroService.AuthServer.csproj
index cd720576e..e6fa3dfda 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/LINGYUN.Abp.MicroService.AuthServer.csproj
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/LINGYUN.Abp.MicroService.AuthServer.csproj
@@ -50,6 +50,7 @@
+
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeFeatureDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeFeatureDefinitionManager.cs
new file mode 100644
index 000000000..887001b2f
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeFeatureDefinitionManager.cs
@@ -0,0 +1,254 @@
+using JetBrains.Annotations;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Features;
+
+[Dependency(ReplaceServices = true)]
+public class MergeFeatureDefinitionManager : FeatureDefinitionManager
+{
+ public MergeFeatureDefinitionManager(
+ IStaticFeatureDefinitionStore staticStore,
+ IDynamicFeatureDefinitionStore dynamicStore)
+ : base(staticStore, dynamicStore)
+ {
+ }
+
+ public async override Task> GetAllAsync()
+ {
+ var staticFeatures = await StaticStore.GetFeaturesAsync();
+ var dynamicFeatures = await DynamicStore.GetFeaturesAsync();
+
+ var mergedFeatures = new Dictionary();
+
+ foreach (var staticFeature in staticFeatures)
+ {
+ mergedFeatures[staticFeature.Name] = staticFeature;
+ }
+
+ foreach (var dynamicFeature in dynamicFeatures)
+ {
+ if (mergedFeatures.TryGetValue(dynamicFeature.Name, out var existingFeature))
+ {
+ MergeFeatureMetadata(existingFeature, dynamicFeature);
+
+ foreach (var child in dynamicFeature.Children)
+ {
+ MergeChildFeature(existingFeature, child);
+ }
+ }
+ else
+ {
+ mergedFeatures[dynamicFeature.Name] = dynamicFeature;
+ }
+ }
+
+ return mergedFeatures.Values.ToImmutableList();
+ }
+
+ public async override Task> GetGroupsAsync()
+ {
+ var staticGroups = await StaticStore.GetGroupsAsync();
+ var dynamicGroups = await DynamicStore.GetGroupsAsync();
+
+ var mergedGroups = new Dictionary();
+
+ foreach (var staticGroup in staticGroups)
+ {
+ mergedGroups[staticGroup.Name] = staticGroup;
+ }
+
+ foreach (var dynamicGroup in dynamicGroups)
+ {
+ if (mergedGroups.TryGetValue(dynamicGroup.Name, out var existingGroup))
+ {
+ MergeGroupFeatures(existingGroup, dynamicGroup);
+ }
+ else
+ {
+ mergedGroups[dynamicGroup.Name] = dynamicGroup;
+ }
+ }
+
+ return mergedGroups.Values.ToImmutableList();
+ }
+
+ private static void MergeGroupFeatures(FeatureGroupDefinition target, FeatureGroupDefinition source)
+ {
+ foreach (var sourceFeature in source.Features)
+ {
+ var existingFeature = GetFeatureOrNull(target, sourceFeature.Name);
+
+ if (existingFeature == null)
+ {
+ var newFeature = target.AddFeature(
+ sourceFeature.Name,
+ sourceFeature.DefaultValue,
+ sourceFeature.DisplayName,
+ sourceFeature.Description,
+ sourceFeature.ValueType,
+ sourceFeature.IsVisibleToClients,
+ sourceFeature.IsAvailableToHost
+ );
+
+ CopyFeatureDetails(sourceFeature, newFeature);
+
+ foreach (var child in sourceFeature.Children)
+ {
+ AddChildFeatureRecursively(newFeature, child);
+ }
+ }
+ else
+ {
+ MergeFeatureMetadata(existingFeature, sourceFeature);
+
+ foreach (var child in sourceFeature.Children)
+ {
+ MergeChildFeature(existingFeature, child);
+ }
+ }
+ }
+ }
+
+ private static void AddChildFeatureRecursively(FeatureDefinition parent, FeatureDefinition sourceChild)
+ {
+ var newChild = parent.CreateChild(
+ sourceChild.Name,
+ sourceChild.DefaultValue,
+ sourceChild.DisplayName,
+ sourceChild.Description,
+ sourceChild.ValueType,
+ sourceChild.IsVisibleToClients,
+ sourceChild.IsAvailableToHost
+ );
+
+ CopyFeatureDetails(sourceChild, newChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ AddChildFeatureRecursively(newChild, grandchild);
+ }
+ }
+
+ private static void MergeChildFeature(FeatureDefinition parent, FeatureDefinition sourceChild)
+ {
+ var existingChild = parent.Children.FirstOrDefault(c => c.Name == sourceChild.Name);
+
+ if (existingChild == null)
+ {
+ var newChild = parent.CreateChild(
+ sourceChild.Name,
+ sourceChild.DefaultValue,
+ sourceChild.DisplayName,
+ sourceChild.Description,
+ sourceChild.ValueType,
+ sourceChild.IsVisibleToClients,
+ sourceChild.IsAvailableToHost
+ );
+ CopyFeatureDetails(sourceChild, newChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ AddChildFeatureRecursively(newChild, grandchild);
+ }
+ }
+ else
+ {
+ MergeFeatureMetadata(existingChild, sourceChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ MergeChildFeature(existingChild, grandchild);
+ }
+ }
+ }
+
+ private static void CopyFeatureDetails(FeatureDefinition source, FeatureDefinition target)
+ {
+ foreach (var property in source.Properties)
+ {
+ target.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var provider in source.AllowedProviders)
+ {
+ if (!target.AllowedProviders.Contains(provider))
+ {
+ target.AllowedProviders.Add(provider);
+ }
+ }
+ }
+
+ private static void MergeFeatureMetadata(FeatureDefinition target, FeatureDefinition source)
+ {
+ if (source.DisplayName != null)
+ {
+ target.DisplayName = source.DisplayName;
+ }
+
+ if (source.Description != null)
+ {
+ target.Description = source.Description;
+ }
+
+ if (source.DefaultValue != null)
+ {
+ target.DefaultValue = source.DefaultValue;
+ }
+
+ if (source.ValueType != null)
+ {
+ target.ValueType = source.ValueType;
+ }
+
+ foreach (var property in source.Properties)
+ {
+ target.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var provider in source.AllowedProviders)
+ {
+ if (!target.AllowedProviders.Contains(provider))
+ {
+ target.AllowedProviders.Add(provider);
+ }
+ }
+
+ target.IsVisibleToClients = target.IsVisibleToClients || source.IsVisibleToClients;
+ target.IsAvailableToHost = target.IsAvailableToHost || source.IsAvailableToHost;
+ }
+
+
+ public static FeatureDefinition GetFeatureOrNull(
+ FeatureGroupDefinition group,
+ [NotNull] string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ return GetFeatureOrNullRecursively(group.Features, name);
+ }
+
+ private static FeatureDefinition GetFeatureOrNullRecursively(
+ IReadOnlyList features,
+ string name)
+ {
+ foreach (var feature in features)
+ {
+ if (feature.Name == name)
+ {
+ return feature;
+ }
+
+ var childFeature = GetFeatureOrNullRecursively(feature.Children, name);
+ if (childFeature != null)
+ {
+ return childFeature;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergePermissionDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergePermissionDefinitionManager.cs
new file mode 100644
index 000000000..68b34e48b
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergePermissionDefinitionManager.cs
@@ -0,0 +1,188 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.Authorization.Permissions;
+
+[Dependency(ReplaceServices = true)]
+public class MergePermissionDefinitionManager : PermissionDefinitionManager
+{
+ private readonly IStaticPermissionDefinitionStore _staticStore;
+ private readonly IDynamicPermissionDefinitionStore _dynamicStore;
+
+ public MergePermissionDefinitionManager(
+ IStaticPermissionDefinitionStore staticStore,
+ IDynamicPermissionDefinitionStore dynamicStore) : base(staticStore, dynamicStore)
+ {
+ _staticStore = staticStore;
+ _dynamicStore = dynamicStore;
+ }
+
+ public async override Task> GetGroupsAsync()
+ {
+ var staticGroups = await _staticStore.GetGroupsAsync();
+ var dynamicGroups = await _dynamicStore.GetGroupsAsync();
+
+ var mergedGroups = new Dictionary();
+
+ foreach (var staticGroup in staticGroups)
+ {
+ mergedGroups[staticGroup.Name] = staticGroup;
+ }
+
+ foreach (var dynamicGroup in dynamicGroups)
+ {
+ if (mergedGroups.TryGetValue(dynamicGroup.Name, out var existingGroup))
+ {
+ MergeGroupPermissions(existingGroup, dynamicGroup);
+ }
+ else
+ {
+ mergedGroups[dynamicGroup.Name] = dynamicGroup;
+ }
+ }
+
+ return mergedGroups.Values.ToImmutableList();
+ }
+
+ private static void MergeGroupPermissions(PermissionGroupDefinition target, PermissionGroupDefinition source)
+ {
+ foreach (var sourcePermission in source.Permissions)
+ {
+ var existingPermission = target.GetPermissionOrNull(sourcePermission.Name);
+
+ if (existingPermission == null)
+ {
+ var newPermission = target.AddPermission(
+ sourcePermission.Name,
+ sourcePermission.DisplayName,
+ sourcePermission.MultiTenancySide,
+ sourcePermission.IsEnabled
+ );
+
+ CopyPermissionDetails(sourcePermission, newPermission);
+
+ foreach (var child in sourcePermission.Children)
+ {
+ AddChildPermissionRecursively(newPermission, child);
+ }
+ }
+ else
+ {
+ MergePermissionMetadata(existingPermission, sourcePermission);
+
+ foreach (var sourceChild in sourcePermission.Children)
+ {
+ MergeChildPermissions(existingPermission, sourceChild);
+ }
+ }
+ }
+ }
+
+ private static void AddChildPermissionRecursively(PermissionDefinition parent, PermissionDefinition sourceChild)
+ {
+ var newChild = parent.AddChild(
+ sourceChild.Name,
+ sourceChild.DisplayName,
+ sourceChild.MultiTenancySide,
+ sourceChild.IsEnabled
+ );
+
+ CopyPermissionDetails(sourceChild, newChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ AddChildPermissionRecursively(newChild, grandchild);
+ }
+ }
+
+ private static void MergeChildPermissions(PermissionDefinition parent, PermissionDefinition sourceChild)
+ {
+ var existingChild = parent.Children.FirstOrDefault(c => c.Name == sourceChild.Name);
+
+ if (existingChild == null)
+ {
+ var newChild = parent.AddChild(
+ sourceChild.Name,
+ sourceChild.DisplayName,
+ sourceChild.MultiTenancySide,
+ sourceChild.IsEnabled
+ );
+ CopyPermissionDetails(sourceChild, newChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ AddChildPermissionRecursively(newChild, grandchild);
+ }
+ }
+ else
+ {
+ MergePermissionMetadata(existingChild, sourceChild);
+
+ foreach (var grandchild in sourceChild.Children)
+ {
+ MergeChildPermissions(existingChild, grandchild);
+ }
+ }
+ }
+
+ private static void CopyPermissionDetails(PermissionDefinition source, PermissionDefinition target)
+ {
+ foreach (var property in source.Properties)
+ {
+ target.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var provider in source.Providers)
+ {
+ if (!target.Providers.Contains(provider))
+ {
+ target.Providers.Add(provider);
+ }
+ }
+
+ foreach (var checker in source.StateCheckers)
+ {
+ if (!target.StateCheckers.Contains(checker))
+ {
+ target.StateCheckers.Add(checker);
+ }
+ }
+ }
+
+ private static void MergePermissionMetadata(PermissionDefinition target, PermissionDefinition source)
+ {
+ target.DisplayName = source.DisplayName;
+
+ foreach (var property in source.Properties)
+ {
+ target.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var provider in source.Providers)
+ {
+ if (!target.Providers.Contains(provider))
+ {
+ target.Providers.Add(provider);
+ }
+ }
+
+ foreach (var checker in source.StateCheckers)
+ {
+ if (!target.StateCheckers.Contains(checker))
+ {
+ target.StateCheckers.Add(checker);
+ }
+ }
+
+ if (source.MultiTenancySide != MultiTenancySides.Both)
+ {
+ target.MultiTenancySide |= source.MultiTenancySide;
+ }
+
+ target.IsEnabled = target.IsEnabled || source.IsEnabled;
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeSettingDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeSettingDefinitionManager.cs
new file mode 100644
index 000000000..c3c479f4f
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeSettingDefinitionManager.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Settings;
+
+[Dependency(ReplaceServices = true)]
+public class MergeSettingDefinitionManager : SettingDefinitionManager
+{
+ private readonly IStaticSettingDefinitionStore _staticStore;
+ private readonly IDynamicSettingDefinitionStore _dynamicStore;
+
+ public MergeSettingDefinitionManager(
+ IStaticSettingDefinitionStore staticStore,
+ IDynamicSettingDefinitionStore dynamicStore)
+ : base(staticStore, dynamicStore)
+ {
+ _staticStore = staticStore;
+ _dynamicStore = dynamicStore;
+ }
+
+ public async override Task> GetAllAsync()
+ {
+ var staticSettings = await _staticStore.GetAllAsync();
+ var dynamicSettings = await _dynamicStore.GetAllAsync();
+
+ var mergedSettings = new Dictionary();
+
+ foreach (var staticSetting in staticSettings)
+ {
+ mergedSettings[staticSetting.Name] = staticSetting;
+ }
+
+ foreach (var dynamicSetting in dynamicSettings)
+ {
+ if (mergedSettings.TryGetValue(dynamicSetting.Name, out var existingSetting))
+ {
+ MergeSetting(existingSetting, dynamicSetting);
+ }
+ else
+ {
+ mergedSettings[dynamicSetting.Name] = dynamicSetting;
+ }
+ }
+
+ return mergedSettings.Values.ToImmutableList();
+ }
+
+ private static void MergeSetting(SettingDefinition target, SettingDefinition source)
+ {
+ target.DisplayName = source.DisplayName;
+ target.Description = source.Description ?? target.Description;
+ target.DefaultValue = source.DefaultValue ?? target.DefaultValue;
+ target.IsVisibleToClients = target.IsVisibleToClients || source.IsVisibleToClients;
+ target.IsInherited = target.IsInherited || source.IsInherited;
+ target.IsEncrypted = target.IsEncrypted || source.IsEncrypted;
+
+ foreach (var property in source.Properties)
+ {
+ target.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var provider in source.Providers)
+ {
+ if (!target.Providers.Contains(provider))
+ {
+ target.Providers.Add(provider);
+ }
+ }
+ }
+}
+
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeTemplateDefinitionManager.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeTemplateDefinitionManager.cs
new file mode 100644
index 000000000..0a45a8908
--- /dev/null
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/Overrides/MergeTemplateDefinitionManager.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.TextTemplating;
+
+namespace Volo.Abp.TextTemplatin;
+
+
+[Dependency(ReplaceServices = true)]
+public class MergeTemplateDefinitionManager : TemplateDefinitionManager
+{
+ public MergeTemplateDefinitionManager(
+ IStaticTemplateDefinitionStore staticStore,
+ IDynamicTemplateDefinitionStore dynamicStore)
+ : base(staticStore, dynamicStore)
+ {
+ }
+ public async override Task> GetAllAsync()
+ {
+ var staticTemplates = await StaticStore.GetAllAsync();
+ var dynamicTemplates = await DynamicStore.GetAllAsync();
+
+ var mergedTemplates = new Dictionary();
+
+ foreach (var staticTemplate in staticTemplates)
+ {
+ mergedTemplates[staticTemplate.Name] = staticTemplate;
+ }
+
+ foreach (var dynamicTemplate in dynamicTemplates)
+ {
+ if (mergedTemplates.TryGetValue(dynamicTemplate.Name, out var existingTemplate))
+ {
+ MergeTemplate(existingTemplate, dynamicTemplate);
+ }
+ else
+ {
+ mergedTemplates[dynamicTemplate.Name] = dynamicTemplate;
+ }
+ }
+
+ return mergedTemplates.Values.ToImmutableList();
+ }
+
+ private static TemplateDefinition MergeTemplate(TemplateDefinition staticTemplate, TemplateDefinition dynamicTemplate)
+ {
+ var localizationResourceName = dynamicTemplate.LocalizationResourceName ?? staticTemplate.LocalizationResourceName;
+ var defaultCultureName = dynamicTemplate.DefaultCultureName ?? staticTemplate.DefaultCultureName;
+ var displayName = dynamicTemplate.DisplayName ?? staticTemplate.DisplayName;
+ var isLayout = dynamicTemplate.IsLayout || staticTemplate.IsLayout;
+ var layout = dynamicTemplate.Layout ?? staticTemplate.Layout;
+
+ var mergedTemplate = new TemplateDefinition(
+ staticTemplate.Name,
+ localizationResourceName,
+ displayName,
+ isLayout,
+ layout,
+ defaultCultureName
+ );
+
+ foreach (var property in staticTemplate.Properties)
+ {
+ mergedTemplate.Properties[property.Key] = property.Value;
+ }
+
+ foreach (var property in dynamicTemplate.Properties)
+ {
+ mergedTemplate.Properties[property.Key] = property.Value;
+ }
+
+ if (!string.IsNullOrEmpty(dynamicTemplate.RenderEngine))
+ {
+ mergedTemplate.RenderEngine = dynamicTemplate.RenderEngine;
+ }
+ else if (!string.IsNullOrEmpty(staticTemplate.RenderEngine))
+ {
+ mergedTemplate.RenderEngine = staticTemplate.RenderEngine;
+ }
+
+ mergedTemplate.IsInlineLocalized = dynamicTemplate.IsInlineLocalized || staticTemplate.IsInlineLocalized;
+
+ return mergedTemplate;
+ }
+}
diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/appsettings.Development.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/appsettings.Development.json
index 128c684cf..09e85a1bd 100644
--- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/appsettings.Development.json
+++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer/appsettings.Development.json
@@ -134,11 +134,11 @@
},
"Serilog": {
"MinimumLevel": {
- "Default": "Warning",
+ "Default": "Debug",
"Override": {
"System": "Warning",
"Microsoft": "Warning",
- "DotNetCore": "Warning"
+ "DotNetCore": "Debug"
}
},
"WriteTo": [
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
index c9d9c61fc..7f06b61ac 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
@@ -78,6 +78,9 @@
"LinkAccountWillBeDeleteWarning": "Are you sure you want to delete the associated account \"{0}\"?",
"LinkLogged": "Accounts are linked",
"StayWithCurrentAccount": "Stay with the current account",
- "Settings:Identity.Account": "Account"
+ "Settings:Identity.Account": "Account",
+ "ImpersonatorUser": "Impersonator",
+ "ImpersonatorWarning": "You will log out from your current account and access the system as {0}!",
+ "RevertImpersonationWarning": "You will log out of your current account and return to the account you previously logged in with!"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
index 64de06182..791208eb4 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
@@ -78,6 +78,9 @@
"LinkAccountWillBeDeleteWarning": "您确定要删除关联帐户\"{0}\"吗?",
"LinkLogged": "帐户已经关联",
"StayWithCurrentAccount": "继续使用当前帐户",
- "Settings:Identity.Account": "用户账户"
+ "Settings:Identity.Account": "用户账户",
+ "ImpersonatorUser": "模拟用户",
+ "ImpersonatorWarning": "您将从当前账户退出, 使用 {0} 的身份访问系统!",
+ "RevertImpersonationWarning": "您将从当前账户退出, 回到您之前登录的账户!"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissionDefinitionProvider.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissionDefinitionProvider.cs
index 562b8c7b5..3fac2259d 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissionDefinitionProvider.cs
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissionDefinitionProvider.cs
@@ -18,6 +18,7 @@ public class IdentityPermissionDefinitionProvider : PermissionDefinitionProvider
userPermission.AddChild(IdentityPermissions.Users.ResetPassword, L("Permission:ResetPassword"));
userPermission.AddChild(IdentityPermissions.Users.ManageClaims, L("Permission:ManageClaims"));
userPermission.AddChild(IdentityPermissions.Users.ManageOrganizationUnits, L("Permission:ManageOrganizationUnits"));
+ userPermission.AddChild(IdentityPermissions.Users.Impersonation, L("Permission:Impersonation"));
}
var rolePermission = identityGroup.GetPermissionOrNull(Volo.Abp.Identity.IdentityPermissions.Roles.Default);
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissions.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissions.cs
index 0bb2ec123..6292a276e 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissions.cs
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IdentityPermissions.cs
@@ -15,6 +15,7 @@ public class IdentityPermissions
public const string ResetPassword = Volo.Abp.Identity.IdentityPermissions.Users.Default + ".ResetPassword";
public const string ManageClaims = Volo.Abp.Identity.IdentityPermissions.Users.Default + ".ManageClaims";
public const string ManageOrganizationUnits = Volo.Abp.Identity.IdentityPermissions.Users.Default + ".ManageOrganizationUnits";
+ public const string Impersonation = Volo.Abp.Identity.IdentityPermissions.Users.Default + ".Impersonation";
}
public static class OrganizationUnits
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
index 1059016d6..65c5f31cf 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
@@ -10,6 +10,7 @@
"Permission:IdentityClaimTypeManagement": "Management claim types",
"Permission:IdentitySessions": "Management sessions",
"Permission:RevokeSession": "Revoke session",
+ "Permission:Impersonation": "Impersonation",
"OrganizationUnits": "Organization Units",
"OrganizationUnit:RefreshRoot": "Refresh Root",
"OrganizationUnit:Tree": "Organization tree",
@@ -124,6 +125,7 @@
"Settings:Identity.Password": "Password",
"Settings:Identity.OrganizationUnit": "OrganizationUnit",
"Settings:Identity.Session": "Session",
- "Settings:Identity.Link": "Link"
+ "Settings:Identity.Link": "Link",
+ "ImpersonationUser": "Impersonation"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
index 1d4b0dacd..3298324b0 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
@@ -10,6 +10,7 @@
"Permission:IdentityClaimTypeManagement": "管理声明类型",
"Permission:IdentitySessions": "管理会话",
"Permission:RevokeSession": "撤销会话",
+ "Permission:Impersonation": "模拟用户",
"OrganizationUnits": "组织机构",
"OrganizationUnit:RefreshRoot": "刷新根组织",
"OrganizationUnit:Tree": "组织机构树",
@@ -124,6 +125,7 @@
"Settings:Identity.Password": "密码配置",
"Settings:Identity.OrganizationUnit": "组织机构",
"Settings:Identity.Session": "会话配置",
- "Settings:Identity.Link": "链接"
+ "Settings:Identity.Link": "链接",
+ "ImpersonationUser": "模拟用户"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xml b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xsd b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN.Abp.OpenIddict.Impersonation.csproj b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN.Abp.OpenIddict.Impersonation.csproj
new file mode 100644
index 000000000..c18ffa627
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN.Abp.OpenIddict.Impersonation.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ net10.0
+ LINGYUN.Abp.OpenIddict.Impersonation
+ LINGYUN.Abp.OpenIddict.Impersonation
+ false
+ false
+ false
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/AbpOpenIddictImpersonationModule.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/AbpOpenIddictImpersonationModule.cs
new file mode 100644
index 000000000..ed8a16fda
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/AbpOpenIddictImpersonationModule.cs
@@ -0,0 +1,44 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.OpenIddict;
+using Volo.Abp.OpenIddict.ExtensionGrantTypes;
+using Volo.Abp.OpenIddict.Localization;
+using Volo.Abp.VirtualFileSystem;
+
+namespace LINGYUN.Abp.OpenIddict.Impersonation;
+
+[DependsOn(
+ typeof(AbpOpenIddictAspNetCoreModule))]
+public class AbpOpenIddictImpersonationModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ PreConfigure(builder =>
+ {
+ builder.AllowImpersonationFlow();
+ });
+ }
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Grants.TryAdd(
+ ImpersonationTokenExtensionGrantConsts.GrantType,
+ new ImpersonationTokenExtensionGrant());
+ });
+
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddVirtualJson("/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources");
+ });
+ }
+}
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationRequest.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationRequest.cs
new file mode 100644
index 000000000..55fc9d238
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationRequest.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace LINGYUN.Abp.OpenIddict.Impersonation;
+
+public class ImpersonationRequest
+{
+ public Guid? UserId { get; set; }
+ public Guid? TenantId { get; set; }
+ public Guid? UserDelegationId { get; set; }
+ public string? TenantUserName { get; set; }
+
+ public bool HasImpersonationTarget()
+ {
+ return UserId.HasValue || TenantId.HasValue || UserDelegationId.HasValue;
+ }
+
+ public bool IsUserImpersonation()
+ {
+ return UserId.HasValue && !UserDelegationId.HasValue;
+ }
+
+ public bool IsTenantImpersonation()
+ {
+ return !UserId.HasValue && TenantId.HasValue && !UserDelegationId.HasValue;
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrant.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrant.cs
new file mode 100644
index 000000000..bb4b16970
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrant.cs
@@ -0,0 +1,453 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using OpenIddict.Abstractions;
+using OpenIddict.Server;
+using OpenIddict.Server.AspNetCore;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Identity;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.OpenIddict;
+using Volo.Abp.OpenIddict.ExtensionGrantTypes;
+using Volo.Abp.OpenIddict.Localization;
+using Volo.Abp.Security.Claims;
+using Volo.Abp.Users;
+using IdentityUser = Volo.Abp.Identity.IdentityUser;
+using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
+
+namespace LINGYUN.Abp.OpenIddict.Impersonation;
+
+public class ImpersonationTokenExtensionGrant : ITokenExtensionGrant
+{
+ public string Name => ImpersonationTokenExtensionGrantConsts.GrantType;
+
+ public async virtual Task HandleAsync(ExtensionGrantContext context)
+ {
+ var localizer = GetRequiredService>(context);
+
+ var principal = await ValidateAccessTokenAsync(context);
+ if (principal == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidToken,
+ localizer["InvalidAccessToken"]);
+ }
+
+ var currentPrincipalAccessor = GetRequiredService(context);
+ using (currentPrincipalAccessor.Change(principal))
+ {
+ return await ProcessImpersonationRequestAsync(context, principal);
+ }
+ }
+
+ protected async virtual Task ValidateAccessTokenAsync(ExtensionGrantContext context)
+ {
+ var factory = GetRequiredService(context);
+ var dispatcher = GetRequiredService(context);
+ var logger = GetRequiredService>(context);
+
+ var transaction = await factory.CreateTransactionAsync();
+ transaction.EndpointType = OpenIddictServerEndpointType.Introspection;
+ transaction.Request = new OpenIddictRequest
+ {
+ ClientId = context.Request.ClientId,
+ ClientSecret = context.Request.ClientSecret,
+ Token = context.Request.AccessToken,
+ TokenTypeHint = OpenIddictConstants.TokenTypeHints.AccessToken
+ };
+
+ var authContext = new OpenIddictServerEvents.ProcessAuthenticationContext(transaction);
+ await dispatcher.DispatchAsync(authContext);
+
+ if (authContext.IsRejected)
+ {
+ logger.LogWarning("Token introspection rejected: {Error}", authContext.Error);
+ return null;
+ }
+
+ return authContext.GenericTokenPrincipal;
+ }
+
+ protected async virtual Task ProcessImpersonationRequestAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal)
+ {
+ var request = context.Request;
+ var localizer = GetRequiredService>(context);
+ var currentPrincipalAccessor = GetRequiredService(context);
+
+ var isCurrentlyImpersonating = currentPrincipalAccessor.Principal.IsImpersonating();
+
+ var impersonationRequest = ParseImpersonationRequest(request);
+
+ if (isCurrentlyImpersonating && !impersonationRequest.HasImpersonationTarget())
+ {
+ return await RevertToOriginalUserAsync(context, principal);
+ }
+
+ if (isCurrentlyImpersonating && impersonationRequest.HasImpersonationTarget())
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["NestedImpersonationNotAllowed"]);
+ }
+
+ if (impersonationRequest.UserDelegationId.HasValue)
+ {
+ return await HandleDelegatedImpersonationAsync(context, principal, impersonationRequest.UserDelegationId.Value);
+ }
+
+ if (impersonationRequest.IsTenantImpersonation())
+ {
+ return await HandleTenantImpersonationAsync(context, principal, impersonationRequest);
+ }
+
+ if (impersonationRequest.IsUserImpersonation())
+ {
+ return await HandleUserImpersonationAsync(context, principal, impersonationRequest);
+ }
+
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["InvalidImpersonationRequest"]);
+ }
+
+ protected virtual ImpersonationRequest ParseImpersonationRequest(OpenIddictRequest request)
+ {
+ return new ImpersonationRequest
+ {
+ UserId = ParseGuidParameter(request, "UserId"),
+ TenantId = ParseGuidParameter(request, "TenantId"),
+ UserDelegationId = ParseGuidParameter(request, "UserDelegationId"),
+ TenantUserName = request.GetParameter("TenantUserName")?.ToString()
+ };
+ }
+
+ protected async virtual Task HandleUserImpersonationAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal,
+ ImpersonationRequest request)
+ {
+ var localizer = GetRequiredService>(context);
+ var currentUser = GetRequiredService(context);
+ var currentTenant = GetRequiredService(context);
+ var userManager = GetRequiredService(context);
+ var permissionChecker = GetRequiredService(context);
+ var options = GetRequiredService>(context);
+
+ using (currentTenant.Change(request.TenantId))
+ {
+ if (currentUser.Id == request.UserId)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["CannotImpersonateYourself"]);
+ }
+
+ var impersonationPermission = options.Value.ImpersonationPermission;
+ if (!impersonationPermission.IsNullOrWhiteSpace() &&
+ !await permissionChecker.IsGrantedAsync(impersonationPermission))
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InsufficientAccess,
+ localizer["InsufficientImpersonationPermission"]);
+ }
+
+ var targetUser = await userManager.FindByIdAsync(request.UserId!.Value.ToString()!);
+ if (targetUser == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["TargetUserNotFound", request.UserId.Value]);
+ }
+
+ return await CreateImpersonatedSignInResult(context, principal, targetUser, request.TenantId);
+ }
+ }
+
+ protected async virtual Task HandleTenantImpersonationAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal,
+ ImpersonationRequest request)
+ {
+ var localizer = GetRequiredService>(context);
+ var currentTenant = GetRequiredService(context);
+ var userManager = GetRequiredService(context);
+ var permissionChecker = GetRequiredService(context);
+ var options = GetRequiredService>(context);
+
+ using (currentTenant.Change(null))
+ {
+ var impersonationPermission = options.Value.ImpersonationTenantPermission;
+ if (!impersonationPermission.IsNullOrWhiteSpace() &&
+ !await permissionChecker.IsGrantedAsync(impersonationPermission))
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InsufficientAccess,
+ localizer["RequirePermissionToImpersonateUser"]);
+ }
+ }
+
+ using (currentTenant.Change(request.TenantId))
+ {
+ var adminUserName = request.TenantUserName ?? options.Value.DefaultTenantAdminUserName;
+ if (adminUserName.IsNullOrWhiteSpace())
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["TenantAdminUserNameNotSpecified"]);
+ }
+
+ var adminUser = await userManager.FindByNameAsync(adminUserName);
+ if (adminUser == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["TenantAdminUserNotFound", adminUserName]);
+ }
+
+ return await CreateImpersonatedSignInResult(context, principal, adminUser, request.TenantId);
+ }
+ }
+
+ protected async virtual Task HandleDelegatedImpersonationAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal,
+ Guid userDelegationId)
+ {
+ var localizer = GetRequiredService>(context);
+ var currentUser = GetRequiredService(context);
+ var currentTenant = GetRequiredService(context);
+ var userManager = GetRequiredService(context);
+ var delegationManager = GetRequiredService(context);
+
+ var delegation = await delegationManager.FindActiveDelegationByIdAsync(userDelegationId);
+ if (delegation == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["InvalidUserDelegation"]);
+ }
+
+ if (currentUser.Id != delegation.TargetUserId)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InsufficientAccess,
+ localizer["NotAuthorizedForDelegation"]);
+ }
+
+ if (delegation.SourceUserId == currentUser.Id)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["CannotImpersonateYourself"]);
+ }
+
+ var sourceUser = await userManager.FindByIdAsync(delegation.SourceUserId.ToString());
+ if (sourceUser == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["DelegationSourceUserNotFound"]);
+ }
+
+ return await CreateImpersonatedSignInResult(
+ context,
+ principal,
+ sourceUser,
+ currentTenant.Id,
+ "DelegatedImpersonation");
+ }
+
+ protected async virtual Task RevertToOriginalUserAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal)
+ {
+ var localizer = GetRequiredService>(context);
+ var currentPrincipalAccessor = GetRequiredService(context);
+ var userManager = GetRequiredService(context);
+ var currentTenant = GetRequiredService(context);
+
+ var impersonatorUserId = currentPrincipalAccessor.Principal.FindImpersonatorUserId();
+ var impersonatorTenantId = currentPrincipalAccessor.Principal.FindImpersonatorTenantId();
+
+ if (!impersonatorUserId.HasValue)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["NoImpersonationContext"]);
+ }
+
+ using (currentTenant.Change(impersonatorTenantId))
+ {
+ var originalUser = await userManager.FindByIdAsync(impersonatorUserId.Value.ToString());
+ if (originalUser == null)
+ {
+ return ForbidWithError(
+ OpenIddictConstants.Errors.InvalidRequest,
+ localizer["OriginalUserNotFound"]);
+ }
+
+ return await CreateAuthenticatedSignInResult(context, principal, originalUser);
+ }
+ }
+
+ protected async virtual Task CreateImpersonatedSignInResult(
+ ExtensionGrantContext context,
+ ClaimsPrincipal currentPrincipal,
+ IdentityUser targetUser,
+ Guid? tenantId,
+ string action = "ImpersonateUser")
+ {
+ var userClaimsPrincipalFactory = GetRequiredService>(context);
+ var currentUser = GetRequiredService(context);
+ var currentTenant = GetRequiredService(context);
+ var currentPrincipalAccessor = GetRequiredService(context);
+
+ var principal = await userClaimsPrincipalFactory.CreateAsync(targetUser);
+ var claims = new List();
+
+ if (currentUser.Id.HasValue && currentUser.Id?.ToString() != currentPrincipalAccessor.Principal.FindImpersonatorUserId()?.ToString())
+ {
+ claims.Add(new Claim(AbpClaimTypes.ImpersonatorUserId, currentUser.Id!.Value.ToString()));
+ claims.Add(new Claim(AbpClaimTypes.ImpersonatorUserName, currentUser.UserName!));
+
+ if (currentTenant.IsAvailable)
+ {
+ claims.Add(new Claim(AbpClaimTypes.ImpersonatorTenantId, currentTenant.Id!.Value.ToString()));
+ }
+ }
+
+ var rememberMeClaim = currentPrincipal.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.RememberMe);
+ if (rememberMeClaim != null)
+ {
+ claims.Add(rememberMeClaim);
+ }
+
+ var identity = principal.Identities.FirstOrDefault();
+ if (identity != null && claims.Count != 0)
+ {
+ identity.AddClaims(claims);
+ }
+
+ return await FinalizeSignInAsync(context, principal, currentPrincipal.GetScopes(), action);
+ }
+
+ protected async Task CreateAuthenticatedSignInResult(
+ ExtensionGrantContext context,
+ ClaimsPrincipal currentPrincipal,
+ IdentityUser targetUser)
+ {
+ var userClaimsPrincipalFactory = GetRequiredService>(context);
+ var principal = await userClaimsPrincipalFactory.CreateAsync(targetUser);
+
+ return await FinalizeSignInAsync(context, principal, currentPrincipal.GetScopes(), "RevertImpersonation");
+ }
+
+ protected async virtual Task FinalizeSignInAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal,
+ IEnumerable scopes,
+ string action)
+ {
+ var currentPrincipalAccessor = GetRequiredService(context);
+
+ using (currentPrincipalAccessor.Change(principal))
+ {
+ await SaveSecurityLogAsync(context, principal, action);
+ }
+
+ principal.SetScopes(scopes);
+ principal.SetResources(await GetResourcesAsync(context, scopes));
+
+ await SetClaimsDestinationsAsync(context, principal);
+
+ return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);
+ }
+
+ protected async virtual Task SaveSecurityLogAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal,
+ string action)
+ {
+ var identitySecurityLogManager = GetRequiredService(context);
+ var currentUser = GetRequiredService(context);
+ var currentTenant = GetRequiredService(context);
+
+ var logContext = new IdentitySecurityLogContext
+ {
+ Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict,
+ Action = action,
+ UserName = principal.FindFirstValue(ClaimTypes.Name),
+ ClientId = context.Request.ClientId
+ };
+ logContext.WithProperty("GrantType", Name);
+ logContext.WithProperty("ImpersonatorUserId", currentUser.Id?.ToString());
+ logContext.WithProperty("ImpersonatorTenantId", currentTenant.Id?.ToString());
+
+ await identitySecurityLogManager.SaveAsync(logContext);
+ }
+
+ protected async virtual Task> GetResourcesAsync(
+ ExtensionGrantContext context,
+ IEnumerable scopes)
+ {
+ var resources = new List();
+ if (scopes.Any())
+ {
+ var scopeManager = GetRequiredService(context);
+
+ await foreach (var resource in scopeManager.ListResourcesAsync(scopes.ToImmutableArray()))
+ {
+ resources.Add(resource);
+ }
+ }
+
+ return resources;
+ }
+
+ protected async virtual Task SetClaimsDestinationsAsync(
+ ExtensionGrantContext context,
+ ClaimsPrincipal principal)
+ {
+ var manager = GetRequiredService(context);
+ await manager.HandleAsync(context.Request, principal);
+ }
+
+ protected virtual IActionResult ForbidWithError(string error, string description)
+ {
+ return new ForbidResult(
+ new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
+ new AuthenticationProperties(new Dictionary
+ {
+ [OpenIddictServerAspNetCoreConstants.Properties.Error] = error,
+ [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = description
+ }));
+ }
+
+ protected virtual T GetRequiredService(ExtensionGrantContext context) where T : notnull
+ {
+ return context.HttpContext.RequestServices.GetRequiredService();
+ }
+
+ protected static Guid? ParseGuidParameter(OpenIddictRequest request, string name)
+ {
+ var parameter = request.GetParameter(name);
+ if (parameter == null)
+ {
+ return null;
+ }
+
+ return Guid.TryParse(parameter.Value.ToString(), out var result) ? result : null;
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrantConsts.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrantConsts.cs
new file mode 100644
index 000000000..286c1cfea
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/ImpersonationTokenExtensionGrantConsts.cs
@@ -0,0 +1,6 @@
+namespace LINGYUN.Abp.OpenIddict.Impersonation;
+
+public static class ImpersonationTokenExtensionGrantConsts
+{
+ public const string GrantType = "impersonation";
+}
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/en.json b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/en.json
new file mode 100644
index 000000000..7cffa3b5e
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/en.json
@@ -0,0 +1,19 @@
+{
+ "culture": "en",
+ "texts": {
+ "InvalidAccessToken": "Invalid access token, token validation failed",
+ "NestedImpersonationNotAllowed": "Nested impersonation is not allowed. You are currently in impersonation mode. Please revert to your original identity first",
+ "InvalidImpersonationRequest": "Invalid impersonation request. Please provide valid impersonation target parameters (User ID, Tenant ID, or Delegation ID)",
+ "CannotImpersonateYourself": "You cannot impersonate yourself. Please select a different user to impersonate",
+ "InsufficientImpersonationPermission": "Insufficient permission to perform user impersonation",
+ "TargetUserNotFound": "Target user not found: {0}",
+ "RequirePermissionToImpersonateUser": "Authorization is required to simulate users/tenants!",
+ "TenantAdminUserNameNotSpecified": "Tenant admin user name is not specified. Tenant impersonation requires an admin user name",
+ "TenantAdminUserNotFound": "Tenant admin user not found: {0}. Please verify the admin user name is correct",
+ "InvalidUserDelegation": "Invalid user delegation. The delegation record does not exist or has expired",
+ "NotAuthorizedForDelegation": "Delegation authorization failed. Current user is not the target of this delegation",
+ "DelegationSourceUserNotFound": "Delegation source user not found. Unable to complete delegated impersonation",
+ "NoImpersonationContext": "No impersonation context found. Unable to revert to original user identity",
+ "OriginalUserNotFound": "Original user not found. The impersonator's user record may have been deleted"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/zh-Hans.json b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/zh-Hans.json
new file mode 100644
index 000000000..74c419f5c
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources/zh-Hans.json
@@ -0,0 +1,19 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "InvalidAccessToken": "无效的访问令牌,令牌验证失败!",
+ "NestedImpersonationNotAllowed": "不允许嵌套模拟,当前已处于模拟登录状态,请先返回原始用户身份!",
+ "InvalidImpersonationRequest": "无效的模拟请求,请提供正确的模拟目标参数(用户ID、租户ID或委托ID)!",
+ "CannotImpersonateYourself": "不能模拟自己,请选择其他用户进行模拟!",
+ "InsufficientImpersonationPermission": "模拟权限不足,您没有执行用户模拟操作的权限!",
+ "TargetUserNotFound": "目标用户不存在:{0}!",
+ "RequirePermissionToImpersonateUser": "需要授权才能模拟用户/租户!",
+ "TenantAdminUserNameNotSpecified": "未指定租户管理员用户名,租户模拟需要提供管理员用户名!",
+ "TenantAdminUserNotFound": "未找到租户管理员用户:{0},请确认管理员用户名是否正确!",
+ "InvalidUserDelegation": "无效的用户委托,委托记录不存在或已失效!",
+ "NotAuthorizedForDelegation": "委托授权失败,当前用户不是该委托的目标用户!",
+ "DelegationSourceUserNotFound": "委托源用户不存在,无法完成委托模拟!",
+ "NoImpersonationContext": "未找到模拟上下文,无法返回原始用户身份!",
+ "OriginalUserNotFound": "原始用户不存在,模拟者的用户记录可能已被删除!"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/OpenIddictImpersonationOptions.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/OpenIddictImpersonationOptions.cs
new file mode 100644
index 000000000..e09ac506a
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/LINGYUN/Abp/OpenIddict/Impersonation/OpenIddictImpersonationOptions.cs
@@ -0,0 +1,21 @@
+namespace LINGYUN.Abp.OpenIddict.Impersonation;
+
+public class OpenIddictImpersonationOptions
+{
+ ///
+ /// 模拟用户时检查权限
+ ///
+ public string? ImpersonationPermission { get; set; }
+ ///
+ /// 模拟租户时检查权限
+ ///
+ public string? ImpersonationTenantPermission { get; set; }
+ ///
+ /// 宿主端模拟租户时默认管理员用户名
+ ///
+ public string DefaultTenantAdminUserName { get; set; }
+ public OpenIddictImpersonationOptions()
+ {
+ DefaultTenantAdminUserName = "admin";
+ }
+}
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/Microsoft/Extensions/DependencyInjection/ImpersonationOpenIddictServerBuilderExtensions.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/Microsoft/Extensions/DependencyInjection/ImpersonationOpenIddictServerBuilderExtensions.cs
new file mode 100644
index 000000000..b9f962117
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/Microsoft/Extensions/DependencyInjection/ImpersonationOpenIddictServerBuilderExtensions.cs
@@ -0,0 +1,11 @@
+using LINGYUN.Abp.OpenIddict.Impersonation;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+public static class ImpersonationOpenIddictServerBuilderExtensions
+{
+ public static OpenIddictServerBuilder AllowImpersonationFlow(this OpenIddictServerBuilder builder)
+ {
+ return builder.AllowCustomFlow(ImpersonationTokenExtensionGrantConsts.GrantType);
+ }
+}
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/System/Security/Claims/ImpersonationClaimExtensions.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/System/Security/Claims/ImpersonationClaimExtensions.cs
new file mode 100644
index 000000000..9a09adcb1
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Impersonation/System/Security/Claims/ImpersonationClaimExtensions.cs
@@ -0,0 +1,23 @@
+using Volo.Abp.Security.Claims;
+
+namespace System.Security.Claims;
+
+public static class ImpersonationClaimExtensions
+{
+ public static bool IsImpersonating(this ClaimsPrincipal principal)
+ {
+ return principal.FindImpersonatorUserId().HasValue;
+ }
+
+ public static Guid? FindImpersonatorUserId(this ClaimsPrincipal principal)
+ {
+ var value = principal.FindFirstValue(AbpClaimTypes.ImpersonatorUserId);
+ return Guid.TryParse(value, out var result) ? result : null;
+ }
+
+ public static Guid? FindImpersonatorTenantId(this ClaimsPrincipal principal)
+ {
+ var value = principal.FindFirstValue(AbpClaimTypes.ImpersonatorTenantId);
+ return Guid.TryParse(value, out var result) ? result : null;
+ }
+}
diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissionDefinitionProvider.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissionDefinitionProvider.cs
index 0776aab55..9027e268a 100644
--- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissionDefinitionProvider.cs
+++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissionDefinitionProvider.cs
@@ -1,10 +1,10 @@
using LINGYUN.Abp.Saas.Localization;
-using Volo.Abp.Authorization.Permissions;
-using Volo.Abp.Localization;
-using Volo.Abp.MultiTenancy;
-
-namespace LINGYUN.Abp.Saas;
-
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Localization;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.Saas;
+
public class AbpSaasPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
@@ -23,10 +23,11 @@ public class AbpSaasPermissionDefinitionProvider : PermissionDefinitionProvider
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host);
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host);
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageConnectionStrings, L("Permission:ManageConnectionStrings"), multiTenancySide: MultiTenancySides.Host);
+ tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Impersonation, L("Permission:Impersonation"), multiTenancySide: MultiTenancySides.Host);
}
private static LocalizableString L(string name)
{
return LocalizableString.Create(name);
}
-}
+}
diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissions.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissions.cs
index 729e1be49..fa37e2a4d 100644
--- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissions.cs
+++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Permissions/AbpSaasPermissions.cs
@@ -1,7 +1,7 @@
-using Volo.Abp.Reflection;
-
-namespace LINGYUN.Abp.Saas;
-
+using Volo.Abp.Reflection;
+
+namespace LINGYUN.Abp.Saas;
+
public static class AbpSaasPermissions
{
public const string GroupName = "AbpSaas";
@@ -23,6 +23,7 @@ public static class AbpSaasPermissions
public const string Delete = Default + ".Delete";
public const string ManageFeatures = Default + ".ManageFeatures";
public const string ManageConnectionStrings = Default + ".ManageConnectionStrings";
+ public const string Impersonation = Default + ".Impersonation";
}
public static string[] GetAll()
diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json
index 8d976749b..bee5ad9e5 100644
--- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json
+++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json
@@ -42,6 +42,7 @@
"Permission:Delete": "Delete",
"Permission:ManageConnectionStrings": "Manage connection strings",
"Permission:ManageFeatures": "Manage features",
+ "Permission:Impersonation": "Impersonation",
"DisplayName:AdminEmailAddress": "Admin Email Address",
"DisplayName:AdminPassword": "Admin Password",
"Features:RecycleStrategy": "Recycle Strategy",
@@ -54,6 +55,7 @@
"RecycleStrategy:Reserve": "Reserve",
"RecycleStrategy:Recycle": "Recycle",
"ManageFeatures": "Manage Features",
- "IfUseCustomDataBaseDefaultConnectionStringIsRequiredMessage": "If you use a custom database, the default connection string must be required!"
+ "IfUseCustomDataBaseDefaultConnectionStringIsRequiredMessage": "If you use a custom database, the default connection string must be required!",
+ "ImpersonationTenant": "Impersonation"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json
index 21dba4be6..d6a882b83 100644
--- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json
+++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json
@@ -42,6 +42,7 @@
"Permission:Delete": "删除",
"Permission:ManageConnectionStrings": "管理连接字符串",
"Permission:ManageFeatures": "管理功能",
+ "Permission:Impersonation": "模拟租户",
"DisplayName:AdminEmailAddress": "管理员电子邮件地址",
"DisplayName:AdminPassword": "管理员密码",
"Features:RecycleStrategy": "资源回收策略",
@@ -54,6 +55,7 @@
"RecycleStrategy:Reserve": "保留",
"RecycleStrategy:Recycle": "回收",
"ManageFeatures": "管理功能",
- "IfUseCustomDataBaseDefaultConnectionStringIsRequiredMessage": "如果使用自定义数据库,默认连接字符串必须输入!"
+ "IfUseCustomDataBaseDefaultConnectionStringIsRequiredMessage": "如果使用自定义数据库,默认连接字符串必须输入!",
+ "ImpersonationTenant": "模拟租户"
}
}
\ No newline at end of file