Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend logic: user with many roles #93

Merged
merged 10 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Acl.Net.Core.Database/Acl.Net.Core.Database.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion src/Acl.Net.Core.Database/AclDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
/// <typeparam name="TUser">The type of the user entity.</typeparam>
/// <typeparam name="TRole">The type of the role entity.</typeparam>
/// <typeparam name="TResource">The type of the resource entity.</typeparam>
public abstract class AclDbContext<TKey, TUser, TRole, TResource> : DbContext

Check warning on line 59 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Reduce the number of generic parameters in the 'AclDbContext' class to no more than the 2 authorized. (https://rules.sonarsource.com/csharp/RSPEC-2436)

Check warning on line 59 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Reduce the number of generic parameters in the 'AclDbContext' class to no more than the 2 authorized. (https://rules.sonarsource.com/csharp/RSPEC-2436)
where TKey : IEquatable<TKey>
where TUser : User<TKey>
where TRole : Role<TKey>
Expand Down Expand Up @@ -110,6 +110,7 @@
modelBuilder.Entity<TUser>(entity =>
{
entity.HasKey(u => u.Id);
entity.HasIndex(u => new { u.Name, u.RoleId });
entity.Property(u => u.Name).IsRequired();

entity.HasOne<TRole>().WithMany().HasForeignKey(ut => ut.RoleId).IsRequired();
Expand All @@ -122,7 +123,7 @@

entity.HasMany<TResource>().WithOne().HasForeignKey(res => res.RoleId).IsRequired();

entity.HasData(_seeder.SeedAdminRole(), _seeder.SeedUserRole());
entity.HasData([_seeder.SeedAdminRole()]);

Check warning on line 126 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Remove this array creation and simply pass the elements. (https://rules.sonarsource.com/csharp/RSPEC-3878)

Check warning on line 126 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Review this call, which partially matches an overload without 'params'. The partial match is 'Microsoft.EntityFrameworkCore.Metadata.Builders.DataBuilder<TRole> Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<TRole>.HasData(IEnumerable<TRole> data)'. (https://rules.sonarsource.com/csharp/RSPEC-3220)

Check warning on line 126 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Remove this array creation and simply pass the elements. (https://rules.sonarsource.com/csharp/RSPEC-3878)

Check warning on line 126 in src/Acl.Net.Core.Database/AclDbContext.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Review this call, which partially matches an overload without 'params'. The partial match is 'Microsoft.EntityFrameworkCore.Metadata.Builders.DataBuilder<TRole> Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<TRole>.HasData(IEnumerable<TRole> data)'. (https://rules.sonarsource.com/csharp/RSPEC-3220)
});

modelBuilder.Entity<TResource>(entity =>
Expand Down
8 changes: 1 addition & 7 deletions src/Acl.Net.Core.Database/IInitialDataSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Acl.Net.Core.Database;

/// <summary>
/// Provides methods for seeding the initial administrative and user roles within the system.
/// Provides methods for seeding the initial roles within the system.
/// </summary>
/// <typeparam name="TKey">The type of the key used to identify the role.</typeparam>
/// <typeparam name="TRole">The type of the role being seeded.</typeparam>
Expand All @@ -16,10 +16,4 @@ public interface IInitialDataSeeder<TKey, out TRole>
/// </summary>
/// <returns>A <see cref="TRole"/> representing the administrative role.</returns>
public TRole SeedAdminRole();

/// <summary>
/// Seeds the user role within the system.
/// </summary>
/// <returns>A <see cref="TRole"/> representing the user role.</returns>
public TRole SeedUserRole();
}
10 changes: 2 additions & 8 deletions src/Acl.Net.Core.Database/RoleDataSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ public class RoleDataSeeder : IInitialDataSeeder<int, Role<int>>
/// <inheritdoc cref="IInitialDataSeeder{TKey,TRole}.SeedAdminRole()" />
public Role<int> SeedAdminRole()
{
return new Role<int> { Id = 1, Name = "AdminRole" };
return new Role<int> { Id = 1, Name = "Admin" };
}

/// <inheritdoc cref="IInitialDataSeeder{TKey,TRole}.SeedUserRole()" />
public Role<int> SeedUserRole()
{
return new Role<int> { Id = 2, Name = "UserRole" };
}
}
}
159 changes: 34 additions & 125 deletions src/Acl.Net.Core.Managers/AclManager.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
using Acl.Net.Core.Database;
using Acl.Net.Core.Database.Entities;
using Acl.Net.Core.Managers.Exceptions;

namespace Acl.Net.Core.Managers;

/// <summary>
/// Manages access control lists (ACLs) using integer keys.
/// This class provides a simplified interface for managing ACLs with integer keys, by extending the more generic <see cref="AclManager{TKey}"/> with TKey.
/// Manages access control lists (ACLs) using integer keys.<br/>
/// This class provides a simplified interface for managing ACLs with integer keys, by extending the more generic <see cref="AclManager{TKey}"/>.
/// </summary>
public class AclManager : AclManager<int>, IAclManager
{
/// <summary>
/// Initializes a new instance of the <see cref="AclManager"/> class
/// with the default <see cref="RoleDataSeeder"/>
/// and with default <see cref="UserManager"/> and <see cref="ResourceManager"/>.
/// Initializes a new instance of the <see cref="AclManager"/> class and with the default <see cref="RoleDataSeeder"/>.
/// </summary>
/// <param name="context">An implementation, or default <see cref="AclDbContext"/>.</param>
public AclManager(
Expand All @@ -22,9 +21,8 @@
{ }

/// <summary>
/// Initializes a new instance of the <see cref="AclManager"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>
/// and with default <see cref="UserManager"/> and <see cref="ResourceManager"/>.
/// Initializes a new instance of the <see cref="AclManager{TKey}"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/> and <see cref="AclDbContext{TKey}"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey,TRole}"/> used to seed initial role data.</param>
/// <param name="context">An implementation, or default <see cref="AclDbContext"/>.</param>
Expand All @@ -34,39 +32,10 @@
)
: base(initialDataSeeder, context)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="AclManager"/> class
/// with the default <see cref="RoleDataSeeder"/>
/// and with the provided <see cref="IUserManager"/>, and <see cref="IResourceManager"/>.
/// </summary>
/// <param name="userManager">An implementation of <see cref="IUserManager"/>.</param>
/// <param name="resourceManager">An implementation of <see cref="IResourceManager"/>.</param>
public AclManager(
IUserManager userManager,
IResourceManager resourceManager
)
: base(new RoleDataSeeder(), userManager, resourceManager)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="AclManager"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>, <see cref="IUserManager"/>, and <see cref="IResourceManager"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey,TRole}"/> used to seed initial role data.</param>
/// <param name="userManager">An implementation of <see cref="IUserManager"/>.</param>
/// <param name="resourceManager">An implementation of <see cref="IResourceManager"/>.</param>
public AclManager(
IInitialDataSeeder<int, Role<int>> initialDataSeeder,
IUserManager userManager,
IResourceManager resourceManager
)
: base(initialDataSeeder, userManager, resourceManager)
{ }
}

/// <summary>
/// Manages access control lists (ACLs) using keys of type <see cref="TKey"/>.
/// Manages access control lists (ACLs) using keys of type <see cref="TKey"/>.<br/>
/// This class provides the base functionality for managing ACLs with specified key types.
/// </summary>
/// <typeparam name="TKey">The type of the key, which must implement <see cref="IEquatable{TKey}"/>.</typeparam>
Expand All @@ -75,8 +44,7 @@
{
/// <summary>
/// Initializes a new instance of the <see cref="AclManager{TKey}"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>
/// and with default <see cref="UserManager{TKey}"/> and <see cref="ResourceManager{TKey}"/>.
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/> and <see cref="AclDbContext{TKey}"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey,TRole}"/> used to seed initial role data.</param>
/// <param name="context">An implementation, or default <see cref="AclDbContext{TKey}"/>.</param>
Expand All @@ -86,45 +54,28 @@
)
: base(initialDataSeeder, context)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="AclManager{TKey}"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>, <see cref="IUserManager{TKey}"/>, and <see cref="IResourceManager{TKey}"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey,TRole}"/> used to seed initial role data.</param>
/// <param name="userManager">An implementation of <see cref="IUserManager{TKey}"/>.</param>
/// <param name="resourceManager">An implementation of <see cref="IResourceManager{TKey}"/>.</param>
public AclManager(
IInitialDataSeeder<TKey, Role<TKey>> initialDataSeeder,
IUserManager<TKey> userManager,
IResourceManager<TKey> resourceManager
)
: base(initialDataSeeder, userManager, resourceManager)
{ }
}

