Fixes U4-10111 Changing email on a user doesn't show the username field

This commit is contained in:
Shannon
2017-07-19 19:22:43 +10:00
parent 32dc9bd275
commit 8df00d5525
14 changed files with 142 additions and 20 deletions

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Core
{
public const string AdminGroupAlias = "admin";
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
@@ -17,6 +17,7 @@ namespace Umbraco.Core
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
internal const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__";
internal const string ForceReAuthFlag = "umbraco-force-auth";
/// <summary>
/// The prefix used for external identity providers for their authentication type

View File

@@ -106,6 +106,16 @@ namespace Umbraco.Core.Models.Identity
private ObservableCollection<IIdentityUserLogin> _logins;
private Lazy<IEnumerable<IIdentityUserLogin>> _getLogins;
private List<IdentityUserRole<string>> _roles;
//TODO: We need to override this but need to wait until the rest of the PRs are merged in
///// <summary>
///// Override Roles because the value of these are the user's group aliases
///// </summary>
//public override ICollection<IdentityUserRole<string>> Roles
//{
// get { return _roles ?? (_roles = Groups.Select(x => x.Alias).ToArray()); }
//}
/// <summary>
/// Used to set a lazy call back to populate the user's Login list

View File

@@ -32,7 +32,8 @@ namespace Umbraco.Core.Models.Identity
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups))
//TODO: This should really be mapping Roles -> Roles
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray()))
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
.ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.AllStartContentIds))

View File

@@ -218,7 +218,7 @@ namespace Umbraco.Core.Security
public static bool RenewUmbracoAuthTicket(this HttpContextBase http)
{
if (http == null) throw new ArgumentNullException("http");
http.Items["umbraco-force-auth"] = true;
http.Items[Constants.Security.ForceReAuthFlag] = true;
return true;
}
@@ -230,7 +230,7 @@ namespace Umbraco.Core.Security
internal static bool RenewUmbracoAuthTicket(this HttpContext http)
{
if (http == null) throw new ArgumentNullException("http");
http.Items["umbraco-force-auth"] = true;
http.Items[Constants.Security.ForceReAuthFlag] = true;
return true;
}

View File

@@ -34,7 +34,11 @@ namespace Umbraco.Core.Security
RealName = user.Name,
AllowedApplications = user.AllowedSections,
Culture = user.Culture,
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
//TODO: In order for this to work, the user.Roles would need to be filled in!
//Currently that is not the case because the BackOfficeIdentityUser deals with Groups (which we need to update)
//For now, I'll fix this by using the user.Groups instead
//Roles = user.Roles.Select(x => x.RoleId).ToArray(),
Roles = user.Groups.Select(x => x.Alias).ToArray(),
StartContentNodes = user.StartContentIds,
StartMediaNodes = user.StartMediaIds,
SessionId = user.SecurityStamp

View File

@@ -32,7 +32,8 @@ namespace Umbraco.Core.Security
var identity = Thread.CurrentPrincipal.GetUmbracoIdentity();
if (identity != null)
{
var user = userService.GetByUsername(identity.Username);
var user = userService.GetUserById(identity.Id.TryConvertTo<int>().Result);
if (user == null) throw new InvalidOperationException("No user with username " + identity.Username + " found");
var userIsAdmin = user.IsAdmin();
if (userIsAdmin)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Globalization;
using System.Linq;
@@ -214,14 +215,8 @@ namespace Umbraco.Core.Services
Save(membershipUser);
}
/// <summary>
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
/// </summary>
/// <remarks>
/// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to.
/// </remarks>
/// <param name="user">The user to save the password for</param>
/// <param name="password">The password to save</param>
[Obsolete("ASP.NET Identity APIs like the BackOfficeUserManager should be used to manage passwords, this will not work with correct security practices because you would need the existing password")]
[EditorBrowsable(EditorBrowsableState.Never)]
public void SavePassword(IUser user, string password)
{
if (user == null) throw new ArgumentNullException("user");

View File

@@ -149,7 +149,8 @@ namespace Umbraco.Web.Editors
/// cookies which means that the auth cookie could be valid but the csrf cookies are no longer there, in that case we need to re-set the csrf cookies.
/// </remarks>
[WebApi.UmbracoAuthorize]
[SetAngularAntiForgeryTokens]
[SetAngularAntiForgeryTokens]
[VerifyIfUserTicketDataIsStale]
public UserDetail GetCurrentUser()
{
var user = UmbracoContext.Security.CurrentUser;
@@ -194,7 +195,8 @@ namespace Umbraco.Web.Editors
return result;
}
//TODO: This should be on the CurrentUserController?
[WebApi.UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()

View File

@@ -325,7 +325,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.ToArray()))
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray()))
.ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.AllStartContentIds))
.ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.AllStartMediaIds))
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username))

