Merge pull request #9412 from AndyButland/feature/membership-helper-facade

NetCore: Creates abstraction for membership helper and migrates controllers now dependent on it.
This commit is contained in:
Bjarke Berg
2020-11-23 13:15:26 +01:00
committed by GitHub
27 changed files with 422 additions and 289 deletions

View File

@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
public class LoginModel : PostRedirectModel
{
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Models
[Required]
[DataMember(Name = "password", IsRequired = true)]
[StringLength(maximumLength:256)]
[StringLength(maximumLength: 256)]
public string Password { get; set; }
}

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
/// <summary>
/// A base model containing a value to indicate to Umbraco where to redirect to after Posting if

View File

@@ -2,36 +2,15 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Web.Models;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
/// <summary>
/// A readonly member profile model
/// </summary>
[ModelBinder(typeof(ProfileModelBinder))]
public class ProfileModel : PostRedirectModel
{
public static ProfileModel CreateModel()
{
var model = new ProfileModel(false);
return model;
}
private ProfileModel(bool doLookup)
{
MemberProperties = new List<UmbracoProperty>();
if (doLookup && Current.UmbracoContext != null)
{
var helper = Current.MembershipHelper;
var model = helper.GetCurrentMemberProfileModel();
MemberProperties = model.MemberProperties;
}
}
[Required]
[RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
ErrorMessage = "Please enter a valid e-mail address")]
@@ -81,18 +60,6 @@ namespace Umbraco.Web.Models
/// <remarks>
/// Adding items to this list on the front-end will not add properties to the member in the database.
/// </remarks>
public List<UmbracoProperty> MemberProperties { get; set; }
/// <summary>
/// A custom model binder for MVC because the default ctor performs a lookup!
/// </summary>
internal class ProfileModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return ProfileModel.CreateModel();
}
}
public List<UmbracoProperty> MemberProperties { get; set; } = new List<UmbracoProperty>();
}
}

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Web.Models;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
[ModelBinder(typeof(RegisterModelBinder))]
public class RegisterModel : PostRedirectModel
{
/// <summary>
@@ -27,7 +24,6 @@ namespace Umbraco.Web.Models
CreatePersistentLoginCookie = true;
}
[Required]
[RegularExpression(@"[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?",
ErrorMessage = "Please enter a valid e-mail address")]
@@ -74,16 +70,5 @@ namespace Umbraco.Web.Models
/// Default is true to create a persistent cookie if LoginOnSuccess is true
/// </summary>
public bool CreatePersistentLoginCookie { get; set; }
/// <summary>
/// A custom model binder for MVC because the default ctor performs a lookup!
/// </summary>
internal class RegisterModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return RegisterModel.CreateModel();
}
}
}
}

View File

@@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;

View File

