diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Controllers/AdminController.cs index 4fd7b4f74d6..6cabfd3f461 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Controllers/AdminController.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -53,7 +54,7 @@ public async Task Localize(string contentItemId, string targetCul var checkContentItem = await _contentManager.NewAsync(contentItem.ContentType); // Set the current user as the owner to check for ownership permissions on creation - checkContentItem.Owner = User.Identity.Name; + checkContentItem.Owner = User.FindFirstValue(ClaimTypes.NameIdentifier); if (!await _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, checkContentItem)) { diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Security/LocalizeContentAuthorizationHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Security/LocalizeContentAuthorizationHandler.cs index b611b372adc..27f05425e90 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Security/LocalizeContentAuthorizationHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/Security/LocalizeContentAuthorizationHandler.cs @@ -74,7 +74,7 @@ private static bool HasOwnership(ClaimsPrincipal user, ContentItem content) return false; } - return user.Identity.Name == content.Owner; + return user.FindFirstValue(ClaimTypes.NameIdentifier) == content.Owner; } private static bool OwnerVariationExists(Permission permission) diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/AllContentDeploymentSource.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/AllContentDeploymentSource.cs index dc7ce703610..323d8cae69f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/AllContentDeploymentSource.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/AllContentDeploymentSource.cs @@ -40,7 +40,7 @@ public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlan if (allContentStep.ExportAsSetupRecipe) { - objectData[nameof(ContentItem.Owner)] = "[js: parameters('AdminUsername')]"; + objectData[nameof(ContentItem.Owner)] = "[js: parameters('AdminUserId')]"; objectData[nameof(ContentItem.Author)] = "[js: parameters('AdminUsername')]"; objectData[nameof(ContentItem.ContentItemId)] = "[js: uuid()]"; objectData.Remove(nameof(ContentItem.ContentItemVersionId)); diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Drivers/OwnerEditorDriver.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Drivers/OwnerEditorDriver.cs index db3e2791dd9..06c2330b0c4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Drivers/OwnerEditorDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Drivers/OwnerEditorDriver.cs @@ -1,5 +1,7 @@ +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Localization; using OrchardCore.ContentManagement.Display.ContentDisplay; @@ -8,7 +10,7 @@ using OrchardCore.Contents.ViewModels; using OrchardCore.DisplayManagement.Views; using OrchardCore.Security; -using OrchardCore.Users.Services; +using OrchardCore.Users; namespace OrchardCore.Contents.Drivers { @@ -16,17 +18,17 @@ public class OwnerEditorDriver : ContentPartDisplayDriver { private readonly IAuthorizationService _authorizationService; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IUserService _userService; + private readonly UserManager _userManager; private readonly IStringLocalizer S; public OwnerEditorDriver(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor, - IUserService userService, + UserManager userManager, IStringLocalizer stringLocalizer) { _authorizationService = authorizationService; _httpContextAccessor = httpContextAccessor; - _userService = userService; + _userManager = userManager; S = stringLocalizer; } @@ -43,9 +45,14 @@ public override async Task EditAsync(CommonPart part, BuildPartE if (settings.DisplayOwnerEditor) { - return Initialize("CommonPart_Edit__Owner", model => + return Initialize("CommonPart_Edit__Owner", async model => { - model.Owner = part.ContentItem.Owner; + if (part.ContentItem.Owner != null) + { + // TODO Move this editor to a user picker. + var user = await _userManager.FindByIdAsync(part.ContentItem.Owner); + model.OwnerName = user.UserName; + } }); } @@ -67,7 +74,7 @@ public override async Task UpdateAsync(CommonPart part, UpdatePa { if (part.ContentItem.Owner == null) { - part.ContentItem.Owner = currentUser.Identity.Name; + part.ContentItem.Owner = currentUser.FindFirstValue(ClaimTypes.NameIdentifier); } } else @@ -76,23 +83,24 @@ public override async Task UpdateAsync(CommonPart part, UpdatePa if (part.ContentItem.Owner != null) { - model.Owner = part.ContentItem.Owner; + var user = await _userManager.FindByIdAsync(part.ContentItem.Owner); + model.OwnerName = user.UserName; } - var priorOwner = model.Owner; + var priorOwnerName = model.OwnerName; await context.Updater.TryUpdateModelAsync(model, Prefix); - if (!string.IsNullOrEmpty(part.ContentItem.Owner) && model.Owner != priorOwner) + if (model.OwnerName != priorOwnerName) { - var newOwner = await _userService.GetUserAsync(model.Owner); + var newOwner = (await _userManager.FindByNameAsync(model.OwnerName)); if (newOwner == null) { - context.Updater.ModelState.AddModelError("CommonPart.Owner", S["Invalid user name"]); + context.Updater.ModelState.AddModelError("CommonPart.OwnerName", S["Invalid user name"]); } else { - part.ContentItem.Owner = newOwner.UserName; + part.ContentItem.Owner = await _userManager.GetUserIdAsync(newOwner); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Security/ContentTypeAuthorizationHandler.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Security/ContentTypeAuthorizationHandler.cs index b8e1a9bb96e..74e2427f6da 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Security/ContentTypeAuthorizationHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Security/ContentTypeAuthorizationHandler.cs @@ -116,7 +116,7 @@ private static bool HasOwnership(ClaimsPrincipal user, ContentItem content) return false; } - return user.Identity.Name == content.Owner; + return user.FindFirstValue(ClaimTypes.NameIdentifier) == content.Owner; } private static bool OwnerVariationExists(Permission permission) diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Services/DefaultContentsAdminListFilter.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Services/DefaultContentsAdminListFilter.cs index 538f76bc178..62ce976801f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Services/DefaultContentsAdminListFilter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Services/DefaultContentsAdminListFilter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -39,6 +40,7 @@ public DefaultContentsAdminListFilter( public async Task FilterAsync(ContentOptionsViewModel model, IQuery query, IUpdateModel updater) { var user = _httpContextAccessor.HttpContext.User; + var userNameIdentifier = user.FindFirstValue(ClaimTypes.NameIdentifier); if (!String.IsNullOrEmpty(model.DisplayText)) { @@ -72,7 +74,7 @@ public async Task FilterAsync(ContentOptionsViewModel model, IQuery // We display a specific type even if it's not listable so that admin pages // can reuse the Content list page for specific types. var contentItem = await _contentManager.NewAsync(contentTypeDefinition.Name); - contentItem.Owner = user.Identity.Name; + contentItem.Owner = userNameIdentifier; var hasContentListPermission = await _authorizationService.AuthorizeAsync(user, ContentTypePermissionsHelper.CreateDynamicPermission(ContentTypePermissionsHelper.PermissionTemplates[CommonPermissions.ListContent.Name], contentTypeDefinition), contentItem); if (hasContentListPermission) @@ -81,7 +83,7 @@ public async Task FilterAsync(ContentOptionsViewModel model, IQuery } else { - query.With(x => x.ContentType == model.SelectedContentType && x.Owner == user.Identity.Name); + query.With(x => x.ContentType == model.SelectedContentType && x.Owner == userNameIdentifier); } } } @@ -98,7 +100,7 @@ public async Task FilterAsync(ContentOptionsViewModel model, IQuery // We want to list the content item if the user can edit their own items at least. // It might display content items the user won't be able to edit though. var contentItem = await _contentManager.NewAsync(ctd.Name); - contentItem.Owner = user.Identity.Name; + contentItem.Owner = userNameIdentifier; var hasEditPermission = await _authorizationService.AuthorizeAsync(user, CommonPermissions.EditContent, contentItem); if (hasEditPermission) @@ -123,7 +125,7 @@ public async Task FilterAsync(ContentOptionsViewModel model, IQuery if (authorizedContentTypes.Any() && !canListAllContent) { - query.With().Where(x => (x.ContentType.IsIn(authorizedContentTypes.Select(t => t.Name).ToArray())) || (x.ContentType.IsIn(unauthorizedContentTypes.Select(t => t.Name).ToArray()) && x.Owner == user.Identity.Name)); + query.With().Where(x => (x.ContentType.IsIn(authorizedContentTypes.Select(t => t.Name).ToArray())) || (x.ContentType.IsIn(unauthorizedContentTypes.Select(t => t.Name).ToArray()) && x.Owner == userNameIdentifier)); } else { @@ -134,13 +136,13 @@ public async Task FilterAsync(ContentOptionsViewModel model, IQuery // we bypass the corresponding ContentsStatus by owned content filtering if (!canListAllContent) { - query.With(x => x.Owner == user.Identity.Name); + query.With(x => x.Owner == userNameIdentifier); } else { if (model.ContentsStatus == ContentsStatus.Owner) { - query.With(x => x.Owner == user.Identity.Name); + query.With(x => x.Owner == userNameIdentifier); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/ViewModels/OwnerEditorViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Contents/ViewModels/OwnerEditorViewModel.cs index 5db9edd33e6..c1b43915d33 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/ViewModels/OwnerEditorViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/ViewModels/OwnerEditorViewModel.cs @@ -2,6 +2,6 @@ namespace OrchardCore.Contents.ViewModels { public class OwnerEditorViewModel { - public string Owner { get; set; } + public string OwnerName { get; set; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Views/CommonPart-Owner.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Contents/Views/CommonPart-Owner.Edit.cshtml index 501f90c3856..39226457dca 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Views/CommonPart-Owner.Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Views/CommonPart-Owner.Edit.cshtml @@ -1,9 +1,9 @@ @using OrchardCore.Contents.ViewModels; @model OwnerEditorViewModel -
- - - - @T["The owner of the content item."] +
+ + + + @T["The name of the owner of the content item."]
diff --git a/src/OrchardCore.Modules/OrchardCore.Lists/RemotePublishing/MetaWeblogHandler.cs b/src/OrchardCore.Modules/OrchardCore.Lists/RemotePublishing/MetaWeblogHandler.cs index cd91f0a6dc8..f0c21530cf0 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lists/RemotePublishing/MetaWeblogHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lists/RemotePublishing/MetaWeblogHandler.cs @@ -277,7 +277,7 @@ private async Task MetaWeblogNewPostAsync( var postType = GetContainedContentTypes(list).FirstOrDefault(); var contentItem = await _contentManager.NewAsync(postType.Name); - contentItem.Owner = userName; + contentItem.Owner = user.FindFirstValue(ClaimTypes.NameIdentifier); contentItem.Alter(x => x.ListContentItemId = list.ContentItemId); foreach (var driver in _metaWeblogDrivers) diff --git a/src/OrchardCore.Modules/OrchardCore.Recipes/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Recipes/Controllers/AdminController.cs index 51dad79f487..e69905f879f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Recipes/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Recipes/Controllers/AdminController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -112,6 +113,7 @@ public async Task Execute(string basePath, string fileName) { site.SiteName, AdminUsername = User.Identity.Name, + AdminUserId = User.FindFirstValue(ClaimTypes.NameIdentifier) }, CancellationToken.None); } diff --git a/src/OrchardCore.Modules/OrchardCore.Settings/Services/SetupEventHandler.cs b/src/OrchardCore.Modules/OrchardCore.Settings/Services/SetupEventHandler.cs index c3579432ed2..9fc9873f168 100644 --- a/src/OrchardCore.Modules/OrchardCore.Settings/Services/SetupEventHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Settings/Services/SetupEventHandler.cs @@ -19,6 +19,7 @@ public SetupEventHandler(ISiteService siteService) public async Task Setup( string siteName, string userName, + string userId, string email, string password, string dbProvider, @@ -31,7 +32,7 @@ Action reportError // Updating site settings var siteSettings = await _siteService.LoadSiteSettingsAsync(); siteSettings.SiteName = siteName; - siteSettings.SuperUser = userName; + siteSettings.SuperUser = userId; siteSettings.TimeZoneId = siteTimeZone; await _siteService.UpdateSiteSettingsAsync(siteSettings); diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs index 89829e5ec25..4d75c2ff29e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs @@ -288,7 +288,7 @@ private async Task LoggedInActionResult(IUser user, string return input["Roles"] = ((User)user).RoleNames; input["Provider"] = info?.LoginProvider; await workflowManager.TriggerEventAsync(nameof(Workflows.Activities.UserLoggedInEvent), - input: input, correlationId: ((User)user).Id.ToString()); + input: input, correlationId: ((User)user).UserId); } return RedirectToLocal(returnUrl); diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs index a273458c8af..a3465270dc0 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs @@ -142,7 +142,7 @@ public async Task Index(UserIndexOptions options, PagerParameters { userEntries.Add(new UserEntry { - Id = user.Id, + UserId = user.UserId, Shape = await _userDisplayManager.BuildDisplayAsync(user, updater: _updateModelAccessor.ModelUpdater, displayType: "SummaryAdmin") } ); @@ -192,11 +192,11 @@ public ActionResult IndexFilterPOST(UsersIndexViewModel model) [HttpPost, ActionName("Index")] [FormValueRequired("submit.BulkAction")] - public async Task IndexPOST(UserIndexOptions options, IEnumerable itemIds) + public async Task IndexPOST(UserIndexOptions options, IEnumerable itemIds) { if (itemIds?.Count() > 0) { - var checkedContentItems = await _session.Query().Where(x => x.DocumentId.IsIn(itemIds)).ListAsync(); + var checkedContentItems = await _session.Query().Where(x => x.UserId.IsIn(itemIds)).ListAsync(); switch (options.BulkAction) { case UsersBulkAction.None: diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ControllerExtensions.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ControllerExtensions.cs index c4afb18d1f1..f4a0328d95f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ControllerExtensions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ControllerExtensions.cs @@ -97,7 +97,7 @@ internal static async Task SendEmailConfirmationTokenAsync(this Controll { var userManager = controller.ControllerContext.HttpContext.RequestServices.GetRequiredService>(); var code = await userManager.GenerateEmailConfirmationTokenAsync(user); - var callbackUrl = controller.Url.Action("ConfirmEmail", "Registration", new { userId = user.Id, code }, protocol: controller.HttpContext.Request.Scheme); + var callbackUrl = controller.Url.Action("ConfirmEmail", "Registration", new { userId = user.UserId, code }, protocol: controller.HttpContext.Request.Scheme); await SendEmailAsync(controller, user.Email, subject, new ConfirmEmailViewModel() { User = user, ConfirmEmailUrl = callbackUrl }); return callbackUrl; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Migrations.cs b/src/OrchardCore.Modules/OrchardCore.Users/Migrations.cs index b211eafbe95..d28d7a470e0 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Migrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Migrations.cs @@ -1,16 +1,46 @@ +using System.Threading.Tasks; using OrchardCore.Data.Migration; using OrchardCore.Users.Indexes; +using OrchardCore.Users.Models; +using YesSql; using YesSql.Sql; namespace OrchardCore.Users { public class Migrations : DataMigration { + private readonly ISession _session; + + public Migrations(ISession session) + { + _session = session; + } + + // This is a sequenced migration. On a new schemas this is complete after UpdateFrom2. public int Create() { SchemaBuilder.CreateMapIndexTable(table => table - .Column("NormalizedUserName") + .Column("NormalizedUserName") // TODO These should have defaults. on SQL Server they will fall at 255. Exceptions are currently thrown if you go over that. .Column("NormalizedEmail") + .Column("IsEnabled", c => c.NotNull().WithDefault(true)) + .Column("UserId") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .CreateIndex("IDX_UserIndex_IsEnabled", "DocumentId", "IsEnabled") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .CreateIndex("IDX_UserIndex_UserId", "DocumentId", "UserId") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + // This index will be used for lookups when logging in. + .CreateIndex("IDX_UserIndex_UserName", "DocumentId", "NormalizedUserName") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .CreateIndex("IDX_UserIndex_Email", "DocumentId", "NormalizedEmail") ); SchemaBuilder.CreateReduceIndexTable(table => table @@ -35,7 +65,9 @@ public int UpdateFrom2() .Column(nameof(UserByClaimIndex.ClaimType)) .Column(nameof(UserByClaimIndex.ClaimValue)), null); - return 3; + + // Return 6 here to skip migrations on new database schemas. + return 6; } public int UpdateFrom3() @@ -49,5 +81,42 @@ public int UpdateFrom3() return 4; } + + // UserId database migration. + public int UpdateFrom4() + { + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .AddColumn("UserId")); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .CreateIndex("IDX_UserIndex_UserId", "DocumentId", "UserId") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + // This index will be used for lookups when logging in. + .CreateIndex("IDX_UserIndex_UserName", "DocumentId", "NormalizedUserName") + ); + + SchemaBuilder.AlterTable(nameof(UserIndex), table => table + .CreateIndex("IDX_UserIndex_Email", "DocumentId", "NormalizedEmail") + ); + + return 5; + } + + // UserId column is added. This initializes the UserId property to the UserName for existing users. + // The UserName property rather than the NormalizedUserName is used as the ContentItem.Owner property matches the UserName. + // New users will be created with a generated Id. + public async Task UpdateFrom5Async() + { + var users = await _session.Query().ListAsync(); + foreach(var user in users) + { + user.UserId = user.UserName; + _session.Save(user); + } + + return 6; + } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Services/DefaultUserIdGenerator.cs b/src/OrchardCore.Modules/OrchardCore.Users/Services/DefaultUserIdGenerator.cs new file mode 100644 index 00000000000..f0d5329d59d --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/Services/DefaultUserIdGenerator.cs @@ -0,0 +1,19 @@ +using OrchardCore.Entities; + +namespace OrchardCore.Users.Services +{ + public class DefaultUserIdGenerator : IUserIdGenerator + { + private readonly IIdGenerator _generator; + + public DefaultUserIdGenerator(IIdGenerator generator) + { + _generator = generator; + } + + public string GenerateUniqueId(IUser user) + { + return _generator.GenerateUniqueId(); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Services/SetupEventHandler.cs b/src/OrchardCore.Modules/OrchardCore.Users/Services/SetupEventHandler.cs index 288db415cca..76fb5144096 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Services/SetupEventHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Services/SetupEventHandler.cs @@ -20,6 +20,7 @@ public SetupEventHandler(IUserService userService) public Task Setup( string siteName, string userName, + string userId, string email, string password, string dbProvider, @@ -32,6 +33,7 @@ Action reportError var user = new User { UserName = userName, + UserId = userId, Email = email, RoleNames = new string[] { "Administrator" }, EmailConfirmed = true diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Users/Startup.cs index 3281099bead..d569fbae234 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Startup.cs @@ -15,6 +15,7 @@ using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Theming; +using OrchardCore.Entities; using OrchardCore.Environment.Commands; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; @@ -173,6 +174,8 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped, DefaultUserClaimsPrincipalFactory>(); + services.AddIdGeneration(); + services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/UsersIndexViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/UsersIndexViewModel.cs index 431db54077d..a837186c249 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/UsersIndexViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/UsersIndexViewModel.cs @@ -14,7 +14,7 @@ public class UsersIndexViewModel public class UserEntry { public dynamic Shape { get; set; } - public int Id { get; set; } + public string UserId { get; set; } } public class UserIndexOptions diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/Index.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/Index.cshtml index 26bbb078329..a084b27b355 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/Index.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/Index.cshtml @@ -72,8 +72,8 @@ {
  • - - + +
    @await DisplayAsync(entry.Shape)
  • diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/UserButtons.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/UserButtons.cshtml index 7a698757b33..39268490bdc 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/UserButtons.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/UserButtons.cshtml @@ -5,14 +5,14 @@ var user = Model.User as User; var currentUser = user.UserName == User.Identity.Name; } -@T["Edit"] -@T["Delete"] +@T["Edit"] +@T["Delete"] -@T["Edit Password"] +@T["Edit Password"] @if (!user.EmailConfirmed && Site.As().UsersMustValidateEmail) {
    - +
    } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Activities/RegisterUserTask.cs b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Activities/RegisterUserTask.cs index 0d2cdc4c6ae..ea25acf8388 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Activities/RegisterUserTask.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Activities/RegisterUserTask.cs @@ -122,7 +122,7 @@ public override async Task ExecuteAsync(WorkflowExecuti var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var uri = _linkGenerator.GetUriByAction(_httpContextAccessor.HttpContext, "ConfirmEmail", - "Registration", new { area = "OrchardCore.Users", userId = user.Id, code }); + "Registration", new { area = "OrchardCore.Users", userId = user.UserId, code }); workflowContext.Properties["EmailConfirmationUrl"] = uri; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/ExternallUserHandler.cs b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/ExternallUserHandler.cs index 8a233cc28db..3a53b5de468 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/ExternallUserHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/ExternallUserHandler.cs @@ -26,7 +26,7 @@ public Task UpdateRoles(UpdateRolesContext context) { return _workflowManager.TriggerEventAsync(nameof(UserLoggedInEvent), input: new { context.User, context.ExternalClaims, context.UserRoles }, - correlationId: ((User)context.User).Id.ToString() + correlationId: ((User)context.User).UserId ); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/UserEventHandler.cs b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/UserEventHandler.cs index a5b802c8c86..da18afadaeb 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/UserEventHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Workflows/Handlers/UserEventHandler.cs @@ -44,7 +44,7 @@ private Task TriggerWorkflowEventAsync(string name, User user) { return _workflowManager.TriggerEventAsync(name, input: new { User = user }, - correlationId: user.Id.ToString() + correlationId: user.UserId ); } } diff --git a/src/OrchardCore.Themes/TheAgencyTheme/Recipes/agency.recipe.json b/src/OrchardCore.Themes/TheAgencyTheme/Recipes/agency.recipe.json index 4ee2149cc45..20a954228c0 100644 --- a/src/OrchardCore.Themes/TheAgencyTheme/Recipes/agency.recipe.json +++ b/src/OrchardCore.Themes/TheAgencyTheme/Recipes/agency.recipe.json @@ -947,7 +947,7 @@ "DisplayText": "Footer", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "LayerMetadata": { "Layer": "Always", @@ -1061,7 +1061,7 @@ "DisplayText": "Main Menu", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "MenuPart": {}, "TitlePart": { @@ -1122,7 +1122,7 @@ "DisplayText": "[js: parameters('SiteName')]", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "LandingPage": {}, "AutoroutePart": { diff --git a/src/OrchardCore.Themes/TheBlogTheme/Recipes/blog.recipe.json b/src/OrchardCore.Themes/TheBlogTheme/Recipes/blog.recipe.json index 50fcd924a4a..65ec2675fa2 100644 --- a/src/OrchardCore.Themes/TheBlogTheme/Recipes/blog.recipe.json +++ b/src/OrchardCore.Themes/TheBlogTheme/Recipes/blog.recipe.json @@ -968,6 +968,8 @@ { "ContentType": "Menu", "ContentItemId": "[js: variables('menuContentItemId')]", + "Owner": "[js: parameters('AdminUserId')]", + "Author": "[js: parameters('AdminUsername')]", "DisplayText": "Main Menu", "Latest": true, "Published": true, @@ -1001,6 +1003,8 @@ }, { "ContentItemId": "[js: variables('tagsContentItemId')]", + "Owner": "[js: parameters('AdminUserId')]", + "Author": "[js: parameters('AdminUsername')]", "ContentType": "Taxonomy", "DisplayText": "Tags", "Latest": true, @@ -1065,6 +1069,8 @@ }, { "ContentItemId": "[js: variables('categoriesContentItemId')]", + "Owner": "[js: parameters('AdminUserId')]", + "Author": "[js: parameters('AdminUsername')]", "ContentType": "Taxonomy", "DisplayText": "Categories", "Latest": true, @@ -1110,7 +1116,7 @@ "DisplayText": "Blog", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "TitlePart": { "Title": "Blog" @@ -1145,7 +1151,7 @@ "DisplayText": "Man must explore, and this is exploration at its greatest", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "TitlePart": { "Title": "Man must explore, and this is exploration at its greatest" @@ -1205,7 +1211,7 @@ "DisplayText": "About", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "AutoroutePart": { "Path": "about", @@ -1234,7 +1240,7 @@ "Displaytext": "Footer", "Latest": true, "Published": true, - "Owner": "[js: parameters('AdminUsername')]", + "Owner": "[js: parameters('AdminUserId')]", "Author": "[js: parameters('AdminUsername')]", "LayerMetadata": { "Layer": "Always", diff --git a/src/OrchardCore.Themes/TheBlogTheme/Views/Content-BlogPost.Summary.liquid b/src/OrchardCore.Themes/TheBlogTheme/Views/Content-BlogPost.Summary.liquid index ea7860c71fe..61d3545f8ec 100644 --- a/src/OrchardCore.Themes/TheBlogTheme/Views/Content-BlogPost.Summary.liquid +++ b/src/OrchardCore.Themes/TheBlogTheme/Views/Content-BlogPost.Summary.liquid @@ -10,7 +10,7 @@
    diff --git a/src/OrchardCore.Themes/TheComingSoonTheme/Recipes/comingsoon.recipe.json b/src/OrchardCore.Themes/TheComingSoonTheme/Recipes/comingsoon.recipe.json index a61672314a0..e1949c10fe5 100644 --- a/src/OrchardCore.Themes/TheComingSoonTheme/Recipes/comingsoon.recipe.json +++ b/src/OrchardCore.Themes/TheComingSoonTheme/Recipes/comingsoon.recipe.json @@ -97,7 +97,6 @@ "data": [ { "ContentItemId": "[js: variables('comingSoonContentItemId')]", - "ContentItemVersionId": "4kt5rnaa8vqaeyahg5cbmb62w2", "ContentType": "ComingSoon", "DisplayText": "Coming Soon", "Latest": true, @@ -105,8 +104,8 @@ "ModifiedUtc": "[js: variables('now')]", "PublishedUtc": "[js: variables('now')]", "CreatedUtc": "[js: variables('now')]", - "Owner": "admin", - "Author": "admin", + "Owner": "[js: parameters('AdminUserId')]", + "Author": "[js: parameters('AdminUsername')]", "ComingSoon": {}, "AutoroutePart": { "Path": null, diff --git a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItem.cs b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItem.cs index 56feb463588..3cbcc45988d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItem.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItem.cs @@ -60,8 +60,7 @@ public ContentItem() : base() public DateTime? CreatedUtc { get; set; } /// - /// The name of the user who first created this content item version - /// and owns content rights. + /// The user id of the user who first created this content item version. /// public string Owner { get; set; } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItemExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItemExtensions.cs index af21e911f20..3ae55ef71e1 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItemExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentItemExtensions.cs @@ -115,7 +115,6 @@ public static ContentItem Merge(this ContentItem contentItem, object properties, if (props.ContainsKey(nameof(contentItem.Owner))) { contentItem.Owner = props[nameof(contentItem.Owner)].ToString(); - contentItem.Author = props[nameof(contentItem.Owner)].ToString(); contentItem.Data.Remove(nameof(contentItem.Owner)); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement/Handlers/ContentsHandler.cs b/src/OrchardCore/OrchardCore.ContentManagement/Handlers/ContentsHandler.cs index 944b2ee5c0d..92d572c1e59 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement/Handlers/ContentsHandler.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement/Handlers/ContentsHandler.cs @@ -1,5 +1,7 @@ +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using OrchardCore.Modules; namespace OrchardCore.ContentManagement.Handlers @@ -28,7 +30,8 @@ public override Task CreatingAsync(CreateContentContext context) var httpContext = _httpContextAccessor.HttpContext; if (context.ContentItem.Owner == null && (httpContext?.User?.Identity?.IsAuthenticated ?? false)) { - context.ContentItem.Owner = context.ContentItem.Author = httpContext.User.Identity.Name; + context.ContentItem.Owner = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + context.ContentItem.Author = httpContext.User.Identity.Name; } return Task.CompletedTask; diff --git a/src/OrchardCore/OrchardCore.Infrastructure/Security/AuthorizationHandlers/SuperUserHandler.cs b/src/OrchardCore/OrchardCore.Infrastructure/Security/AuthorizationHandlers/SuperUserHandler.cs index 0ba5f7bfab5..f5ac478088d 100644 --- a/src/OrchardCore/OrchardCore.Infrastructure/Security/AuthorizationHandlers/SuperUserHandler.cs +++ b/src/OrchardCore/OrchardCore.Infrastructure/Security/AuthorizationHandlers/SuperUserHandler.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using OrchardCore.Settings; @@ -20,19 +21,25 @@ public SuperUserHandler(ISiteService siteService) public async Task HandleAsync(AuthorizationHandlerContext context) { - if (context?.User?.Identity?.Name == null) + var userId = context?.User?.FindFirstValue(ClaimTypes.NameIdentifier); + if (userId == null) { return; } - var superUser = (await _siteService.GetSiteSettingsAsync()).SuperUser; + var site = await _siteService.GetSiteSettingsAsync(); - if (String.Equals(context.User.Identity.Name, superUser, StringComparison.OrdinalIgnoreCase)) + if (String.Equals(userId, site.SuperUser, StringComparison.OrdinalIgnoreCase)) { - foreach (var requirement in context.Requirements.OfType()) - { - context.Succeed(requirement); - } + SucceedAllRequirements(context); + } + } + + private static void SucceedAllRequirements(AuthorizationHandlerContext context) + { + foreach (var requirement in context.Requirements.OfType()) + { + context.Succeed(requirement); } } } diff --git a/src/OrchardCore/OrchardCore.Setup.Abstractions/Events/ISetupEventHandler.cs b/src/OrchardCore/OrchardCore.Setup.Abstractions/Events/ISetupEventHandler.cs index d0fc28a5f3f..ceed1ffecfc 100644 --- a/src/OrchardCore/OrchardCore.Setup.Abstractions/Events/ISetupEventHandler.cs +++ b/src/OrchardCore/OrchardCore.Setup.Abstractions/Events/ISetupEventHandler.cs @@ -11,6 +11,7 @@ public interface ISetupEventHandler Task Setup( string siteName, string userName, + string userId, string email, string password, string dbProvider, diff --git a/src/OrchardCore/OrchardCore.Setup.Abstractions/ISetupUserIdGenerator.cs b/src/OrchardCore/OrchardCore.Setup.Abstractions/ISetupUserIdGenerator.cs new file mode 100644 index 00000000000..90d32c07a76 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Setup.Abstractions/ISetupUserIdGenerator.cs @@ -0,0 +1,7 @@ +namespace OrchardCore.Setup.Services +{ + public interface ISetupUserIdGenerator + { + string GenerateUniqueId(); + } +} diff --git a/src/OrchardCore/OrchardCore.Setup.Abstractions/SetupContext.cs b/src/OrchardCore/OrchardCore.Setup.Abstractions/SetupContext.cs index 9cd21f0190e..8ff129effa3 100644 --- a/src/OrchardCore/OrchardCore.Setup.Abstractions/SetupContext.cs +++ b/src/OrchardCore/OrchardCore.Setup.Abstractions/SetupContext.cs @@ -24,6 +24,12 @@ public class SetupContext /// public string AdminUsername { get; set; } + + /// + /// Gets or sets the administrator user id. + /// + public string AdminUserId { get; set; } + /// /// Gets or sets the administrator email. /// diff --git a/src/OrchardCore/OrchardCore.Setup.Core/OrchardCore.Setup.Core.csproj b/src/OrchardCore/OrchardCore.Setup.Core/OrchardCore.Setup.Core.csproj index 4a263575fd6..3dc2cd70070 100644 --- a/src/OrchardCore/OrchardCore.Setup.Core/OrchardCore.Setup.Core.csproj +++ b/src/OrchardCore/OrchardCore.Setup.Core/OrchardCore.Setup.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/src/OrchardCore/OrchardCore.Setup.Core/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.Setup.Core/ServiceCollectionExtensions.cs index 17cbfc943a3..5ea5c9867bc 100644 --- a/src/OrchardCore/OrchardCore.Setup.Core/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.Setup.Core/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Entities; using OrchardCore.Setup.Services; namespace OrchardCore.Setup @@ -15,6 +16,9 @@ public static IServiceCollection AddSetup(this IServiceCollection services) { services.AddScoped(); + services.AddIdGeneration(); + services.AddSingleton(); + return services; } } diff --git a/src/OrchardCore/OrchardCore.Setup.Core/SetupService.cs b/src/OrchardCore/OrchardCore.Setup.Core/SetupService.cs index 9fdb387937c..c7306b1e884 100644 --- a/src/OrchardCore/OrchardCore.Setup.Core/SetupService.cs +++ b/src/OrchardCore/OrchardCore.Setup.Core/SetupService.cs @@ -26,6 +26,7 @@ public class SetupService : ISetupService { private readonly IShellHost _shellHost; private readonly IShellContextFactory _shellContextFactory; + private readonly ISetupUserIdGenerator _setupUserIdGenerator; private readonly IEnumerable _recipeHarvesters; private readonly ILogger _logger; private readonly IStringLocalizer S; @@ -39,6 +40,7 @@ public class SetupService : ISetupService /// The . /// The . /// The . + /// The . /// A list of s. /// The . /// The . @@ -47,6 +49,7 @@ public SetupService( IShellHost shellHost, IHostEnvironment hostingEnvironment, IShellContextFactory shellContextFactory, + ISetupUserIdGenerator setupUserIdGenerator, IEnumerable recipeHarvesters, ILogger logger, IStringLocalizer stringLocalizer, @@ -56,6 +59,7 @@ IHostApplicationLifetime applicationLifetime _shellHost = shellHost; _applicationName = hostingEnvironment.ApplicationName; _shellContextFactory = shellContextFactory; + _setupUserIdGenerator = setupUserIdGenerator; _recipeHarvesters = recipeHarvesters; _logger = logger; S = stringLocalizer; @@ -119,6 +123,10 @@ private async Task SetupInternalAsync(SetupContext context) // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up. context.ShellSettings.State = TenantState.Initializing; + // Due to database collation we normalize the userId to lower invariant. + // During setup there are no users so we do not need to check unicity. + context.AdminUserId = _setupUserIdGenerator.GenerateUniqueId().ToLowerInvariant(); + var shellSettings = new ShellSettings(context.ShellSettings); if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"])) @@ -189,6 +197,7 @@ await scope { context.SiteName, context.AdminUsername, + context.AdminUserId, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, @@ -213,6 +222,7 @@ void reportError(string key, string message) await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup( context.SiteName, context.AdminUsername, + context.AdminUserId, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, diff --git a/src/OrchardCore/OrchardCore.Setup.Core/SetupUserIdGenerator.cs b/src/OrchardCore/OrchardCore.Setup.Core/SetupUserIdGenerator.cs new file mode 100644 index 00000000000..66e5bf56588 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Setup.Core/SetupUserIdGenerator.cs @@ -0,0 +1,19 @@ +using OrchardCore.Entities; + +namespace OrchardCore.Setup.Services +{ + public class SetupUserIdGenerator : ISetupUserIdGenerator + { + private readonly IIdGenerator _generator; + + public SetupUserIdGenerator(IIdGenerator generator) + { + _generator = generator; + } + + public string GenerateUniqueId() + { + return _generator.GenerateUniqueId(); + } + } +} diff --git a/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserIdGenerator.cs b/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserIdGenerator.cs new file mode 100644 index 00000000000..daaeb2d3b22 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserIdGenerator.cs @@ -0,0 +1,7 @@ +namespace OrchardCore.Users.Services +{ + public interface IUserIdGenerator + { + string GenerateUniqueId(IUser user); + } +} diff --git a/src/OrchardCore/OrchardCore.Users.Core/Indexes/UserIndex.cs b/src/OrchardCore/OrchardCore.Users.Core/Indexes/UserIndex.cs index 8103365bea6..c510ce323ee 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Indexes/UserIndex.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Indexes/UserIndex.cs @@ -5,7 +5,8 @@ namespace OrchardCore.Users.Indexes { public class UserIndex : MapIndex { - public string DocumentId { get; set; } + public int DocumentId { get; set; } + public string UserId { get; set; } public string NormalizedUserName { get; set; } public string NormalizedEmail { get; set; } public bool IsEnabled { get; set; } @@ -20,6 +21,7 @@ public override void Describe(DescribeContext context) { return new UserIndex { + UserId = user.UserId, NormalizedUserName = user.NormalizedUserName, NormalizedEmail = user.NormalizedEmail, IsEnabled = user.IsEnabled diff --git a/src/OrchardCore/OrchardCore.Users.Core/Models/User.cs b/src/OrchardCore/OrchardCore.Users.Core/Models/User.cs index 00b142d2859..2658010cf14 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Models/User.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Models/User.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Identity; using OrchardCore.Entities; @@ -7,6 +8,7 @@ namespace OrchardCore.Users.Models public class User : Entity, IUser { public int Id { get; set; } + public string UserId { get; set; } public string UserName { get; set; } public string NormalizedUserName { get; set; } public string Email { get; set; } diff --git a/src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs b/src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs index 38a2c037f4a..df1b5863bdf 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs @@ -30,12 +30,14 @@ public class UserStore : private readonly ISession _session; private readonly IRoleService _roleService; private readonly ILookupNormalizer _keyNormalizer; + private readonly IUserIdGenerator _userIdGenerator; private readonly ILogger _logger; private readonly IDataProtectionProvider _dataProtectionProvider; public UserStore(ISession session, IRoleService roleService, ILookupNormalizer keyNormalizer, + IUserIdGenerator userIdGenerator, ILogger logger, IEnumerable handlers, IDataProtectionProvider dataProtectionProvider) @@ -43,6 +45,7 @@ public UserStore(ISession session, _session = session; _roleService = roleService; _keyNormalizer = keyNormalizer; + _userIdGenerator = userIdGenerator; _logger = logger; _dataProtectionProvider = dataProtectionProvider; Handlers = handlers; @@ -67,10 +70,34 @@ public string NormalizeKey(string key) throw new ArgumentNullException(nameof(user)); } - _session.Save(user); + if (!(user is User newUser)) + { + throw new ArgumentException("Expected a User instance.", nameof(user)); + } + + if (String.IsNullOrEmpty(newUser.UserId)) + { + // Due to database collation we normalize the userId to lower invariant. + newUser.UserId = _userIdGenerator.GenerateUniqueId(user).ToLowerInvariant(); + } try { + var unique = false; + do + { + if (await _session.QueryIndex(x => x.UserId == newUser.UserId).CountAsync() == 0) + { + unique = true; + } + else + { + newUser.UserId = _userIdGenerator.GenerateUniqueId(user).ToLowerInvariant(); + } + } while (!unique); + + _session.Save(user); + await _session.CommitAsync(); var context = new UserContext(user); @@ -110,13 +137,7 @@ public string NormalizeKey(string key) public async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { - int id; - if (!int.TryParse(userId, out id)) - { - return null; - } - - return await _session.GetAsync(id); + return await _session.Query(u => u.UserId == userId).FirstOrDefaultAsync(); } public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)) @@ -141,7 +162,7 @@ public string NormalizeKey(string key) throw new ArgumentNullException(nameof(user)); } - return Task.FromResult(((User)user).Id.ToString(System.Globalization.CultureInfo.InvariantCulture)); + return Task.FromResult(((User)user).UserId); } public Task GetUserNameAsync(IUser user, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/docs/reference/modules/Liquid/README.md b/src/docs/reference/modules/Liquid/README.md index d86d66774e0..98847fc1208 100644 --- a/src/docs/reference/modules/Liquid/README.md +++ b/src/docs/reference/modules/Liquid/README.md @@ -338,7 +338,7 @@ Gives access to the current site settings, e.g `Site.SiteName`. | `MaxPageSize` | `100` | The maximum page size that can be set by a user. | | `PageSize` | `10` | The default page size of lists. | | `SiteName` | `My Site` | The friendly name of the site. | -| `SuperUser` | `admin` | The user name of the site's super user. | +| `SuperUser` | `4kxfgfrxqmdpnt5n508cqvpvca` | The user id of the site's super user. | | `TimeZoneId` | `America/Los_Angeles` | The site's time zone id as per the tz database, c.f., https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | | `UseCdn` | `false` | Enable/disable the use of a CDN. | | `ResourceDebugMode` | `Disabled` | Provides options for whether src or debug-src is used for loading scripts and stylesheets | diff --git a/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalFactoryTests.cs b/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalFactoryTests.cs index 14a2ec4b4bf..746261632b7 100644 --- a/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalFactoryTests.cs +++ b/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalFactoryTests.cs @@ -22,8 +22,8 @@ public async Task EnsurePrincipalHasExpectedClaims(bool emailVerified) { //Arrange var userManager = UsersMockHelper.MockUserManager(); - var user = new User { Id = new Random().Next(), UserName = "Foo", Email = "foo@foo.com" }; - userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.Id.ToString()); + var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "foo@foo.com" }; + userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); userManager.Setup(m => m.GetEmailAsync(user)).ReturnsAsync(user.Email); if (emailVerified)