diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index 7bfaabd03017..4650417eb4f9 100644 --- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -129,7 +129,7 @@ private void Map(IUser source, BackOfficeIdentityUser target) target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; DateTime? lockedOutUntil = source.LastLockoutDate?.AddMinutes(_securitySettings.UserDefaultLockoutTimeInMinutes); - target.LockoutEnd = source.IsLockedOut ? (lockedOutUntil ?? DateTime.MaxValue).ToUniversalTime() : null; + target.LockoutEnd = source.IsLockedOut ? lockedOutUntil ?? DateTime.MaxValue : null; } // Umbraco.Code.MapAll -Id -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -ConcurrencyStamp -NormalizedEmail -NormalizedUserName -Roles @@ -146,17 +146,46 @@ private void Map(IMember source, MemberIdentityUser target) target.PasswordConfig = source.PasswordConfiguration; target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; - DateTime? lockedOutUntil = source.LastLockoutDate?.AddMinutes(_securitySettings.MemberDefaultLockoutTimeInMinutes); - target.LockoutEnd = source.IsLockedOut ? (lockedOutUntil ?? DateTime.MaxValue).ToUniversalTime() : null; + target.LockoutEnd = GetLockoutEnd(source); + target.LastLockoutDateUtc = GetLastLockoutDateUtc(source); target.Comments = source.Comments; - target.LastLockoutDateUtc = source.LastLockoutDate == DateTime.MinValue - ? null - : source.LastLockoutDate?.ToUniversalTime(); - target.CreatedDateUtc = source.CreateDate.ToUniversalTime(); + target.CreatedDateUtc = EnsureUtcWithServerTime(source.CreateDate); target.Key = source.Key; target.MemberTypeAlias = source.ContentTypeAlias; target.TwoFactorEnabled = _twoFactorLoginService.IsTwoFactorEnabledAsync(source.Key).GetAwaiter().GetResult(); // NB: same comments re AutoMapper as per BackOfficeUser } + + private DateTimeOffset? GetLockoutEnd(IMember source) + { + if (source.IsLockedOut is false) + { + return null; + } + + DateTime? lockedOutUntil = source.LastLockoutDate?.AddMinutes(_securitySettings.MemberDefaultLockoutTimeInMinutes); + if (lockedOutUntil.HasValue is false) + { + return DateTime.MaxValue; + } + + return EnsureUtcWithServerTime(lockedOutUntil.Value); + } + + private static DateTime? GetLastLockoutDateUtc(IMember source) + { + if (source.LastLockoutDate is null || source.LastLockoutDate == DateTime.MinValue) + { + return null; + } + + return EnsureUtcWithServerTime(source.LastLockoutDate.Value); + } + + private static DateTime EnsureUtcWithServerTime(DateTime date) => + + // We have a server time value here, but the the Kind is UTC, so we can't use .ToUniversalTime() to convert to the UTC + // value that the LockoutEnd property expects. We need to create a DateTimeOffset with the correct offset. + DateTime.SpecifyKind(date, DateTimeKind.Local).ToUniversalTime(); }