38 changed files with 1348 additions and 30 deletions
@ -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<IReadOnlyList<FeatureDefinition>> GetAllAsync() |
||||
|
{ |
||||
|
var staticFeatures = await StaticStore.GetFeaturesAsync(); |
||||
|
var dynamicFeatures = await DynamicStore.GetFeaturesAsync(); |
||||
|
|
||||
|
var mergedFeatures = new Dictionary<string, FeatureDefinition>(); |
||||
|
|
||||
|
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<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync() |
||||
|
{ |
||||
|
var staticGroups = await StaticStore.GetGroupsAsync(); |
||||
|
var dynamicGroups = await DynamicStore.GetGroupsAsync(); |
||||
|
|
||||
|
var mergedGroups = new Dictionary<string, FeatureGroupDefinition>(); |
||||
|
|
||||
|
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<FeatureDefinition> 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; |
||||
|
} |
||||
|
} |
||||
@ -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<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync() |
||||
|
{ |
||||
|
var staticGroups = await _staticStore.GetGroupsAsync(); |
||||
|
var dynamicGroups = await _dynamicStore.GetGroupsAsync(); |
||||
|
|
||||
|
var mergedGroups = new Dictionary<string, PermissionGroupDefinition>(); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -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<IReadOnlyList<SettingDefinition>> GetAllAsync() |
||||
|
{ |
||||
|
var staticSettings = await _staticStore.GetAllAsync(); |
||||
|
var dynamicSettings = await _dynamicStore.GetAllAsync(); |
||||
|
|
||||
|
var mergedSettings = new Dictionary<string, SettingDefinition>(); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -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<IReadOnlyList<TemplateDefinition>> GetAllAsync() |
||||
|
{ |
||||
|
var staticTemplates = await StaticStore.GetAllAsync(); |
||||
|
var dynamicTemplates = await DynamicStore.GetAllAsync(); |
||||
|
|
||||
|
var mergedTemplates = new Dictionary<string, TemplateDefinition>(); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,26 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.OpenIddict.Impersonation</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.OpenIddict.Impersonation</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\OpenIddict\Impersonation\Localization\Resources\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\OpenIddict\Impersonation\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.OpenIddict.AspNetCore" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -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<OpenIddictServerBuilder>(builder => |
||||
|
{ |
||||
|
builder.AllowImpersonationFlow(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpOpenIddictExtensionGrantsOptions>(options => |
||||
|
{ |
||||
|
options.Grants.TryAdd( |
||||
|
ImpersonationTokenExtensionGrantConsts.GrantType, |
||||
|
new ImpersonationTokenExtensionGrant()); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpOpenIddictImpersonationModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<AbpOpenIddictResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/OpenIddict/Impersonation/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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<IActionResult> HandleAsync(ExtensionGrantContext context) |
||||
|
{ |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
|
||||
|
var principal = await ValidateAccessTokenAsync(context); |
||||
|
if (principal == null) |
||||
|
{ |
||||
|
return ForbidWithError( |
||||
|
OpenIddictConstants.Errors.InvalidToken, |
||||
|
localizer["InvalidAccessToken"]); |
||||
|
} |
||||
|
|
||||
|
var currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(context); |
||||
|
using (currentPrincipalAccessor.Change(principal)) |
||||
|
{ |
||||
|
return await ProcessImpersonationRequestAsync(context, principal); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<ClaimsPrincipal?> ValidateAccessTokenAsync(ExtensionGrantContext context) |
||||
|
{ |
||||
|
var factory = GetRequiredService<IOpenIddictServerFactory>(context); |
||||
|
var dispatcher = GetRequiredService<IOpenIddictServerDispatcher>(context); |
||||
|
var logger = GetRequiredService<ILogger<ImpersonationTokenExtensionGrant>>(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<IActionResult> ProcessImpersonationRequestAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal) |
||||
|
{ |
||||
|
var request = context.Request; |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
var currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(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<IActionResult> HandleUserImpersonationAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal, |
||||
|
ImpersonationRequest request) |
||||
|
{ |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
var currentUser = GetRequiredService<ICurrentUser>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(context); |
||||
|
var userManager = GetRequiredService<IdentityUserManager>(context); |
||||
|
var permissionChecker = GetRequiredService<IPermissionChecker>(context); |
||||
|
var options = GetRequiredService<IOptions<OpenIddictImpersonationOptions>>(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<IActionResult> HandleTenantImpersonationAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal, |
||||
|
ImpersonationRequest request) |
||||
|
{ |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(context); |
||||
|
var userManager = GetRequiredService<IdentityUserManager>(context); |
||||
|
var permissionChecker = GetRequiredService<IPermissionChecker>(context); |
||||
|
var options = GetRequiredService<IOptions<OpenIddictImpersonationOptions>>(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<IActionResult> HandleDelegatedImpersonationAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal, |
||||
|
Guid userDelegationId) |
||||
|
{ |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
var currentUser = GetRequiredService<ICurrentUser>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(context); |
||||
|
var userManager = GetRequiredService<IdentityUserManager>(context); |
||||
|
var delegationManager = GetRequiredService<IdentityUserDelegationManager>(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<IActionResult> RevertToOriginalUserAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal) |
||||
|
{ |
||||
|
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context); |
||||
|
var currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(context); |
||||
|
var userManager = GetRequiredService<IdentityUserManager>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(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<IActionResult> CreateImpersonatedSignInResult( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal currentPrincipal, |
||||
|
IdentityUser targetUser, |
||||
|
Guid? tenantId, |
||||
|
string action = "ImpersonateUser") |
||||
|
{ |
||||
|
var userClaimsPrincipalFactory = GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>(context); |
||||
|
var currentUser = GetRequiredService<ICurrentUser>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(context); |
||||
|
var currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(context); |
||||
|
|
||||
|
var principal = await userClaimsPrincipalFactory.CreateAsync(targetUser); |
||||
|
var claims = new List<Claim>(); |
||||
|
|
||||
|
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<IActionResult> CreateAuthenticatedSignInResult( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal currentPrincipal, |
||||
|
IdentityUser targetUser) |
||||
|
{ |
||||
|
var userClaimsPrincipalFactory = GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>(context); |
||||
|
var principal = await userClaimsPrincipalFactory.CreateAsync(targetUser); |
||||
|
|
||||
|
return await FinalizeSignInAsync(context, principal, currentPrincipal.GetScopes(), "RevertImpersonation"); |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<IActionResult> FinalizeSignInAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
ClaimsPrincipal principal, |
||||
|
IEnumerable<string> scopes, |
||||
|
string action) |
||||
|
{ |
||||
|
var currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(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<IdentitySecurityLogManager>(context); |
||||
|
var currentUser = GetRequiredService<ICurrentUser>(context); |
||||
|
var currentTenant = GetRequiredService<ICurrentTenant>(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<IEnumerable<string>> GetResourcesAsync( |
||||
|
ExtensionGrantContext context, |
||||
|
IEnumerable<string> scopes) |
||||
|
{ |
||||
|
var resources = new List<string>(); |
||||
|
if (scopes.Any()) |
||||
|
{ |
||||
|
var scopeManager = GetRequiredService<IOpenIddictScopeManager>(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<AbpOpenIddictClaimsPrincipalManager>(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<string, string?> |
||||
|
{ |
||||
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = error, |
||||
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = description |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
protected virtual T GetRequiredService<T>(ExtensionGrantContext context) where T : notnull |
||||
|
{ |
||||
|
return context.HttpContext.RequestServices.GetRequiredService<T>(); |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.OpenIddict.Impersonation; |
||||
|
|
||||
|
public static class ImpersonationTokenExtensionGrantConsts |
||||
|
{ |
||||
|
public const string GrantType = "impersonation"; |
||||
|
} |
||||
@ -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" |
||||
|
} |
||||
|
} |
||||
@ -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": "原始用户不存在,模拟者的用户记录可能已被删除!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
namespace LINGYUN.Abp.OpenIddict.Impersonation; |
||||
|
|
||||
|
public class OpenIddictImpersonationOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 模拟用户时检查权限
|
||||
|
/// </summary>
|
||||
|
public string? ImpersonationPermission { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 模拟租户时检查权限
|
||||
|
/// </summary>
|
||||
|
public string? ImpersonationTenantPermission { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 宿主端模拟租户时默认管理员用户名
|
||||
|
/// </summary>
|
||||
|
public string DefaultTenantAdminUserName { get; set; } |
||||
|
public OpenIddictImpersonationOptions() |
||||
|
{ |
||||
|
DefaultTenantAdminUserName = "admin"; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue