Skip to content

Commit

Permalink
Refactor PermissionsChecker
Browse files Browse the repository at this point in the history
Used `IRepository.GetAggregateAsync` method with a newly created `MemberPermissionsSpecification` to reduce a number of db queries.
  • Loading branch information
romandykyi committed Mar 6, 2024
1 parent 51b2a61 commit be2cce7
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 21 deletions.
13 changes: 13 additions & 0 deletions AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using AdvancedTodoList.Core.Models.TodoLists.Members;

namespace AdvancedTodoList.Core.Dtos;

/// <summary>
/// Represents an aggregate of a to-do list member with a role.
/// </summary>
public record PermissionsAggregate(RoleEssentials? Role);

/// <summary>
/// Represents role's permissions and priority.
/// </summary>
public record RoleEssentials(int Priority, RolePermissions Permissions);
35 changes: 14 additions & 21 deletions AdvancedTodoList.Infrastructure/Services/Auth/PermissionsChecker.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
using AdvancedTodoList.Core.Models;
using AdvancedTodoList.Core.Dtos;
using AdvancedTodoList.Core.Models;
using AdvancedTodoList.Core.Models.TodoLists.Members;
using AdvancedTodoList.Core.Repositories;
using AdvancedTodoList.Core.Services.Auth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AdvancedTodoList.Infrastructure.Specifications;

namespace AdvancedTodoList.Infrastructure.Services.Auth;

Expand Down Expand Up @@ -47,15 +44,12 @@ public async Task<bool> IsMemberOfListAsync(string userId, string todoListId)
/// </returns>
public async Task<bool> HasPermissionAsync(string userId, string todoListId, Func<RolePermissions, bool> permission)
{
var member = await _membersRepository.FindAsync(todoListId, userId);
MemberPermissionsSpecification specification = new(todoListId, userId);
var member = await _membersRepository.GetAggregateAsync<PermissionsAggregate>(specification);
// User is not a member or has no role - return false
if (member == null || member.RoleId == null) return false;
if (member == null || member.Role == null) return false;

// Check user's permissions
var role = await _rolesRepository.GetByIdAsync(member.RoleId.Value);
if (role == null) return false;

return permission(role.Permissions);
return permission(member.Role.Permissions);
}

/// <summary>
Expand Down Expand Up @@ -100,19 +94,18 @@ Task<bool> IPermissionsChecker.CanTouchEntityAsync<TEntity, TKey>(string userId,
/// </returns>
public async Task<bool> HasPermissionOverRoleAsync(string userId, string todoListId, int roleId, Func<RolePermissions, bool> permission)
{
var member = await _membersRepository.FindAsync(todoListId, userId);
// User is not a member or has no role - return false
if (member == null || member.RoleId == null) return false;
MemberPermissionsSpecification specification = new(todoListId, userId);
var member = await _membersRepository.GetAggregateAsync<PermissionsAggregate>(specification);

// Check if user has a permission
var userRole = await _rolesRepository.GetByIdAsync(member.RoleId.Value);
if (userRole == null || !permission(userRole.Permissions)) return false;
// User is not a member, has no role or permission - return false
if (member == null || member.Role == null || !permission(member.Role.Permissions))
return false;

// Get other role
var role = await _rolesRepository.GetByIdAsync(roleId) ??
var role = await _rolesRepository.GetByIdAsync(roleId) ??
throw new ArgumentException("Role with 'roleId' was not found", nameof(roleId));

// Check if user has a higher priority
return userRole.Priority < role.Priority;
return member.Role.Priority < role.Priority;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using AdvancedTodoList.Core.Models.TodoLists.Members;
using AdvancedTodoList.Core.Specifications;
using AdvancedTodoList.Infrastructure.Services.Auth;
using System.Linq.Expressions;

namespace AdvancedTodoList.Infrastructure.Specifications;

/// <summary>
/// Represents a specification for obtaining an aggregate containg the to-do list member
/// and his/her/their role, used in <see cref="PermissionsChecker" />.
/// </summary>
/// <param name="todoListId">ID of the to-do list.</param>
/// <param name="userId">ID of the user.</param>
public class MemberPermissionsSpecification(string todoListId, string userId) : ISpecification<TodoListMember>
{
/// <summary>
/// Gets the to-do list ID.
/// </summary>
public string TodoListId { get; } = todoListId;
/// <summary>
/// Gets the user ID.
/// </summary>
public string UserId { get; } = userId;

/// <summary>
/// Gets the criteria expression that defines the filtering conditions.
/// </summary>
public Expression<Func<TodoListMember, bool>> Criteria =>
x => x.TodoListId == TodoListId && x.UserId == UserId;

/// <summary>
/// Gets the list of include expressions specifying a to-do list role to be included in the query results.
/// </summary>
public List<Expression<Func<TodoListMember, object?>>> Includes =>
[
x => x.Role
];

/// <summary>
/// Gets the list of include strings specifying related entities to be included in the query results.
/// </summary>
public List<string> IncludeStrings { get; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using AdvancedTodoList.Core.Models.TodoLists.Members;
using AdvancedTodoList.Infrastructure.Specifications;

namespace AdvancedTodoList.UnitTests.Specifications;

public class MemberPermissionsSpecificationTests
{
[Test]
public void Criteria_ValidTodoListIdAndUserId_ReturnsTrue()
{
// Arrange
const string todoListId = "TodoId";
const string userId = "UserId";
TodoListMember member = new()
{
TodoListId = todoListId,
UserId = userId
};
MemberPermissionsSpecification specification = new(todoListId, userId);
var criteria = specification.Criteria.Compile();

// Act
bool result = criteria(member);

// Arrange
Assert.That(result, Is.True);
}

[Test]
[TestCase("TodoListId", "invalid")]
[TestCase("invalid", "UserId")]
[TestCase("invalid", "invalid")]
public void Criteria_InvalidTodoListIdAndUserIdPairs_ReturnsFalse(string todoListId, string userId)
{
// Arrange
TodoListMember member = new()
{
TodoListId = "TodoListId",
UserId = "UserId"
};
MemberPermissionsSpecification specification = new(todoListId, userId);
var criteria = specification.Criteria.Compile();

// Act
bool result = criteria(member);

// Arrange
Assert.That(result, Is.False);
}
}

0 comments on commit be2cce7

Please sign in to comment.