diff --git a/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs b/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs index f14adae174..2f35a83dc2 100644 --- a/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs +++ b/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs @@ -1,20 +1,51 @@ -using Umbraco.Core.Models.Security; +using System.Collections.Generic; +using System.Threading.Tasks; +using Umbraco.Core.Models.Security; namespace Umbraco.Core.Security { public interface IUmbracoWebsiteSecurity { - // TODO: this should return the member, but in what form? MembershipUser is in place on MembershipHelper, but - // isn't appropriate for when we're using ASP.NET Identity. - void RegisterMember(RegisterModel model, out RegisterMemberStatus status, bool logMemberIn = true); + /// + /// Registers a new member. + /// + /// Register member model. + /// Flag for whether to log the member in upon successful registration. + /// Result of registration operation. + Task RegisterMemberAsync(RegisterModel model, bool logMemberIn = true); - // TODO: again, should this return the member? - void UpdateMemberProfile(ProfileModel model, out UpdateMemberProfileStatus status, out string errorMessage); + /// + /// Updates the currently logged in member's profile. + /// + /// Update member profile model. + /// Result of update profile operation. + Task UpdateMemberProfileAsync(ProfileModel model); - bool Login(string username, string password); + /// + /// A helper method to perform the validation and logging in of a member. + /// + /// The username. + /// The password. + /// Result of login operation. + Task LoginAsync(string username, string password); bool IsLoggedIn(); - void LogOut(); + /// + /// Logs out the current member. + /// + Task LogOutAsync(); + + /// + /// Checks if the current member is authorized based on the parameters provided. + /// + /// Allowed types. + /// Allowed groups. + /// Allowed individual members. + /// True or false if the currently logged in member is authorized + bool IsMemberAuthorized( + IEnumerable allowTypes = null, + IEnumerable allowGroups = null, + IEnumerable allowMembers = null); } } diff --git a/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs new file mode 100644 index 0000000000..1d435378a6 --- /dev/null +++ b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs @@ -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 }; + } + } + +} diff --git a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs index 27c2922637..4019f462eb 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs @@ -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 /// public class UmbracoMemberAuthorizeFilter : IAuthorizationFilter { + private readonly IUmbracoWebsiteSecurity _websiteSecurity; + + public UmbracoMemberAuthorizeFilter(IUmbracoWebsiteSecurity websiteSecurity) + { + _websiteSecurity = websiteSecurity; + } + /// /// Comma delimited list of allowed member types /// @@ -27,9 +35,7 @@ namespace Umbraco.Web.Common.Filters /// 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(); 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); } } } diff --git a/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs index a986cef77e..51938f00f5 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; @@ -26,14 +27,14 @@ namespace Umbraco.Web.Website.Controllers [HttpPost] [ValidateAntiForgeryToken] [ValidateUmbracoFormRouteString] - public IActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) + public async Task HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) { return CurrentUmbracoPage(); } - if (_websiteSecurity.Login(model.Username, model.Password) == false) + if (await _websiteSecurity.LoginAsync(model.Username, model.Password) == false) { // Don't add a field level error, just model level. ModelState.AddModelError("loginModel", "Invalid username or password"); diff --git a/src/Umbraco.Web.Website/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web.Website/Controllers/UmbLoginStatusController.cs index 1c026c7b46..3da1f34282 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbLoginStatusController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; @@ -11,7 +12,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Website.Controllers { - // TOOO: reinstate [MemberAuthorize] + [UmbracoMemberAuthorize] public class UmbLoginStatusController : SurfaceController { private readonly IUmbracoWebsiteSecurity _websiteSecurity; @@ -27,7 +28,7 @@ namespace Umbraco.Web.Website.Controllers [HttpPost] [ValidateAntiForgeryToken] [ValidateUmbracoFormRouteString] - public IActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) + public async Task HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) { if (ModelState.IsValid == false) { @@ -36,7 +37,7 @@ namespace Umbraco.Web.Website.Controllers if (_websiteSecurity.IsLoggedIn()) { - _websiteSecurity.LogOut(); + await _websiteSecurity.LogOutAsync(); } TempData["LogoutSuccess"] = true; diff --git a/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs b/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs index e91266f396..69bf77981e 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Cache; @@ -12,7 +13,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Website.Controllers { - // TOOO: reinstate [MemberAuthorize] + [UmbracoMemberAuthorize] public class UmbProfileController : SurfaceController { private readonly IUmbracoWebsiteSecurity _websiteSecurity; @@ -28,21 +29,21 @@ namespace Umbraco.Web.Website.Controllers [HttpPost] [ValidateAntiForgeryToken] [ValidateUmbracoFormRouteString] - public IActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) + public async Task HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { if (ModelState.IsValid == false) { return CurrentUmbracoPage(); } - _websiteSecurity.UpdateMemberProfile(model, out var status, out var errorMessage); - switch(status) + var result = await _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", errorMessage); + ModelState.AddModelError("profileModel", result.ErrorMessage); return CurrentUmbracoPage(); default: throw new ArgumentOutOfRangeException(); diff --git a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs index e20cb88715..8af2157022 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Cache; @@ -27,7 +28,7 @@ namespace Umbraco.Web.Website.Controllers [HttpPost] [ValidateAntiForgeryToken] [ValidateUmbracoFormRouteString] - public IActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) + public async Task HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) { if (ModelState.IsValid == false) { @@ -41,9 +42,9 @@ namespace Umbraco.Web.Website.Controllers model.Name = model.Email; } - _websiteSecurity.RegisterMember(model, out var status, model.LoginOnSuccess); + var result = await _websiteSecurity.RegisterMemberAsync(model, model.LoginOnSuccess); - switch (status) + switch (result) { case RegisterMemberStatus.Success: @@ -80,7 +81,7 @@ namespace Umbraco.Web.Website.Controllers break; case RegisterMemberStatus.Error: // Don't add a field level error, just model level. - ModelState.AddModelError("registerModel", "An error occurred creating the member: " + status); + ModelState.AddModelError("registerModel", $"An error occurred creating the member: {result}"); break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs b/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs index cd1d667dc9..90e80537ec 100644 --- a/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs +++ b/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs @@ -1,31 +1,55 @@ -using Umbraco.Core.Models.Security; +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 { - public void RegisterMember(RegisterModel model, out RegisterMemberStatus status, bool logMemberIn = true) + private readonly IHttpContextAccessor _httpContextAccessor; + + public UmbracoWebsiteSecurity(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + public Task RegisterMemberAsync(RegisterModel model, bool logMemberIn = true) { throw new System.NotImplementedException(); } - public void UpdateMemberProfile(ProfileModel model, out UpdateMemberProfileStatus status, out string errorMessage) + /// + public Task UpdateMemberProfileAsync(ProfileModel model) { throw new System.NotImplementedException(); } + /// public bool IsLoggedIn() { - throw new System.NotImplementedException(); + var httpContext = _httpContextAccessor.HttpContext; + return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated; } - public bool Login(string username, string password) + /// + public Task LoginAsync(string username, string password) { throw new System.NotImplementedException(); } - public void LogOut() + /// + public async Task LogOutAsync() + { + await _httpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + } + + /// + public bool IsMemberAuthorized(IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { throw new System.NotImplementedException(); }