Skip to content

ShadyNagy/EntityFrameworkCore.PolicyEnforcement

Repository files navigation

publish to nuget EntityFrameworkCore.PolicyEnforcement on NuGet NuGet License Sponsor

EntityFrameworkCore.PolicyEnforcement

πŸ“Œ Introduction

A .NET library for enforcing access control policies in Entity Framework Core applications with dependency injection support.

πŸ“Œ Key Features:

  • πŸ”’ Declarative Access Control: Define policies at the entity level using a fluent API
  • 🧩 Multiple Policy Types: Support for role-based, permission-based, ownership-based, and property-based policies
  • ⚑ Automatic Enforcement: Policies applied automatically to both queries and commands
  • πŸ”Œ Extensible: Build custom policy types for specific business requirements
  • πŸ—οΈ Clean Architecture Friendly: Designed to work well with domain-driven and clean architecture approaches
  • πŸ§ͺ Testable: Easy to mock and test in isolation
  • πŸ”„ Chainable Policies: Combine multiple policies with AND/OR operators
  • 🧠 Smart Caching: Performance optimized with compiled expression caching

πŸ“₯ Installation

dotnet add package EntityFrameworkCore.PolicyEnforcement

πŸš€ Quick Start:

1. Configure in your DbContext

public class ApplicationDbContext : DbContext
{
    public DbSet<Document> Documents { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(connectionString)
                     .UsePolicyEnforcement();
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Document>()
            .HasAccessPolicy(policy => 
                policy.RequireOwnership(d => d.OwnerId)
                    .Or(policy.RequireRole("Admin"))
            );
    }
}

2. Set up the User Context

Create a class implementing IUserContext:

public class CurrentUserContext : IUserContext
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public CurrentUserContext(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public string? GetCurrentUserId() => 
        _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
    
    public bool IsInRole(string role) => 
        _httpContextAccessor.HttpContext?.User?.IsInRole(role) ?? false;
    
    public bool HasPermission(string permission) => 
        _httpContextAccessor.HttpContext?.User?.HasClaim(c => 
            c.Type == "Permission" && c.Value == permission) ?? false;
}

3. Configure in Startup

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IUserContext, CurrentUserContext>();
    
    services.AddDbContext<ApplicationDbContext>(options => 
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
               .UsePolicyEnforcement(opt => 
               {
                   opt.EnableForQueries = true;
                   opt.EnableForCommands = true;
                   opt.ThrowOnViolation = true;
               }));
}

4. Set User Context Before Operations

public class DocumentService
{
    private readonly ApplicationDbContext _dbContext;
    private readonly IUserContext _userContext;
    
    public DocumentService(ApplicationDbContext dbContext, IUserContext userContext)
    {
        _dbContext = dbContext;
        _userContext = userContext;
    }
    
    public async Task<List<Document>> GetUserDocumentsAsync()
    {
        // Set the user context before querying
        _dbContext.SetUserContext(_userContext);
        
        // Policy is automatically applied
        return await _dbContext.Documents.ToListAsync();
    }
}

Policy Types

Role-Based Policies

modelBuilder.Entity<Payroll>()
    .HasAccessPolicy(policy => policy.RequireRole("HR"));

Permission-Based Policies

modelBuilder.Entity<Customer>()
    .HasAccessPolicy(policy => policy.RequirePermission("customers.read"));

Ownership-Based Policies

modelBuilder.Entity<UserProfile>()
    .HasAccessPolicy(policy => policy.RequireOwnership(p => p.UserId));

Property-Based Policies

modelBuilder.Entity<Document>()
    .HasAccessPolicy(policy => policy.RequireProperty(d => d.IsPublic));

Combined Policies

modelBuilder.Entity<Project>()
    .HasAccessPolicy(policy => 
        policy.RequireOwnership(p => p.OwnerId)
            .Or(policy.RequireRole("Manager"))
            .Or(policy.RequireProperty(p => p.IsPublic))
    );

Operation-Specific Policies

modelBuilder.Entity<Ticket>()
    .HasAccessPolicy(policy => policy.RequireRole("Support"), "Default")
    .HasAccessPolicy(policy => policy.RequireRole("SupportLead"), "Update")
    .HasAccessPolicy(policy => policy.RequireRole("Manager"), "Delete");

Advanced Usage

Custom Policies

modelBuilder.Entity<FinancialRecord>()
    .HasAccessPolicy(policy => policy.Custom(userContext => 
        fr => fr.Amount < 1000 || userContext.IsInRole("FinancialApprover")
    ));

Self-Checking Entities

public class Team : IDefineOwnAccessPolicy
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<TeamMember> Members { get; set; }
    
    public bool CanAccess(IUserContext userContext, string operation)
    {
        var userId = userContext.GetCurrentUserId();
        if (string.IsNullOrEmpty(userId)) return false;
        
        // Allow members to read, but only admins to modify
        if (operation == "Read")
            return Members.Any(m => m.UserId == userId);
            
        return Members.Any(m => m.UserId == userId && m.IsAdmin);
    }
}

πŸ”— License

This project is licensed under the MIT License. See the LICENSE file for details.


πŸ™Œ Contributing

🎯 Found a bug or have an idea for improvement?
Feel free to open an issue or submit a pull request!
πŸ”— GitHub Issues


⭐ Support the Project

If you find this package useful, give it a star ⭐ on GitHub and share it with others! πŸš€

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages