From 5edd019926d6fdd50011161493099c4ed21bf8d7 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Wed, 24 Jul 2024 10:44:49 +0200 Subject: [PATCH 1/8] Add external sign in --- .editorconfig | 2 +- .../VirtoCommerce.Xapi.Core.csproj | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 3 ++ .../Services/ExternalSignInUserBuilder.cs | 29 +++++++++++++++ .../Services/ExternalSignInValidator.cs | 36 +++++++++++++++++++ .../VirtoCommerce.Xapi.Data.csproj | 2 +- src/VirtoCommerce.Xapi.Web/module.manifest | 4 +-- 7 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs create mode 100644 src/VirtoCommerce.Xapi.Data/Services/ExternalSignInValidator.cs diff --git a/.editorconfig b/.editorconfig index 52a5b14..d467f1b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -102,7 +102,7 @@ csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_expression_bodied_lambdas = true:silent diff --git a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj index 26bac04..c941dbd 100644 --- a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj +++ b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/src/VirtoCommerce.Xapi.Data/Extensions/ServiceCollectionExtensions.cs b/src/VirtoCommerce.Xapi.Data/Extensions/ServiceCollectionExtensions.cs index bf53f39..46ef422 100644 --- a/src/VirtoCommerce.Xapi.Data/Extensions/ServiceCollectionExtensions.cs +++ b/src/VirtoCommerce.Xapi.Data/Extensions/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using VirtoCommerce.Platform.Core.Security.ExternalSignIn; using VirtoCommerce.Platform.Security.OpenIddict; using VirtoCommerce.Xapi.Core; using VirtoCommerce.Xapi.Core.Extensions; @@ -52,6 +53,8 @@ public static IServiceCollection AddXCore(this IServiceCollection services, IGra services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // provider for external fields services.AddSingleton(); diff --git a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs new file mode 100644 index 0000000..24266ee --- /dev/null +++ b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs @@ -0,0 +1,29 @@ +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using VirtoCommerce.CustomerModule.Core.Model; +using VirtoCommerce.CustomerModule.Core.Services; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Platform.Core.Security; +using VirtoCommerce.Platform.Core.Security.ExternalSignIn; + +namespace VirtoCommerce.Xapi.Data.Services; + +public class ExternalSignInUserBuilder(IMemberService memberService) : IExternalSignInUserBuilder +{ + public async Task BuildNewUser(ApplicationUser user, ExternalLoginInfo externalLoginInfo) + { + if (user.MemberId is null && user.UserType == UserType.Customer.ToString()) + { + var contact = AbstractTypeFactory.TryCreateInstance(); + contact.Name = externalLoginInfo.Principal.FindFirstValue("name"); + contact.FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName); + contact.LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname); + contact.Emails = [user.Email]; + + await memberService.SaveChangesAsync([contact]); + + user.MemberId = contact.Id; + } + } +} diff --git a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInValidator.cs b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInValidator.cs new file mode 100644 index 0000000..25a602a --- /dev/null +++ b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInValidator.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Platform.Core.Security.ExternalSignIn; +using VirtoCommerce.StoreModule.Core.Services; + +namespace VirtoCommerce.Xapi.Data.Services; + +public class ExternalSignInValidator(IStoreService storeService, + IStoreAuthenticationService storeAuthenticationService) + : IExternalSignInValidator +{ + public async Task ValidateAsync(ExternalSignInRequest request) + { + var store = await storeService.GetNoCloneAsync(request.StoreId); + + if (store is null || + string.IsNullOrEmpty(store.Url) || + !Uri.TryCreate(store.Url, UriKind.Absolute, out var storeUri) || + !IsValidUrl(storeUri, request.OidcUrl) || + !IsValidUrl(storeUri, request.CallbackUrl)) + { + return false; + } + + var schemes = await storeAuthenticationService.GetStoreSchemesAsync(request.StoreId, clone: false); + + return schemes.Any(x => x.IsActive && x.Name.EqualsIgnoreCase(request.AuthenticationType)); + } + + private static bool IsValidUrl(Uri baseUri, string uriString) + { + return Uri.TryCreate(uriString, UriKind.Absolute, out var uri) && baseUri.IsBaseOf(uri); + } +} diff --git a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj index b469227..60960ba 100644 --- a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj +++ b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/VirtoCommerce.Xapi.Web/module.manifest b/src/VirtoCommerce.Xapi.Web/module.manifest index 79c8d62..6c01c59 100644 --- a/src/VirtoCommerce.Xapi.Web/module.manifest +++ b/src/VirtoCommerce.Xapi.Web/module.manifest @@ -4,12 +4,12 @@ 3.801.0 - 3.841.0 + 3.845.0-alpha.12952-vcst-1431 - + From 1b574d57e237b36f4dea24ce3c129f44cae2196d Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Wed, 24 Jul 2024 10:45:03 +0200 Subject: [PATCH 2/8] Add AuthenticationTypes to StoreSettings --- src/VirtoCommerce.Xapi.Core/Models/StoreSettings.cs | 3 +++ .../Schemas/StoreSettingsType.cs | 1 + .../Queries/GetStoreQueryHandler.cs | 10 +++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/VirtoCommerce.Xapi.Core/Models/StoreSettings.cs b/src/VirtoCommerce.Xapi.Core/Models/StoreSettings.cs index 7c0569a..bf80d07 100644 --- a/src/VirtoCommerce.Xapi.Core/Models/StoreSettings.cs +++ b/src/VirtoCommerce.Xapi.Core/Models/StoreSettings.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Identity; namespace VirtoCommerce.Xapi.Core.Models @@ -28,6 +29,8 @@ public class StoreSettings public PasswordOptions PasswordRequirements { get; set; } + public IList AuthenticationTypes { get; set; } + public ModuleSettings[] Modules { get; set; } } } diff --git a/src/VirtoCommerce.Xapi.Core/Schemas/StoreSettingsType.cs b/src/VirtoCommerce.Xapi.Core/Schemas/StoreSettingsType.cs index 8e4516f..f6a6301 100644 --- a/src/VirtoCommerce.Xapi.Core/Schemas/StoreSettingsType.cs +++ b/src/VirtoCommerce.Xapi.Core/Schemas/StoreSettingsType.cs @@ -19,6 +19,7 @@ public StoreSettingsType() Field(x => x.DefaultSelectedForCheckout).Description("Default \"Selected for checkout\" state for new line items and gifts"); Field(x => x.EnvironmentName).Description("Environment name"); Field("passwordRequirements", "Password requirements"); + Field>>("authenticationTypes"); Field>>>("modules"); } } diff --git a/src/VirtoCommerce.Xapi.Data/Queries/GetStoreQueryHandler.cs b/src/VirtoCommerce.Xapi.Data/Queries/GetStoreQueryHandler.cs index 8098644..796bb9b 100644 --- a/src/VirtoCommerce.Xapi.Data/Queries/GetStoreQueryHandler.cs +++ b/src/VirtoCommerce.Xapi.Data/Queries/GetStoreQueryHandler.cs @@ -29,6 +29,7 @@ public class GetStoreQueryHandler : IQueryHandler private readonly IdentityOptions _identityOptions; private readonly GraphQLWebSocketOptions _webSocketOptions; private readonly StoresOptions _storeOptions; + private readonly IStoreAuthenticationService _storeAuthenticationService; public GetStoreQueryHandler( IStoreService storeService, @@ -37,13 +38,15 @@ public GetStoreQueryHandler( ISettingsManager settingsManager, IOptions identityOptions, IOptions webSocketOptions, - IOptions storeOptions) + IOptions storeOptions, + IStoreAuthenticationService storeAuthenticationService) { _storeService = storeService; _storeSearchService = storeSearchService; _storeCurrencyResolver = storeCurrencyResolver; _settingsManager = settingsManager; + _storeAuthenticationService = storeAuthenticationService; _identityOptions = identityOptions.Value; _webSocketOptions = webSocketOptions.Value; _storeOptions = storeOptions.Value; @@ -116,6 +119,11 @@ public async Task Handle(GetStoreQuery request, CancellationToken EnvironmentName = _settingsManager.GetValue(ModuleConstants.Settings.General.EnvironmentName), PasswordRequirements = _identityOptions.Password, + AuthenticationTypes = (await _storeAuthenticationService.GetStoreSchemesAsync(store.Id)) + .Where(x => x.IsActive) + .Select(x => x.Name) + .ToList(), + Modules = ToModulesSettings(store.Settings) }; } From 5d833690d403463d16110c183251af7107cb141f Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Wed, 24 Jul 2024 16:46:23 +0200 Subject: [PATCH 3/8] Update dependencies --- src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj | 2 +- src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj | 2 +- src/VirtoCommerce.Xapi.Web/module.manifest | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj index c941dbd..e98def1 100644 --- a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj +++ b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj index 60960ba..b29af89 100644 --- a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj +++ b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/VirtoCommerce.Xapi.Web/module.manifest b/src/VirtoCommerce.Xapi.Web/module.manifest index 6c01c59..b36bb51 100644 --- a/src/VirtoCommerce.Xapi.Web/module.manifest +++ b/src/VirtoCommerce.Xapi.Web/module.manifest @@ -4,12 +4,12 @@ 3.801.0 - 3.845.0-alpha.12952-vcst-1431 + 3.845.0-alpha.12954-vcst-1431 - + From b3c36afa3233bdf62087df9e67c8fee58e3e4481 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Wed, 7 Aug 2024 15:14:59 +0200 Subject: [PATCH 4/8] Set status for new external contact --- .../Services/ExternalSignInUserBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs index 24266ee..e30e99d 100644 --- a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs +++ b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs @@ -20,6 +20,7 @@ public async Task BuildNewUser(ApplicationUser user, ExternalLoginInfo externalL contact.FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName); contact.LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname); contact.Emails = [user.Email]; + contact.Status = "Approved"; // TODO: Get default external contact status from settings await memberService.SaveChangesAsync([contact]); From 55ba29d21f74f2b87a971676e1fd89772bd523c3 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Thu, 8 Aug 2024 11:41:07 +0200 Subject: [PATCH 5/8] Set status for a new external contact --- .../Services/ExternalSignInUserBuilder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs index e30e99d..6f957a1 100644 --- a/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs +++ b/src/VirtoCommerce.Xapi.Data/Services/ExternalSignInUserBuilder.cs @@ -6,10 +6,13 @@ using VirtoCommerce.Platform.Core.Common; using VirtoCommerce.Platform.Core.Security; using VirtoCommerce.Platform.Core.Security.ExternalSignIn; +using VirtoCommerce.Platform.Core.Settings; +using VirtoCommerce.StoreModule.Core.Services; +using CustomerSettings = VirtoCommerce.CustomerModule.Core.ModuleConstants.Settings.General; namespace VirtoCommerce.Xapi.Data.Services; -public class ExternalSignInUserBuilder(IMemberService memberService) : IExternalSignInUserBuilder +public class ExternalSignInUserBuilder(IStoreService storeService, IMemberService memberService) : IExternalSignInUserBuilder { public async Task BuildNewUser(ApplicationUser user, ExternalLoginInfo externalLoginInfo) { @@ -20,7 +23,12 @@ public async Task BuildNewUser(ApplicationUser user, ExternalLoginInfo externalL contact.FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName); contact.LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname); contact.Emails = [user.Email]; - contact.Status = "Approved"; // TODO: Get default external contact status from settings + + if (!string.IsNullOrEmpty(user.StoreId)) + { + var store = await storeService.GetNoCloneAsync(user.StoreId); + contact.Status = store?.Settings.GetValue(CustomerSettings.ContactDefaultStatus); + } await memberService.SaveChangesAsync([contact]); From ac2843798437f4743bafed222c8942df1c91be65 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Thu, 8 Aug 2024 11:48:05 +0200 Subject: [PATCH 6/8] Update dependencies --- src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj | 2 +- src/VirtoCommerce.Xapi.Web/VirtoCommerce.Xapi.Web.csproj | 2 -- src/VirtoCommerce.Xapi.Web/module.manifest | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj index b29af89..2522da6 100644 --- a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj +++ b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/VirtoCommerce.Xapi.Web/VirtoCommerce.Xapi.Web.csproj b/src/VirtoCommerce.Xapi.Web/VirtoCommerce.Xapi.Web.csproj index d315364..4f33e3d 100644 --- a/src/VirtoCommerce.Xapi.Web/VirtoCommerce.Xapi.Web.csproj +++ b/src/VirtoCommerce.Xapi.Web/VirtoCommerce.Xapi.Web.csproj @@ -9,8 +9,6 @@ - - diff --git a/src/VirtoCommerce.Xapi.Web/module.manifest b/src/VirtoCommerce.Xapi.Web/module.manifest index 872ca23..eaa0dc7 100644 --- a/src/VirtoCommerce.Xapi.Web/module.manifest +++ b/src/VirtoCommerce.Xapi.Web/module.manifest @@ -4,7 +4,7 @@ 3.802.0 - 3.845.0-alpha.12954-vcst-1431 + 3.848.0-alpha.12968-vcst-1431 From 0e42cff7a8589f87ee08069863c2952cefd19260 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Thu, 8 Aug 2024 11:49:51 +0200 Subject: [PATCH 7/8] Don't throw PasswordExpired when using external sign in --- .../Services/IUserManagerCore.cs | 5 ++++ .../Services/UserManagerCore.cs | 23 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/VirtoCommerce.Xapi.Core/Services/IUserManagerCore.cs b/src/VirtoCommerce.Xapi.Core/Services/IUserManagerCore.cs index b46c380..8b579f1 100644 --- a/src/VirtoCommerce.Xapi.Core/Services/IUserManagerCore.cs +++ b/src/VirtoCommerce.Xapi.Core/Services/IUserManagerCore.cs @@ -1,4 +1,6 @@ +using System; using System.Threading.Tasks; +using GraphQL; using VirtoCommerce.Platform.Core.Security; namespace VirtoCommerce.Xapi.Core.Services @@ -7,6 +9,9 @@ public interface IUserManagerCore { Task IsLockedOutAsync(ApplicationUser user); + [Obsolete("Use CheckCurrentUserState()", DiagnosticId = "VC0009", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions/")] Task CheckUserState(string userId, bool allowAnonymous); + + Task CheckCurrentUserState(IResolveFieldContext context, bool allowAnonymous); } } diff --git a/src/VirtoCommerce.Xapi.Data/Services/UserManagerCore.cs b/src/VirtoCommerce.Xapi.Data/Services/UserManagerCore.cs index 0d59c94..cfeb7ff 100644 --- a/src/VirtoCommerce.Xapi.Data/Services/UserManagerCore.cs +++ b/src/VirtoCommerce.Xapi.Data/Services/UserManagerCore.cs @@ -1,7 +1,10 @@ using System; using System.Threading.Tasks; +using GraphQL; using Microsoft.AspNetCore.Identity; using VirtoCommerce.Platform.Core.Security; +using VirtoCommerce.Platform.Security.Extensions; +using VirtoCommerce.Xapi.Core.Extensions; using VirtoCommerce.Xapi.Core.Security.Authorization; using VirtoCommerce.Xapi.Core.Services; @@ -25,7 +28,23 @@ public virtual async Task IsLockedOutAsync(ApplicationUser user) return result; } - public async Task CheckUserState(string userId, bool allowAnonymous) + [Obsolete("Use CheckCurrentUserState()", DiagnosticId = "VC0009", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions/")] + public Task CheckUserState(string userId, bool allowAnonymous) + { + return CheckUserState(userId, allowAnonymous, isExternalSignIn: false); + } + + public Task CheckCurrentUserState(IResolveFieldContext context, bool allowAnonymous) + { + var principal = context.GetCurrentPrincipal(); + var userId = principal.GetCurrentUserId(); + var isExternalSignIn = principal.IsExternalSignIn(); + + return CheckUserState(userId, allowAnonymous, isExternalSignIn); + } + + + private async Task CheckUserState(string userId, bool allowAnonymous, bool isExternalSignIn) { var userManager = _userManagerFactory(); var user = await userManager.FindByIdAsync(userId); @@ -40,7 +59,7 @@ public async Task CheckUserState(string userId, bool allowAnonymous) throw AuthorizationError.AnonymousAccessDenied(); } - if (user.PasswordExpired) + if (user.PasswordExpired && !isExternalSignIn) { throw AuthorizationError.PasswordExpired(); } From 7f50bb85510b3e6b1f611d961f3b6b9656722373 Mon Sep 17 00:00:00 2001 From: artem-dudarev Date: Sat, 10 Aug 2024 00:05:54 +0200 Subject: [PATCH 8/8] Update dependencies --- src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj | 2 +- src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj | 2 +- src/VirtoCommerce.Xapi.Web/module.manifest | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj index e98def1..11d4e66 100644 --- a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj +++ b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj index 2522da6..763e393 100644 --- a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj +++ b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/VirtoCommerce.Xapi.Web/module.manifest b/src/VirtoCommerce.Xapi.Web/module.manifest index 8848358..d032e67 100644 --- a/src/VirtoCommerce.Xapi.Web/module.manifest +++ b/src/VirtoCommerce.Xapi.Web/module.manifest @@ -4,12 +4,12 @@ 3.802.0 - 3.848.0-alpha.12968-vcst-1431 + 3.848.0 - +