View File

@@ -96,8 +96,8 @@ namespace Umbraco.Web.Security.Identity
if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false;
if (//check the explicit flag
(checkForceAuthTokens && ctx.Get<bool?>("umbraco-force-auth") != null)
|| (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null)
(checkForceAuthTokens && ctx.Get<bool?>(Constants.Security.ForceReAuthFlag) != null)
|| (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null)
//check back office
|| request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)
//check installer

View File

@@ -71,7 +71,7 @@ namespace Umbraco.Web.Security.Identity
var httpCtx = Context.TryGetHttpContext();
//check for the special flag in either the owin or http context
var shouldRenew = Context.Get<bool?>("umbraco-force-auth") != null || (httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null);
var shouldRenew = Context.Get<bool?>(Constants.Security.ForceReAuthFlag) != null || (httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null);
if (shouldRenew)
{

View File

@@ -798,6 +798,7 @@
<Compile Include="WebApi\Filters\UmbracoTreeAuthorizeAttribute.cs" />
<Compile Include="WebApi\Filters\UmbracoUseHttps.cs" />
<Compile Include="WebApi\Filters\ValidateAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="WebApi\Filters\VerifyIfUserTicketDataIsStaleAttribute.cs" />
<Compile Include="WebApi\HttpControllerContextExtensions.cs" />
<Compile Include="WebApi\CustomDateTimeConvertor.cs" />
<Compile Include="Models\ContentEditing\ContentSortOrder.cs" />

View File

@@ -0,0 +1,106 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Web.Security;
using UserExtensions = Umbraco.Core.Models.UserExtensions;
namespace Umbraco.Web.WebApi.Filters
{
/// <summary>
/// This filter will check if the current Principal/Identity assigned to the request has stale data in it compared
/// to what is persisted for the current user and will update the current auth ticket with the correct data if required.
/// </summary>
public sealed class VerifyIfUserTicketDataIsStaleAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
await CheckStaleData(actionContext);
}
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
await CheckStaleData(actionExecutedContext.ActionContext);
}
private async Task CheckStaleData(HttpActionContext actionContext)
{
var identity = actionContext.RequestContext.Principal.Identity as UmbracoBackOfficeIdentity;
if (identity == null) return;
var userId = identity.Id.TryConvertTo<int>();
if (userId == false) return;
var user = ApplicationContext.Current.Services.UserService.GetUserById(userId.Result);
if (user == null) return;
if (user.Username != identity.Username)
{
await ReSync(user, identity, actionContext);
return;
}
var culture = UserExtensions.GetUserCulture(user, ApplicationContext.Current.Services.TextService).ToString();
if (culture != identity.Culture)
{
//TODO: Might have to log out if this happens or somehow refresh the back office UI with a special header maybe?
await ReSync(user, identity, actionContext);
return;
}
if (user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false)
{
await ReSync(user, identity, actionContext);
return;
}
if (user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false)
{
await ReSync(user, identity, actionContext);
return;
}
//TODO: This will need to be changed when http://issues.umbraco.org/issue/U4-10173 is merged
var startContentIds = user.AllStartContentIds;
if (startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false)
{
await ReSync(user, identity, actionContext);
return;
}
//TODO: This will need to be changed when http://issues.umbraco.org/issue/U4-10173 is merged
var startMediaIds = user.AllStartMediaIds;
if (startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false)
{
await ReSync(user, identity, actionContext);
return;
}
}
/// <summary>
/// This will update the current request IPrincipal to be correct and re-create the auth ticket
/// </summary>
/// <param name="user"></param>
/// <param name="identityUser"></param>
/// <param name="actionContext"></param>
/// <returns></returns>
private async Task ReSync(IUser user, UmbracoBackOfficeIdentity identityUser, HttpActionContext actionContext)
{
var owinCtx = actionContext.Request.TryGetOwinContext().Result;
var signInManager = owinCtx.GetBackOfficeSignInManager();
//ensure the remainder of the request has the correct principal set
actionContext.Request.SetPrincipalForRequest(user);
var backOfficeIdentityUser = Mapper.Map<BackOfficeIdentityUser>(user);
await signInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true, rememberBrowser: false);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace Umbraco.Web.WebApi
[UmbracoAuthorize]
[DisableBrowserCache]
[UmbracoWebApiRequireHttps]
[VerifyIfUserTicketDataIsStale]
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
{