Refactored to async where appropriate.

Added call to new abstraction in member authorize attribute.
This commit is contained in:
Andy Butland
2020-11-18 16:52:40 +01:00
parent 0fbe01cd22
commit d8ef341854
8 changed files with 134 additions and 37 deletions

View File

@@ -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);
/// <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);
// TODO: again, should this return the member?
void UpdateMemberProfile(ProfileModel model, out UpdateMemberProfileStatus status, out string errorMessage);
/// <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);
bool Login(string username, string password);
/// <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);
bool IsLoggedIn();
void LogOut();
/// <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,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

@@ -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

@@ -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<IActionResult> 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");

View File

@@ -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<IActionResult> 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;

View File

@@ -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<IActionResult> 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();

View File

@@ -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<IActionResult> 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();

View File

@@ -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;
}
/// <inheritdoc/>
public Task<RegisterMemberStatus> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true)
{
throw new System.NotImplementedException();
}
public void UpdateMemberProfile(ProfileModel model, out UpdateMemberProfileStatus status, out string errorMessage)
/// <inheritdoc/>
public Task<UpdateMemberProfileResult> UpdateMemberProfileAsync(ProfileModel model)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
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)
/// <inheritdoc/>
public Task<bool> LoginAsync(string username, string password)
{
throw new System.NotImplementedException();
}
public void LogOut()
/// <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();
}