-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from romandykyi/implement-permissions-checker
Implement Permissions Checker
- Loading branch information
Showing
10 changed files
with
656 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace AdvancedTodoList.Core.Models; | ||
|
||
/// <summary> | ||
/// An interface that represents an entity with an owner ID property. | ||
/// </summary> | ||
public interface IHasOwner | ||
{ | ||
/// <summary> | ||
/// Foreign key referencing the user who created this entity. | ||
/// </summary> | ||
public string? OwnerId { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using AdvancedTodoList.Core.Models; | ||
using AdvancedTodoList.Core.Models.TodoLists.Members; | ||
|
||
namespace AdvancedTodoList.Core.Services.Auth; | ||
|
||
/// <summary> | ||
/// Interface for a service that checks user's permissions. | ||
/// </summary> | ||
public interface IPermissionsChecker | ||
{ | ||
/// <summary> | ||
/// Asynchronously checks whether the user is a member of the to-do list with | ||
/// specified ID. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list.</param> | ||
/// <returns> | ||
/// <see langword="true" /> if user is a member of the list; otherwise <see langword="false" />. | ||
/// </returns> | ||
Task<bool> IsMemberOfListAsync(string userId, string todoListId); | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user is a member of the to-do list and | ||
/// has a permission defined by the funciton <paramref name="permission"/>. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true" /> if user is a member of the list and has required permission; | ||
/// otherwise <see langword="false" />. | ||
/// </returns> | ||
Task<bool> HasPermissionAsync(string userId, string todoListId, Func<RolePermissions, bool> permission); | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user can touch an entity. | ||
/// </summary> | ||
/// <remarks> | ||
/// This method firstly checks whether <paramref name="entity"/> implements <see cref="IHasOwner"/> | ||
/// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; | ||
/// otherwise the method checks if user has the permission defined by the function <paramref name="permission"/>. | ||
/// </remarks> | ||
/// <typeparam name="TEntity">Type of the entity.</typeparam> | ||
/// <typeparam name="TKey">Type of the unique identifier used by the entity.</typeparam> | ||
/// <param name="userId">ID of the user whose permissions are achecked.</param> | ||
/// <param name="todoListId">ID of the to-do list for which permission is checked.</param> | ||
/// <param name="entity">ID of the entity.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true"/> if user is either an owner of the entity and a member of a to-do list, | ||
/// or he/she/they has permission defined by <paramref name="permission"/>; otherwise <see langword="false" />. | ||
/// </returns> | ||
Task<bool> CanTouchEntityAsync<TEntity, TKey>(string userId, string todoListId, | ||
TEntity entity, Func<RolePermissions, bool> permission) | ||
where TEntity : class, IEntity<TKey> | ||
where TKey : IEquatable<TKey>; | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user has a permission to change the role | ||
/// defined by <paramref name="roleId"/>. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list for which permission is checked.</param> | ||
/// <param name="roleId">ID of the role.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true"/> if user has <paramref name="permission"/> and highest role priority than | ||
/// the role defined by <paramref name="roleId"/>; otherwise <see langword="false" />. | ||
/// </returns> | ||
Task<bool> HasPermissionOverRoleAsync(string userId, string todoListId, | ||
int roleId, Func<RolePermissions, bool> permission); | ||
} |
111 changes: 111 additions & 0 deletions
111
AdvancedTodoList.Infrastructure/Services/Auth/PermissionsChecker.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using AdvancedTodoList.Core.Dtos; | ||
using AdvancedTodoList.Core.Models; | ||
using AdvancedTodoList.Core.Models.TodoLists.Members; | ||
using AdvancedTodoList.Core.Repositories; | ||
using AdvancedTodoList.Core.Services.Auth; | ||
using AdvancedTodoList.Infrastructure.Specifications; | ||
|
||
namespace AdvancedTodoList.Infrastructure.Services.Auth; | ||
|
||
/// <summary> | ||
/// Service that checks user's permissions. | ||
/// </summary> | ||
public class PermissionsChecker( | ||
ITodoListMembersRepository membersRepository, | ||
IRepository<TodoListRole, int> rolesRepository) : IPermissionsChecker | ||
{ | ||
private readonly ITodoListMembersRepository _membersRepository = membersRepository; | ||
private readonly IRepository<TodoListRole, int> _rolesRepository = rolesRepository; | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user is a member of the to-do list with | ||
/// specified ID. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list.</param> | ||
/// <returns> | ||
/// <see langword="true" /> if user is a member of the list; otherwise <see langword="false" />. | ||
/// </returns> | ||
public async Task<bool> IsMemberOfListAsync(string userId, string todoListId) | ||
{ | ||
return await _membersRepository.FindAsync(todoListId, userId) != null; | ||
} | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user is a member of the to-do list and | ||
/// has a permission defined by the funciton <paramref name="permission"/>. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true" /> if user is a member of the list and has required permission; | ||
/// otherwise <see langword="false" />. | ||
/// </returns> | ||
public async Task<bool> HasPermissionAsync(string userId, string todoListId, Func<RolePermissions, bool> permission) | ||
{ | ||
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.Role == null) return false; | ||
|
||
return permission(member.Role.Permissions); | ||
} | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user can touch an entity. | ||
/// </summary> | ||
/// <remarks> | ||
/// This method firstly checks whether <paramref name="entity"/> implements <see cref="IHasOwner"/> | ||
/// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; | ||
/// otherwise the method checks if user has the permission defined by the function <paramref name="permission"/>. | ||
/// </remarks> | ||
/// <typeparam name="TEntity">Type of the entity.</typeparam> | ||
/// <typeparam name="TKey">Type of the unique identifier used by the entity.</typeparam> | ||
/// <param name="userId">ID of the user whose permissions are achecked.</param> | ||
/// <param name="todoListId">ID of the to-do list for which permission is checked.</param> | ||
/// <param name="entity">ID of the entity.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true"/> if user is either an owner of the entity and a member of a to-do list, | ||
/// or he/she/they has permission defined by <paramref name="permission"/>; otherwise <see langword="false" />. | ||
/// </returns> | ||
Task<bool> IPermissionsChecker.CanTouchEntityAsync<TEntity, TKey>(string userId, string todoListId, TEntity entity, Func<RolePermissions, bool> permission) | ||
{ | ||
// If user owns entity only check if he/she/they is member | ||
if (entity is IHasOwner ownedEntity && ownedEntity.OwnerId == userId) | ||
{ | ||
return IsMemberOfListAsync(userId, todoListId); | ||
} | ||
// Otherwise check if user has permission | ||
return HasPermissionAsync(userId, todoListId, permission); | ||
} | ||
|
||
/// <summary> | ||
/// Asynchronously checks whether the user is a member of the to-do list and | ||
/// has a permission defined by the funciton <paramref name="permission"/>. | ||
/// </summary> | ||
/// <param name="userId">ID of the user.</param> | ||
/// <param name="todoListId">ID of the to-do list.</param> | ||
/// <param name="permission">Function that should return <see langword="true"/> if user has required permission.</param> | ||
/// <returns> | ||
/// <see langword="true" /> if user is a member of the list and has required permission; | ||
/// otherwise <see langword="false" />. | ||
/// </returns> | ||
public async Task<bool> HasPermissionOverRoleAsync(string userId, string todoListId, int roleId, Func<RolePermissions, bool> permission) | ||
{ | ||
MemberPermissionsSpecification specification = new(todoListId, userId); | ||
var member = await _membersRepository.GetAggregateAsync<PermissionsAggregate>(specification); | ||
|
||
// 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) ?? | ||
throw new ArgumentException("Role with 'roleId' was not found", nameof(roleId)); | ||
|
||
// Check if user has a higher priority | ||
return member.Role.Priority < role.Priority; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
AdvancedTodoList.Infrastructure/Specifications/MemberPermissionsSpecification.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } = []; | ||
} |
Oops, something went wrong.