如何为帖子请求设计多角色授权?

时间:2018-05-13 17:58:22

标签: c# asp.net-core-mvc authorization

在我正在构建的系统中,存在一个复杂且不断变化的基于资源的授权。目前共有六个角色。

系统正在处理所有成员可以在自己的个人资料中编辑基本信息的成员,另一个角色中的另一个人可以编辑其个人资料中的更多信息,等等。

我无法弄清楚哪个是使用端点/操作设计此方法的最佳方法,例如编辑成员操作。我最终做的,但不喜欢的是,每个角色都有一个控制器动作,视图和视图模型。这样做而不是拥有一个视图模型的主要原因是我觉得拥有一个人甚至无法编辑的所有属性都没有意义,这是过度发布的吗?

我对结果不太满意。 6个视图模型,6个视图,6个疯狂相似的控制器动作,6个验证器等。

我现在的想法是,我将只有一个编辑操作,然后在映射回域对象,视图和验证器类时有一堆if语句。叠加仍然存在,但使用if语句进行管理。我也是这样想的 - 如果系统成为API怎么办? api/members/1/edit/api/members/1/editAsTreasurer更有意义吗?

你怎么看?任何人都有另一种我没想过的解决方案吗?

一些代码部分,重复代码的示例,当然还有更多验证器类,视图和映射,不确定要包括多少:

[HttpPost]
public IActionResult EditAsSecretary(EditMemberAsSecretaryViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
        return View("EditAsSecretary", viewModel);
    }

    var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
    if (member == null) return NotFound();

    // Authorize
    if (!_authorizationProvider.Authorize(viewModel.MemberInfo.LogeId, AdminType.Sekreterare))
        return Forbid();

    var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);

    var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
                       !member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;

    _domainLogger.UpdateLog(viewModel, member, user);
    UpdateMember(viewModel, member, user.Id);
    _unitOfWork.Complete();

    if (finallyEmail) SendUserResetPasswordMail(member).Wait();

    TempData["Message"] = "Member has been updated.";

    return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}


[HttpPost]
public IActionResult EditAsManager(EditMemberAsManagerViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
        return View("EditAsManager", viewModel);
    }

    var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
    if (member == null) return NotFound();

    // Authorize
    if (!_authorizationProvider.Authorize(member.LogeId, AdminType.Manager))
        return Forbid();

    var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);

    var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
                       !member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;

    _domainLogger.UpdateLog(viewModel, member, user);
    UpdateMember(viewModel, member, user.Id);
    _unitOfWork.Complete();

    if (finallyEmail) SendUserResetPasswordMail(member).Wait();

    TempData["Message"] = "Member has been updated.";

    return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}


private void UpdateMember(EditMemberAsSecretaryViewModel viewModel, Member member, string userId)
{
    _mapper.Map(viewModel, member);
    MapGodfathers(viewModel.MemberInfo, member);
    AfterUpdateMember(member, userId);
    _userManager.UpdateNormalizedEmailAsync(member).Wait();
}

private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
    _mapper.Map(viewModel, member);
    MapGodfathers(viewModel.MemberInfo, member);
    AfterUpdateMember(member, userId);
    _userManager.UpdateNormalizedEmailAsync(member).Wait();
}

1 个答案:

答案 0 :(得分:1)

  

我现在的想法是,我将只有一个编辑操作,然后在映射回域对象,视图和验证器类时有一堆if语句。叠加仍然存在,但使用if语句进行管理

别。

除了使代码可读性更低外,它还会带来安全风险。每个动作都应该根据需要采取尽可能少的参数。没有任何东西可以让你有更多的动作,所以没有理由这样做。

您的代码存在一些问题,这有助于复制:

  1. 您似乎正在针对从用户收到的内容进行安全验证,而不是使用当前经过身份验证的用户。这是一个大问题,因为您信任来自用户的数据 而不是这样,创建一个自定义授权策略,用于检查使用业务逻辑的用户类型。然后可以将它们添加到内置容器中,您可以使用:

    [Authorize(Policy = "EnsureManager")]
    public IActionResult EditAsManager(...)
    

    这将允许您删除所有重复的代码并更接近SRP。

  2. 您的重复UpdateMember看起来与您的模型无关。在这样的情况下,拥有基本模型然后具有所需属性的子项会好得多:

    public abstract class EditMemberBaseViewModel
    {
        [Required]
        public Something Something { get; set; }
    }
    
    public class EditMemberAsSecretaryViewModel : EditMemberBaseViewModel
    {
        [Required]
        public AnotherThing AnotherThing { get; set; }
    }
    

    这将允许您拥有一个UpdateMember,因为逻辑基于EditMemberBaseViewModel而不是他们的孩子,只要您显示的是:

    private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
    {
        _mapper.Map(viewModel, member);
        MapGodfathers(viewModel.MemberInfo, member);
        AfterUpdateMember(member, userId);
        _userManager.UpdateNormalizedEmailAsync(member).Wait();
    }
    
  3. 作为最后也可能是最重要的一点,此代码存在问题:

    _userManager.UpdateNormalizedEmailAsync(member).Wait();
    

    那是真的坏。您正在使ASP.NET Core挂起整个线程,等待该操作完成。这是2000年代的同步代码  您需要学习在应用程序中为每个与IO相关的操作(如数据库调用)使用异步代码,否则性能将受到批次的影响。举个例子:

    public async Task<IActionResult> EditAsManager(...)
    {
        .....
        await UpdateMemberAsync(...);
    }
    
    public async Task UpdateMemberAsync(...)
    {
        await _userManager.UpdateNormalizedEmailAsync(member);
    }