@@ -1,15 +1,12 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Security;
using Umbraco.Web;
using Umbraco.Web.Security;
namespace Umbraco.Core
namespace Umbraco.Core.Security
{
public class HybridBackofficeSecurityAccessor : HybridAccessorBase<IBackOfficeSecurity>, IBackOfficeSecurityAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="HybridUmbracoContextAccessor"/> class.
/// Initializes a new instance of the <see cref="HybridBackofficeSecurityAccessor"/> class.
/// </summary>
public HybridBackofficeSecurityAccessor(IRequestCache requestCache)
: base(requestCache)
@@ -19,7 +16,7 @@ namespace Umbraco.Core
protected override string ItemKey => "Umbraco.Web.HybridBackofficeSecurityAccessor";
/// <summary>
/// Gets or sets the <see cref="BackOfficeSecurity"/> object.
/// Gets or sets the <see cref="IBackOfficeSecurity"/> object.
/// </summary>
public IBackOfficeSecurity BackOfficeSecurity
{

View File

@@ -0,0 +1,28 @@
using Umbraco.Core.Cache;
using Umbraco.Web;
namespace Umbraco.Core.Security
{
public class HybridUmbracoWebsiteSecurityAccessor : HybridAccessorBase<IUmbracoWebsiteSecurity>, IUmbracoWebsiteSecurityAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="HybridUmbracoWebsiteSecurityAccessor"/> class.
/// </summary>
public HybridUmbracoWebsiteSecurityAccessor(IRequestCache requestCache)
: base(requestCache)
{ }
/// <inheritdoc />
protected override string ItemKey => "Umbraco.Web.HybridUmbracoWebsiteSecurityAccessor";
/// <summary>
/// Gets or sets the <see cref="IUmbracoWebsiteSecurity"/> object.
/// </summary>
public IUmbracoWebsiteSecurity WebsiteSecurity
{
get => Value;
set => Value = value;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Umbraco.Core.Models.Security;
namespace Umbraco.Core.Security
{
public interface IUmbracoWebsiteSecurity
{
/// <summary>
/// Registers a new member.
/// </summary>
/// <param name="model">Register member model.</param>
/// <param name="logMemberIn">Flag for whether to log the member in upon successful registration.</param>
/// <returns>Result of registration operation.</returns>
Task<RegisterMemberStatus> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true);
/// <summary>
/// Updates the currently logged in member's profile.
/// </summary>
/// <param name="model">Update member profile model.</param>
/// <returns>Result of update profile operation.</returns>
Task<UpdateMemberProfileResult> UpdateMemberProfileAsync(ProfileModel model);
/// <summary>
/// A helper method to perform the validation and logging in of a member.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <returns>Result of login operation.</returns>
Task<bool> LoginAsync(string username, string password);
/// <summary>
/// Check if a member is logged in
/// </summary>
/// <returns>True if logged in, false if not.</returns>
bool IsLoggedIn();
/// <summary>
/// Logs out the current member.
/// </summary>
Task LogOutAsync();
/// <summary>
/// Checks if the current member is authorized based on the parameters provided.
/// </summary>
/// <param name="allowTypes">Allowed types.</param>
/// <param name="allowGroups">Allowed groups.</param>
/// <param name="allowMembers">Allowed individual members.</param>
/// <returns>True or false if the currently logged in member is authorized</returns>
bool IsMemberAuthorized(
IEnumerable<string> allowTypes = null,
IEnumerable<string> allowGroups = null,
IEnumerable<int> allowMembers = null);
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Security
{
public interface IUmbracoWebsiteSecurityAccessor
{
IUmbracoWebsiteSecurity WebsiteSecurity { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace Umbraco.Core.Security
{
public enum RegisterMemberStatus
{
Success,
InvalidUserName,
InvalidPassword,
InvalidEmail,
DuplicateUserName,
DuplicateEmail,
Error,
}
}

View File

@@ -0,0 +1,24 @@
namespace Umbraco.Core.Security
{
public class UpdateMemberProfileResult
{
private UpdateMemberProfileResult()
{
}
public UpdateMemberProfileStatus Status { get; private set; }
public string ErrorMessage { get; private set; }
public static UpdateMemberProfileResult Success()
{
return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Success };
}
public static UpdateMemberProfileResult Error(string message)
{
return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Error, ErrorMessage = message };
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Security
{
public enum UpdateMemberProfileStatus
{
Success,
Error,
}
}

View File

@@ -3,17 +3,19 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
@@ -25,12 +27,10 @@ using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.Security;
using Umbraco.Web.Editors.Filters;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Constants = Umbraco.Core.Constants;
using Microsoft.AspNetCore.Identity;
using Umbraco.Web.Editors.Filters;
namespace Umbraco.Web.BackOffice.Controllers
{

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Extensions;
namespace Umbraco.Web.Common.Filters
@@ -12,6 +13,13 @@ namespace Umbraco.Web.Common.Filters
/// </summary>
public class UmbracoMemberAuthorizeFilter : IAuthorizationFilter
{
private readonly IUmbracoWebsiteSecurity _websiteSecurity;
public UmbracoMemberAuthorizeFilter(IUmbracoWebsiteSecurity websiteSecurity)
{
_websiteSecurity = websiteSecurity;
}
/// <summary>
/// Comma delimited list of allowed member types
/// </summary>
@@ -27,9 +35,7 @@ namespace Umbraco.Web.Common.Filters
/// </summary>
public string AllowMembers { get; private set; }
private UmbracoMemberAuthorizeFilter(
string allowType, string allowGroup, string allowMembers)
private UmbracoMemberAuthorizeFilter(string allowType, string allowGroup, string allowMembers)
{
AllowType = allowType;
AllowGroup = allowGroup;
@@ -48,11 +54,19 @@ namespace Umbraco.Web.Common.Filters
private bool IsAuthorized()
{
if (AllowMembers.IsNullOrWhiteSpace())
AllowMembers = "";
{
AllowMembers = string.Empty;
}
if (AllowGroup.IsNullOrWhiteSpace())
AllowGroup = "";
{
AllowGroup = string.Empty;
}
if (AllowType.IsNullOrWhiteSpace())
AllowType = "";
{
AllowType = string.Empty;
}
var members = new List<int>();
foreach (var s in AllowMembers.Split(','))
@@ -63,7 +77,7 @@ namespace Umbraco.Web.Common.Filters
}
}
return false;// TODO reintroduce when members are implemented: _memberHelper.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members);
return _websiteSecurity.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members);
}
}
}

View File

@@ -74,9 +74,12 @@ namespace Umbraco.Web.Common.Runtime
// register the umbraco context factory
composition.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
composition.Services.AddUnique<IBackOfficeSecurityFactory, BackOfficeSecurityFactory>();
composition.Services.AddUnique<IBackOfficeSecurityAccessor, HybridBackofficeSecurityAccessor>();
composition.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, HybridUmbracoWebsiteSecurityAccessor>();
//register the install components
composition.ComposeInstaller();

View File

@@ -21,7 +21,13 @@ namespace Umbraco.Web.Website.Controllers
// [MergeParentContextViewData]
public abstract class SurfaceController : PluginController
{
private readonly IPublishedUrlProvider _publishedUrlProvider;
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
PublishedUrlProvider = publishedUrlProvider;
}
protected IPublishedUrlProvider PublishedUrlProvider { get; }
/// <summary>
/// Gets the current page.
@@ -39,12 +45,6 @@ namespace Umbraco.Web.Website.Controllers
}
}
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_publishedUrlProvider = publishedUrlProvider;
}
/// <summary>
/// Redirects to the Umbraco page with the given id
/// </summary>
@@ -52,7 +52,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey)
{
return new RedirectToUmbracoPageResult(contentKey, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -63,7 +63,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey, QueryString queryString)
{
return new RedirectToUmbracoPageResult(contentKey, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -73,7 +73,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent)
{
return new RedirectToUmbracoPageResult(publishedContent, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -84,7 +84,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, QueryString queryString)
{
return new RedirectToUmbracoPageResult(publishedContent, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -93,7 +93,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage()
{
return new RedirectToUmbracoPageResult(CurrentPage, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -103,7 +103,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(QueryString queryString)
{
return new RedirectToUmbracoPageResult(CurrentPage, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>

View File

@@ -0,0 +1,60 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.Controllers
{
public class UmbLoginController : SurfaceController
{
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbLoginController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider,
IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public async Task<IActionResult> HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (await _websiteSecurityAccessor.WebsiteSecurity.LoginAsync(model.Username, model.Password) == false)
{
// Don't add a field level error, just model level.
ModelState.AddModelError("loginModel", "Invalid username or password");
return CurrentUmbracoPage();
}
TempData["LoginSuccess"] = true;
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
// Validate the redirect url.
// If it's not a local url we'll redirect to the root of the current site.
return Redirect(Url.IsLocalUrl(model.RedirectUrl)
? model.RedirectUrl
: CurrentPage.AncestorOrSelf(1).Url(PublishedUrlProvider));
}
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,58 +1,54 @@
using System.Web.Mvc;
using System.Web.Security;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Controllers
namespace Umbraco.Web.Website.Controllers
{
[MemberAuthorize]
[UmbracoMemberAuthorize]
public class UmbLoginStatusController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbLoginStatusController()
{
}
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbLoginStatusController(IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches,
IProfilingLogger profilingLogger, MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_membershipHelper = membershipHelper;
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
public async Task<IActionResult> HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (_membershipHelper.IsLoggedIn())
if (_websiteSecurityAccessor.WebsiteSecurity.IsLoggedIn())
{
FormsAuthentication.SignOut();
await _websiteSecurityAccessor.WebsiteSecurity.LogOutAsync();
}
TempData["LogoutSuccess"] = true;
//if there is a specified path to redirect to then use it
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.Controllers
{
[UmbracoMemberAuthorize]
public class UmbProfileController : SurfaceController
{
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbProfileController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public async Task<IActionResult> HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
var result = await _websiteSecurityAccessor.WebsiteSecurity.UpdateMemberProfileAsync(model);
switch (result.Status)
{
case UpdateMemberProfileStatus.Success:
break;
case UpdateMemberProfileStatus.Error:
// Don't add a field level error, just model level.
ModelState.AddModelError("profileModel", result.ErrorMessage);
return CurrentUmbracoPage();
default:
throw new ArgumentOutOfRangeException();
}
TempData["ProfileUpdateSuccess"] = true;
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,37 +1,34 @@
using System;
using System.Web.Mvc;
using System.Web.Security;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Controllers
namespace Umbraco.Web.Website.Controllers
{
public class UmbRegisterController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbRegisterController()
{
}
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbRegisterController(IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches,
IProfilingLogger profilingLogger, MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_membershipHelper = membershipHelper;
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
public async Task<IActionResult> HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
{
if (ModelState.IsValid == false)
{
@@ -39,60 +36,52 @@ namespace Umbraco.Web.Controllers
}
// U4-10762 Server error with "Register Member" snippet (Cannot save member with empty name)
// If name field is empty, add the email address instead
// If name field is empty, add the email address instead.
if (string.IsNullOrEmpty(model.Name) && string.IsNullOrEmpty(model.Email) == false)
{
model.Name = model.Email;
}
MembershipCreateStatus status;
var member = _membershipHelper.RegisterMember(model, out status, model.LoginOnSuccess);
var result = await _websiteSecurityAccessor.WebsiteSecurity.RegisterMemberAsync(model, model.LoginOnSuccess);
switch (status)
switch (result)
{
case MembershipCreateStatus.Success:
case RegisterMemberStatus.Success:
TempData["FormSuccess"] = true;
//if there is a specified path to redirect to then use it
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
case MembershipCreateStatus.InvalidUserName:
case RegisterMemberStatus.InvalidUserName:
ModelState.AddModelError((model.UsernameIsEmail || model.Username == null)
? "registerModel.Email"
: "registerModel.Username",
"Username is not valid");
break;
case MembershipCreateStatus.InvalidPassword:
case RegisterMemberStatus.InvalidPassword:
ModelState.AddModelError("registerModel.Password", "The password is not strong enough");
break;
case MembershipCreateStatus.InvalidQuestion:
case MembershipCreateStatus.InvalidAnswer:
// TODO: Support q/a http://issues.umbraco.org/issue/U4-3213
throw new NotImplementedException(status.ToString());
case MembershipCreateStatus.InvalidEmail:
case RegisterMemberStatus.InvalidEmail:
ModelState.AddModelError("registerModel.Email", "Email is invalid");
break;
case MembershipCreateStatus.DuplicateUserName:
case RegisterMemberStatus.DuplicateUserName:
ModelState.AddModelError((model.UsernameIsEmail || model.Username == null)
? "registerModel.Email"
: "registerModel.Username",
"A member with this username already exists.");
break;
case MembershipCreateStatus.DuplicateEmail:
case RegisterMemberStatus.DuplicateEmail:
ModelState.AddModelError("registerModel.Email", "A member with this e-mail address already exists");
break;
case MembershipCreateStatus.UserRejected:
case MembershipCreateStatus.InvalidProviderUserKey:
case MembershipCreateStatus.DuplicateProviderUserKey:
case MembershipCreateStatus.ProviderError:
//don't add a field level error, just model level
ModelState.AddModelError("registerModel", "An error occurred creating the member: " + status);
case RegisterMemberStatus.Error:
// Don't add a field level error, just model level.
ModelState.AddModelError("registerModel", $"An error occurred creating the member: {result}");
break;
default:
throw new ArgumentOutOfRangeException();
@@ -100,6 +89,5 @@ namespace Umbraco.Web.Controllers
return CurrentUmbracoPage();
}
}
}

View File

@@ -5,7 +5,7 @@ using Umbraco.Web.Website.ViewEngines;
namespace Umbraco.Extensions
{
public static class UmbracoWebstiteServiceCollectionExtensions
public static class UmbracoWebsiteServiceCollectionExtensions
{
public static void AddUmbracoWebsite(this IServiceCollection services)
{

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Security;
namespace Umbraco.Web.Website.Security
{
public class UmbracoWebsiteSecurity : IUmbracoWebsiteSecurity
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UmbracoWebsiteSecurity(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc/>
public Task<RegisterMemberStatus> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public Task<UpdateMemberProfileResult> UpdateMemberProfileAsync(ProfileModel model)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public bool IsLoggedIn()
{
var httpContext = _httpContextAccessor.HttpContext;
return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated;
}
/// <inheritdoc/>
public Task<bool> LoginAsync(string username, string password)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public async Task LogOutAsync()
{
await _httpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
/// <inheritdoc/>
public bool IsMemberAuthorized(IEnumerable<string> allowTypes = null, IEnumerable<string> allowGroups = null, IEnumerable<int> allowMembers = null)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

View File

@@ -1,63 +0,0 @@
using System.Web.Mvc;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
namespace Umbraco.Web.Controllers
{
public class UmbLoginController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbLoginController()
{
}
public UmbLoginController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_membershipHelper = membershipHelper;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (_membershipHelper.Login(model.Username, model.Password) == false)
{
//don't add a field level error, just model level
ModelState.AddModelError("loginModel", "Invalid username or password");
return CurrentUmbracoPage();
}
TempData["LoginSuccess"] = true;
//if there is a specified path to redirect to then use it
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
// validate the redirect url
// if it's not a local url we'll redirect to the root of the current site
return Redirect(Url.IsLocalUrl(model.RedirectUrl)
? model.RedirectUrl
: CurrentPage.AncestorOrSelf(1).Url());
}
//redirect to current page by default
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
using System.Web.Mvc;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Core.Security;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
namespace Umbraco.Web.Controllers
{
[MemberAuthorize]
public class UmbProfileController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbProfileController()
{ }
public UmbProfileController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_membershipHelper = membershipHelper;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
var updateAttempt = _membershipHelper.UpdateMemberProfile(model);
if (updateAttempt.Success == false)
{
//don't add a field level error, just model level
ModelState.AddModelError("profileModel", updateAttempt.Exception.Message);
return CurrentUmbracoPage();
}
TempData["ProfileUpdateSuccess"] = true;
//if there is a specified path to redirect to then use it
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,20 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Security;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Core.Cache;
using Umbraco.Web.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Editors;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security.Providers;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Web.Security
{
@@ -443,7 +443,7 @@ namespace Umbraco.Web.Security
return null;
}
var model = ProfileModel.CreateModel();
var model = new ProfileModel();
model.Name = member.Name;
model.MemberTypeAlias = member.ContentTypeAlias;
@@ -458,7 +458,6 @@ namespace Umbraco.Web.Security
model.LastActivityDate = membershipUser.LastActivityDate;
model.LastPasswordChangedDate = membershipUser.LastPasswordChangedDate;
var memberType = _memberTypeService.Get(member.ContentTypeId);
var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key).ToArray();

View File

@@ -142,6 +142,7 @@
<Compile Include="Macros\MemberUserKeyProvider.cs" />
<Compile Include="Mvc\IRenderController.cs" />
<Compile Include="Mvc\IRenderMvcController.cs" />
<Compile Include="Mvc\MemberAuthorizeAttribute.cs" />
<Compile Include="Mvc\RenderMvcController.cs" />
<Compile Include="Mvc\UmbracoViewPageOfTModel.cs" />
<Compile Include="Security\IdentityFactoryMiddleware.cs" />
@@ -220,8 +221,6 @@
<Compile Include="WebAssets\CDF\UmbracoClientDependencyLoader.cs" />
<Compile Include="UmbracoDefaultOwinStartup.cs" />
<Compile Include="Mvc\ProfilingView.cs" />
<Compile Include="Controllers\UmbProfileController.cs" />
<Compile Include="Controllers\UmbLoginStatusController.cs" />
<Compile Include="GridTemplateExtensions.cs" />
<Compile Include="Mvc\NotFoundHandler.cs" />
<Compile Include="Mvc\RedirectToUmbracoUrlResult.cs" />
@@ -236,13 +235,10 @@
<Compile Include="WebApi\AngularJsonOnlyConfigurationAttribute.cs" />
<Compile Include="WebApi\Filters\AngularAntiForgeryHelper.cs" />
<Compile Include="WebApi\Filters\ValidateAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="Controllers\UmbRegisterController.cs" />
<Compile Include="Models\ProfileModel.cs" />
<Compile Include="Models\LoginStatusModel.cs" />
<Compile Include="PublishedPropertyExtension.cs" />
<Compile Include="Mvc\MergeParentContextViewDataAttribute.cs" />
<Compile Include="Mvc\ViewDataDictionaryExtensions.cs" />
<Compile Include="Models\RegisterModel.cs" />
<Compile Include="Security\MembershipHelper.cs" />
<Compile Include="HttpCookieExtensions.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
@@ -253,10 +249,8 @@
<Compile Include="Macros\PartialViewMacroPage.cs" />
<Compile Include="Mvc\AreaRegistrationExtensions.cs" />
<Compile Include="Mvc\QueryStringFilterAttribute.cs" />
<Compile Include="Mvc\MemberAuthorizeAttribute.cs" />
<Compile Include="Mvc\ControllerFactoryExtensions.cs" />
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
<Compile Include="Controllers\UmbLoginController.cs" />
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="UrlHelperRenderExtensions.cs" />
<Compile Include="WebApi\IsBackOfficeAttribute.cs" />