/// <summary>
/// Manages access control lists (ACLs) using keys, users, roles, and resources of specified types.
/// Manages access control lists (ACLs) using keys, users, roles, and resources of specified types. <br/>
/// This class provides the complete functionality for managing ACLs with specified key, user, role, and resource types.
/// </summary>
/// <typeparam name="TKey">The type of the key, which must implement <see cref="IEquatable{TKey}"/>.</typeparam>
/// <typeparam name="TUser">The type of the user, which must be a derived type of <see cref="User{TKey}"/>.</typeparam>
/// <typeparam name="TRole">The type of the role, which must be a derived type of <see cref="Role{TKey}"/></typeparam>
/// <typeparam name="TResource">The type of the resource, which must be a derived type of <see cref="Resource{TKey}"/></typeparam>
public class AclManager<TKey, TUser, TRole, TResource> : IAclManager<TKey, TUser, TResource>
public class AclManager<TKey, TUser, TRole, TResource> : IAclManager<TKey, TUser, TRole, TResource>

Check notice on line 67 in src/Acl.Net.Core.Managers/AclManager.cs

View check run for this annotation

codefactor.io / CodeFactor

src/Acl.Net.Core.Managers/AclManager.cs#L67

File may only contain a single type. (SA1402)

Check warning on line 67 in src/Acl.Net.Core.Managers/AclManager.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Reduce the number of generic parameters in the 'AclManager' class to no more than the 2 authorized. (https://rules.sonarsource.com/csharp/RSPEC-2436)

Check warning on line 67 in src/Acl.Net.Core.Managers/AclManager.cs

View workflow job for this annotation

GitHub Actions / Build and analyze

Reduce the number of generic parameters in the 'AclManager' class to no more than the 2 authorized. (https://rules.sonarsource.com/csharp/RSPEC-2436)
where TKey : IEquatable<TKey>
where TUser : User<TKey>, new()
where TRole : Role<TKey>
where TResource : Resource<TKey>
{
protected readonly IInitialDataSeeder<TKey, TRole> InitialDataSeeder;
protected readonly IUserManager<TKey, TUser, TRole> UserManager;
protected readonly IResourceManager<TKey, TUser, TResource> ResourceManager;
protected readonly AclDbContext<TKey, TUser, TRole, TResource> Context;

/// <summary>
/// Initializes a new instance of the <see cref="AclManager{TKey,TUser,TRole,TResource}"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>
/// and with default <see cref="UserManager{TKey,TUser,TRole,TResource}"/> and <see cref="ResourceManager{TKey,TUser,TRole,TResource}"/>.
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/> and <see cref="AclDbContext{TKey,TUser,TRole,TResource}"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey, TRole}"/> used to seed initial role data.</param>
/// <param name="context">An implementation, or default <see cref="AclDbContext{TKey,TUser,TRole,TResource}"/>.</param>
Expand All @@ -134,91 +85,49 @@
)
{
InitialDataSeeder = initialDataSeeder;
UserManager = new UserManager<TKey, TUser, TRole, TResource>(context);
ResourceManager = new ResourceManager<TKey, TUser, TRole, TResource>(context, initialDataSeeder);
}

/// <summary>
/// Initializes a new instance of the <see cref="AclManager{TKey,TUser,TRole,TResource}"/> class
/// with the provided <see cref="IInitialDataSeeder{TKey,TRole}"/>, <see cref="IUserManager{TKey,TUser,TRole}"/>, and <see cref="IResourceManager{TKey,TUser,TResource}"/>.
/// </summary>
/// <param name="initialDataSeeder">An implementation of <see cref="IInitialDataSeeder{TKey,TRole}"/> used to seed initial role data.</param>
/// <param name="userManager">An implementation of <see cref="IUserManager{TKey,TUser,TRole}"/> for user management.</param>
/// <param name="resourceManager">An implementation of <see cref="IResourceManager{TKey,TUser,TResource}"/> for resource management.</param>
public AclManager(
IInitialDataSeeder<TKey, TRole> initialDataSeeder,
IUserManager<TKey, TUser, TRole> userManager,
IResourceManager<TKey, TUser, TResource> resourceManager
)
{
InitialDataSeeder = initialDataSeeder;
UserManager = userManager;
ResourceManager = resourceManager;
}

/// <inheritdoc />
public virtual bool IsPermitted(string userName, string resourceName)
{
var user = UserManager.UserProcessing(userName, InitialDataSeeder.SeedUserRole());
return ResourceManager.IsPermitted(user, resourceName);
}

/// <inheritdoc />
public virtual bool IsPermitted(TUser user, string resourceName)
{
return ResourceManager.IsPermitted(user, resourceName);
Context = context;
}

/// <inheritdoc />
public virtual bool IsPermitted(string userName, TResource resource)
public bool IsPermitted(string userName, string resourceName)
{
var user = UserManager.UserProcessing(userName, InitialDataSeeder.SeedUserRole());
return ResourceManager.IsPermitted(user, resource);
var user = GetUserByName(userName);
return IsAdmin(user) || GetUserRoles(user.Name).Any(role => IsPermitted(role, GetResourceByName(resourceName)));
}

/// <inheritdoc />
public virtual bool IsPermitted(TUser user, TResource resource)
public bool IsPermitted(TUser user, TResource resource)
{
return ResourceManager.IsPermitted(user, resource);
return IsAdmin(user) || GetUserRoles(user.Name).Any(role => IsPermitted(role, resource));
}

/// <inheritdoc />
public virtual IEnumerable<TResource> IsPermitted(string userName, IEnumerable<string> resourceNames)
public bool IsPermitted(TRole role, TResource resource)
{
var user = UserManager.UserProcessing(userName, InitialDataSeeder.SeedUserRole());
return ResourceManager.IsPermitted(user, resourceNames);
return IsAdmin(role) || Context.Resources.Any(res => res.Name.Equals(resource.Name) && res.RoleId.Equals(role.Id));
}

/// <inheritdoc />
public virtual async Task<bool> IsPermittedAsync(string userName, string resourceName)
{
var user = await UserManager.UserProcessingAsync(userName, InitialDataSeeder.SeedUserRole());
return await ResourceManager.IsPermittedAsync(user, resourceName);
}
public bool IsAdmin(TKey roleId) => roleId.Equals(InitialDataSeeder.SeedAdminRole().Id);
public bool IsAdmin(TUser user) => IsAdmin(user.RoleId);
public bool IsAdmin(TRole role) => IsAdmin(role.Id);

/// <inheritdoc />
public virtual async Task<bool> IsPermittedAsync(TUser user, string resourceName)
public IEnumerable<TRole> GetUserRoles(string userName)
{
return await ResourceManager.IsPermittedAsync(user, resourceName);
return Context.Roles
.Where(r => Context.Users
.Where(u => u.Name == userName)
.Select(u => u.RoleId)
.Contains(r.Id))
.ToArray();
}

/// <inheritdoc />
public virtual async Task<bool> IsPermittedAsync(string userName, TResource resource)
{
var user = await UserManager.UserProcessingAsync(userName, InitialDataSeeder.SeedUserRole());
return await ResourceManager.IsPermittedAsync(user, resource);
}

/// <inheritdoc />
public virtual async Task<bool> IsPermittedAsync(TUser user, TResource resource)
private TResource GetResourceByName(string resourceName)
{
return await ResourceManager.IsPermittedAsync(user, resource);
return Context.Resources.FirstOrDefault(r => r.Name == resourceName) ?? throw new ResourceNotFoundException(resourceName);
}

/// <inheritdoc />
public virtual async Task<IEnumerable<TResource>> IsPermittedAsync(string userName, IEnumerable<string> resourceNames)
private TUser GetUserByName(string userName)
{
var user = await UserManager.UserProcessingAsync(userName, InitialDataSeeder.SeedUserRole());
return await ResourceManager.IsPermittedAsync(user, resourceNames);
return Context.Users.FirstOrDefault(r => r.Name == userName) ?? throw new UserNotFoundException(userName);
}
}
15 changes: 15 additions & 0 deletions src/Acl.Net.Core.Managers/Exceptions/UserNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Acl.Net.Core.Managers.Exceptions;

/// <summary>
/// Represents an exception that is thrown when a specific user is not found.
/// </summary>
public class UserNotFoundException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="UserNotFoundException"/> class with the specified user-name.
/// </summary>
/// <param name="userName">The name of the user that could not be found.</param>
public UserNotFoundException(string userName)
: base($"User with name '{userName}' not found.")
{ }
}
Loading
Loading