diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5b5adb13..aeb4f1b28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: "Tagged Release" on: push: - branches: [ rel-7.3.2 ] + branches: [ rel-7.3.3 ] jobs: tagged-release: @@ -14,4 +14,4 @@ jobs: with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false - automatic_release_tag: "7.3.2" + automatic_release_tag: "7.3.3" diff --git a/aspnet-core/Directory.Build.props b/aspnet-core/Directory.Build.props index 9efa3b881..1ac893c9a 100644 --- a/aspnet-core/Directory.Build.props +++ b/aspnet-core/Directory.Build.props @@ -1,8 +1,8 @@  - 7.3.2 + 7.3.3 2.3.2 - 7.3.2 + 7.3.3 1.11.0 1.0.2 7.2.0 diff --git a/aspnet-core/common.props b/aspnet-core/common.props index 901bccb12..fb2f0cecf 100644 --- a/aspnet-core/common.props +++ b/aspnet-core/common.props @@ -1,7 +1,7 @@ latest - 7.3.2 + 7.3.3 colin $(NoWarn);CS1591;CS0436;CS8618;NU1803 https://github.com/colinin/abp-next-admin diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Portal/LINGYUN/Abp/OpenIddict/Portal/PortalTokenExtensionGrant.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Portal/LINGYUN/Abp/OpenIddict/Portal/PortalTokenExtensionGrant.cs index 8d6231fed..244bc5d99 100644 --- a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Portal/LINGYUN/Abp/OpenIddict/Portal/PortalTokenExtensionGrant.cs +++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Portal/LINGYUN/Abp/OpenIddict/Portal/PortalTokenExtensionGrant.cs @@ -1,6 +1,5 @@ using LINGYUN.Platform.Portal; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -24,6 +23,7 @@ using Volo.Abp.OpenIddict.ExtensionGrantTypes; using Volo.Abp.Uow; using Volo.Abp.Validation; +using static Volo.Abp.OpenIddict.Controllers.TokenController; using IdentityUser = Volo.Abp.Identity.IdentityUser; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; @@ -34,7 +34,6 @@ public class PortalTokenExtensionGrant : ITokenExtensionGrant protected IAbpLazyServiceProvider LazyServiceProvider { get; set; } protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); - protected IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService(); protected IEnterpriseRepository EnterpriseRepository => LazyServiceProvider.LazyGetRequiredService(); protected SignInManager SignInManager => LazyServiceProvider.LazyGetRequiredService>(); protected IdentityUserManager UserManager => LazyServiceProvider.LazyGetRequiredService(); @@ -47,12 +46,12 @@ public class PortalTokenExtensionGrant : ITokenExtensionGrant protected IOptions IdentityOptions => LazyServiceProvider.LazyGetRequiredService>(); protected IOptions MultiTenancyOptions => LazyServiceProvider.LazyGetRequiredService>(); protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService(); + + [UnitOfWork] public async virtual Task HandleAsync(ExtensionGrantContext context) { LazyServiceProvider = context.HttpContext.RequestServices.GetRequiredService(); - using var scope = ServiceScopeFactory.CreateScope(); - using var unitOfWork = UnitOfWorkManager.Begin(); var enterprise = context.Request.GetParameter("EnterpriseId")?.ToString(); Guid? tenantId = null; @@ -91,8 +90,9 @@ public async virtual Task HandleAsync(ExtensionGrantContext conte } } - protected virtual async Task HandlePasswordAsync(ExtensionGrantContext context) + protected async virtual Task HandlePasswordAsync(ExtensionGrantContext context) { + using var scope = ServiceScopeFactory.CreateScope(); await ReplaceEmailToUsernameOfInputIfNeeds(context.Request); IdentityUser user = null; @@ -101,7 +101,7 @@ protected virtual async Task HandlePasswordAsync(ExtensionGrantCo { foreach (var externalLoginProviderInfo in AbpIdentityOptions.Value.ExternalLoginProviders.Values) { - var externalLoginProvider = (IExternalLoginProvider)context.HttpContext.RequestServices + var externalLoginProvider = (IExternalLoginProvider)scope.ServiceProvider .GetRequiredService(externalLoginProviderInfo.Type); if (await externalLoginProvider.TryAuthenticateAsync(context.Request.Username, context.Request.Password)) @@ -148,6 +148,14 @@ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() var result = await SignInManager.CheckPasswordSignInAsync(user, context.Request.Password, true); if (!result.Succeeded) { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, + Action = result.ToIdentitySecurityLogAction(), + UserName = context.Request.Username, + ClientId = context.Request.ClientId + }); + string errorDescription; if (result.IsLockedOut) { @@ -157,6 +165,17 @@ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() else if (result.IsNotAllowed) { Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.Request.Username); + + if (user.ShouldChangePasswordOnNextLogin) + { + return await HandleShouldChangePasswordOnNextLoginAsync(context, user, context.Request.Password); + } + + if (await UserManager.ShouldPeriodicallyChangePasswordAsync(user)) + { + return await HandlePeriodicallyChangePasswordAsync(context, user, context.Request.Password); + } + errorDescription = "You are not allowed to login! Your account is inactive or needs to confirm your email/phone number."; } else @@ -179,14 +198,6 @@ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() return await HandleTwoFactorLoginAsync(context, user); } - await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext - { - Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, - Action = result.ToIdentitySecurityLogAction(), - UserName = context.Request.Username, - ClientId = context.Request.ClientId - }); - return await SetSuccessResultAsync(context, user); } @@ -264,6 +275,96 @@ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext } } + protected virtual async Task HandleShouldChangePasswordOnNextLoginAsync(ExtensionGrantContext context, IdentityUser user, string currentPassword) + { + return await HandleChangePasswordAsync(context, user, currentPassword, ChangePasswordType.ShouldChangePasswordOnNextLogin); + } + + protected virtual async Task HandlePeriodicallyChangePasswordAsync(ExtensionGrantContext context, IdentityUser user, string currentPassword) + { + return await HandleChangePasswordAsync(context, user, currentPassword, ChangePasswordType.PeriodicallyChangePassword); + } + + protected virtual async Task HandleChangePasswordAsync(ExtensionGrantContext context, IdentityUser user, string currentPassword, ChangePasswordType changePasswordType) + { + var changePasswordToken = context.Request.GetParameter("ChangePasswordToken")?.ToString(); + var newPassword = context.Request.GetParameter("NewPassword")?.ToString(); + if (!changePasswordToken.IsNullOrWhiteSpace() && !currentPassword.IsNullOrWhiteSpace() && !newPassword.IsNullOrWhiteSpace()) + { + if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString(), changePasswordToken)) + { + var changePasswordResult = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword); + if (changePasswordResult.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, + Action = IdentitySecurityLogActionConsts.ChangePassword, + UserName = context.Request.Username, + ClientId = context.Request.ClientId + }); + + if (changePasswordType == ChangePasswordType.ShouldChangePasswordOnNextLogin) + { + user.SetShouldChangePasswordOnNextLogin(false); + } + + await UserManager.UpdateAsync(user); + return await SetSuccessResultAsync(context, user); + } + else + { + Logger.LogInformation("ChangePassword failed for username: {username}, reason: {changePasswordResult}", context.Request.Username, changePasswordResult.Errors.Select(x => x.Description).JoinAsString(", ")); + + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = changePasswordResult.Errors.Select(x => x.Description).JoinAsString(", ") + }); + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + else + { + Logger.LogInformation("Authentication failed for username: {username}, reason: InvalidAuthenticatorCode", context.Request.Username); + + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Invalid authenticator code!" + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + else + { + Logger.LogInformation($"Authentication failed for username: {{{context.Request.Username}}}, reason: {{{changePasswordType.ToString()}}}"); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, + Action = OpenIddictSecurityLogActionConsts.LoginNotAllowed, + UserName = context.Request.Username, + ClientId = context.Request.ClientId + }); + + var properties = new AuthenticationProperties( + items: new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = changePasswordType.ToString() + }, + parameters: new Dictionary + { + ["userId"] = user.Id.ToString("N"), + ["changePasswordToken"] = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString()) + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + protected virtual async Task SetSuccessResultAsync(ExtensionGrantContext context, IdentityUser user) { // Create a new ClaimsPrincipal containing the claims that diff --git a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs index f295a766f..a5d806a25 100644 --- a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs +++ b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs @@ -314,6 +314,18 @@ await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.RequireUpperca await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.RequireNonAlphanumeric, providerName, providerKey), ValueType.Boolean, providerName); + passwordSetting.AddDetail( + SettingDefinitionManager.Get(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword, providerName, providerKey), + ValueType.Boolean, + providerName); + passwordSetting.AddDetail( + SettingDefinitionManager.Get(IdentitySettingNames.Password.PasswordChangePeriodDays), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.PasswordChangePeriodDays, providerName, providerKey), + ValueType.Number, + providerName); #endregion diff --git a/gateways/Directory.Build.props b/gateways/Directory.Build.props index 3778bee2b..a76bb4922 100644 --- a/gateways/Directory.Build.props +++ b/gateways/Directory.Build.props @@ -1,7 +1,7 @@  - 7.3.2 - 7.3.2 + 7.3.3 + 7.3.3 1.11.0 7.2.0 1.5.10 diff --git a/gateways/common.props b/gateways/common.props index 2f65212b0..2d0f10fd7 100644 --- a/gateways/common.props +++ b/gateways/common.props @@ -1,7 +1,7 @@ latest - 7.3.2 + 7.3.3 colin $(NoWarn);CS1591;CS0436;CS8618;NU1803 https://github.com/colinin/abp-next-admin @@ -23,8 +23,4 @@ - - $(SolutionDir)LocalNuget - - \ No newline at end of file diff --git a/gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/LINGYUN.MicroService.Internal.ApiGateway.sln b/gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/LINGYUN.MicroService.Internal.ApiGateway.sln index 7a8e5a763..c62116b96 100644 --- a/gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/LINGYUN.MicroService.Internal.ApiGateway.sln +++ b/gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/LINGYUN.MicroService.Internal.ApiGateway.sln @@ -7,6 +7,39 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.MicroService.Intern EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8067AED-2B6E-4134-AAF8-9101457D709A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F33DFD2B-F97E-4169-A68C-7B3DD681A5F7}" + ProjectSection(SolutionItems) = preProject + ..\..\common.props = ..\..\common.props + ..\..\configureawait.props = ..\..\configureawait.props + ..\..\Directory.Build.props = ..\..\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{80E45092-1181-456D-B88C-7DDCB7F16368}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "auditing", "auditing", "{F05E560E-0126-4949-9092-B1490A4E1199}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logging", "logging", "{3B460BD3-26EE-4BAC-A8B8-CFDF5F108A63}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "localization", "localization", "{7D797267-1682-4D9D-971B-C5079AD4AEDE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elasticsearch", "elasticsearch", "{66DE14AB-D2B5-438E-AB6B-0B0674BEEBA1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Elasticsearch", "..\..\..\aspnet-core\modules\elasticsearch\LINGYUN.Abp.Elasticsearch\LINGYUN.Abp.Elasticsearch.csproj", "{15A60DC9-B5F5-4F72-A97D-BECE5722907F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AuditLogging", "..\..\..\aspnet-core\modules\auditing\LINGYUN.Abp.AuditLogging\LINGYUN.Abp.AuditLogging.csproj", "{84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AuditLogging.Elasticsearch", "..\..\..\aspnet-core\modules\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj", "{90F6E585-768C-42F1-B5B7-78716A2C515D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.CultureMap", "..\..\..\aspnet-core\modules\localization\LINGYUN.Abp.Localization.CultureMap\LINGYUN.Abp.Localization.CultureMap.csproj", "{782A9F27-BE5D-422F-B475-DAC6E9A0C160}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Serilog.Enrichers.Application", "..\..\..\aspnet-core\modules\logging\LINGYUN.Abp.Serilog.Enrichers.Application\LINGYUN.Abp.Serilog.Enrichers.Application.csproj", "{177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Serilog.Enrichers.UniqueId", "..\..\..\aspnet-core\modules\logging\LINGYUN.Abp.Serilog.Enrichers.UniqueId\LINGYUN.Abp.Serilog.Enrichers.UniqueId.csproj", "{13EDA39A-A859-447A-A6A6-056181B66EDC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{EE01F3C2-14D1-4089-8C02-00C8083D9795}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.IdGenerator", "..\..\..\aspnet-core\modules\common\LINGYUN.Abp.IdGenerator\LINGYUN.Abp.IdGenerator.csproj", "{30A639F6-08C2-4250-A79E-9D766600F7F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,12 +50,52 @@ Global {00A2F7A3-BEC3-48F4-A91C-5A336C32A5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {00A2F7A3-BEC3-48F4-A91C-5A336C32A5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {00A2F7A3-BEC3-48F4-A91C-5A336C32A5D2}.Release|Any CPU.Build.0 = Release|Any CPU + {15A60DC9-B5F5-4F72-A97D-BECE5722907F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15A60DC9-B5F5-4F72-A97D-BECE5722907F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15A60DC9-B5F5-4F72-A97D-BECE5722907F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15A60DC9-B5F5-4F72-A97D-BECE5722907F}.Release|Any CPU.Build.0 = Release|Any CPU + {84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8}.Release|Any CPU.Build.0 = Release|Any CPU + {90F6E585-768C-42F1-B5B7-78716A2C515D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90F6E585-768C-42F1-B5B7-78716A2C515D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90F6E585-768C-42F1-B5B7-78716A2C515D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90F6E585-768C-42F1-B5B7-78716A2C515D}.Release|Any CPU.Build.0 = Release|Any CPU + {782A9F27-BE5D-422F-B475-DAC6E9A0C160}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {782A9F27-BE5D-422F-B475-DAC6E9A0C160}.Debug|Any CPU.Build.0 = Debug|Any CPU + {782A9F27-BE5D-422F-B475-DAC6E9A0C160}.Release|Any CPU.ActiveCfg = Release|Any CPU + {782A9F27-BE5D-422F-B475-DAC6E9A0C160}.Release|Any CPU.Build.0 = Release|Any CPU + {177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC}.Release|Any CPU.Build.0 = Release|Any CPU + {13EDA39A-A859-447A-A6A6-056181B66EDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13EDA39A-A859-447A-A6A6-056181B66EDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13EDA39A-A859-447A-A6A6-056181B66EDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13EDA39A-A859-447A-A6A6-056181B66EDC}.Release|Any CPU.Build.0 = Release|Any CPU + {30A639F6-08C2-4250-A79E-9D766600F7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30A639F6-08C2-4250-A79E-9D766600F7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30A639F6-08C2-4250-A79E-9D766600F7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30A639F6-08C2-4250-A79E-9D766600F7F0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {00A2F7A3-BEC3-48F4-A91C-5A336C32A5D2} = {E8067AED-2B6E-4134-AAF8-9101457D709A} + {F05E560E-0126-4949-9092-B1490A4E1199} = {80E45092-1181-456D-B88C-7DDCB7F16368} + {3B460BD3-26EE-4BAC-A8B8-CFDF5F108A63} = {80E45092-1181-456D-B88C-7DDCB7F16368} + {7D797267-1682-4D9D-971B-C5079AD4AEDE} = {80E45092-1181-456D-B88C-7DDCB7F16368} + {66DE14AB-D2B5-438E-AB6B-0B0674BEEBA1} = {80E45092-1181-456D-B88C-7DDCB7F16368} + {15A60DC9-B5F5-4F72-A97D-BECE5722907F} = {66DE14AB-D2B5-438E-AB6B-0B0674BEEBA1} + {84F2D49C-4994-4E8E-B3BC-6C8DD34CEEE8} = {F05E560E-0126-4949-9092-B1490A4E1199} + {90F6E585-768C-42F1-B5B7-78716A2C515D} = {F05E560E-0126-4949-9092-B1490A4E1199} + {782A9F27-BE5D-422F-B475-DAC6E9A0C160} = {7D797267-1682-4D9D-971B-C5079AD4AEDE} + {177BB8EB-7BBB-46D0-B7EC-01E176F7CEAC} = {3B460BD3-26EE-4BAC-A8B8-CFDF5F108A63} + {13EDA39A-A859-447A-A6A6-056181B66EDC} = {3B460BD3-26EE-4BAC-A8B8-CFDF5F108A63} + {EE01F3C2-14D1-4089-8C02-00C8083D9795} = {80E45092-1181-456D-B88C-7DDCB7F16368} + {30A639F6-08C2-4250-A79E-9D766600F7F0} = {EE01F3C2-14D1-4089-8C02-00C8083D9795} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {972464AB-1B23-4D87-89A2-13271E1873B9}