Skip to content

Global query filter

Global Query Filter

This Implementation creates a dynamic security filter that automatically applies access control rules to all entities implementing IAccessControlledEntity. It ensures users can only see data they own or have explicit permission to access.

flowchart TD
    A[Start Filter] --> B{Access Controlled?}
    B -->|No| C[No Security]
    B -->|Yes| D{User Logged In?}
    D -->|No| E[Block Access]
    D -->|Yes| F{Is Owner?}
    F -->|Yes| G[Allow Access]
    F -->|No| H{Has Valid Grant?}
    H -->|Yes| G
    H -->|No| E

Flow Description

  1. Initial Filter Check

    If entity doesn't implement IAccessControlledEntity, then it should return base filter.

    if (!typeof(IAccessControlledEntity).IsAssignableFrom(typeof(TEntity)))
    {
        return baseExpression;
    }
    
  2. Dynamic Primary Key Resolution

    Handle entities with different primary key names

    var entityId = modelBuilder.Entity<TEntity>().Metadata.FindPrimaryKey()?.Properties.First().Name ?? "Id";
    
  3. Access Control Logic Construction

    The core security logic follows a two-path access model:

    Path A: Ownership Access

    Users automatically access entities they own, Ownership bypasses all grant checks for performance

    var entityId = modelBuilder.Entity<TEntity>().Metadata.FindPrimaryKey()?.Properties.First().Name ?? "Id";
    

    Path B: Grant-Based Access

    Grant Validity Checks:

    1. Expiration Check: (ag.ExpiresAt == null || ag.ExpiresAt > NowUtc) - Grant not expired

    2. Entity Matching:

      • ag.EntityId == EF.Property<Guid>(e, entityId) - Matches target entity
      • ag.EntityType == typeof(TEntity).Name - Matches entity type
    3. Tenant Isolation: ag.TenantId == ScopeTenantId - Prevents cross-tenant data leaks

    4. Permission Verification: ((int)ag.Permissions & (int)PermissionFlags.Read) == (int)PermissionFlags.Read - Bitwise Read permission check

    Grant Assignment Checks:

    1. User Assignment: ag.UserId != null && ag.UserId == ScopeUserId - Direct user grant OR

    2. OU Assignment:

      • ag.OrganizationUnitId != null - OU-based grant
      • ScopeOrganizationUnitIds.Count > 0- User has OUs
      • ScopeOrganizationUnitIds.Contains(ag.OrganizationUnitId.Value) - User in granted OU

Security Principles Implemented

  1. Defense in Depth

  2. Multiple validation checks

  3. Tenant isolation
  4. Type safety through entity type matching

  5. Principle of Least Privilege

  6. Explicit grants required for non-owners

  7. Permission granularity through bitwise flags
  8. Temporal access control via expiration

  9. Performance Optimization

  10. Ownership shortcut avoids grant queries

  11. Combined filter execution at database level
  12. Dynamic PK handling prevents reflection overhead

Query Translation

The Expression Tree gets translated to SQL WHERE clauses:

WHERE (
    [OwnerUserId] = @userid
    OR EXISTS (
        SELECT 1 FROM AccessGrants
        WHERE (ExpiresAt IS NULL OR ExpiresAt > GETUTCDATE())
        AND EntityId = [Id]
        AND EntityType = 'EntityName'
        AND TenantId = @tenantid
        AND (Permissions & 1) = 1
        AND (
            UserId = @userid
            OR (
                OrganizationUnitId IS NOT NULL
                AND OrganizationUnitId IN (user_ou_list)
            )
        )
    )
)

Multi-Tenant Safety

  • Explicit tenant ID matching prevents data leakage between tenants
  • All access checks include tenant context