From 0ec0bacf1f40f3e1286cccd5f8789db7391f0bff Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 10 Apr 2024 10:38:39 -0700 Subject: [PATCH] feat: #379 allow global suppression of auto-includes --- .../modeling/model-components/data-sources.md | 3 +- docs/topics/security.md | 1 + playground/Coalesce.Domain/AppDbContext.cs | 2 ++ .../CoalesceConfigurationAttribute.cs | 30 +++++++++++++++++++ .../Helpers/QueryableExtensions.cs | 2 +- .../Helpers/ReflectionExtensions.cs | 7 +++++ .../Helpers/SymbolExtensions.cs | 2 +- .../TypeDefinition/PropertyViewModel.cs | 5 ++++ .../Reflection/ReflectionTypeViewModel.cs | 2 ++ .../Symbol/SymbolTypeViewModel.cs | 3 ++ .../TypeDefinition/TypeViewModel.cs | 2 ++ 11 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 src/IntelliTect.Coalesce/Application/CoalesceConfigurationAttribute.cs diff --git a/docs/modeling/model-components/data-sources.md b/docs/modeling/model-components/data-sources.md index b5d276c12..010885ff8 100644 --- a/docs/modeling/model-components/data-sources.md +++ b/docs/modeling/model-components/data-sources.md @@ -166,8 +166,9 @@ When an object or list of objects is requested, the default behavior of the the Clients can suppress this per-request by setting `includes = "none"` on your TypeScript [ViewModel](/stacks/disambiguation/view-model.md) or [ListViewModel](/stacks/disambiguation/list-view-model.md), but note this is not a security mechanism and should only be used to reduce payload size or improve response time. -On the server, you can suppress this behavior by placing `[Read(NoAutoInclude = true)]` on either an entire class (affecting all navigation properties of that type), or on specific navigation properties. When placed on a entity class that holds sensitive data, this can help ensure you don't accidentally leak records due to forgetting to customize the data sources of the types whose navigation properties reference your sensitive entity. +On the server, you can suppress this behavior by placing `[Read(NoAutoInclude = true)]` on either an entire class (affecting all navigation properties of that type), or on specific navigation properties. When placed on a entity class that holds sensitive data, this can help ensure you don't accidentally leak records due to forgetting to customize the data sources of the types whose navigation properties reference your sensitive entity. +You can also suppress this for your entire application by placing `[assembly: CoalesceConfiguration(NoAutoInclude = true)]` on the assembly that holds your models. ### Properties diff --git a/docs/topics/security.md b/docs/topics/security.md index 244c23066..0435d3155 100644 --- a/docs/topics/security.md +++ b/docs/topics/security.md @@ -46,6 +46,7 @@ Prevent * Omit `base` call in Data Source `GetQuery` override. * `[Read(NoAutoInclude = true)]` on properties or types. +* `[assembly: CoalesceConfiguration(NoAutoInclude = true)]` diff --git a/playground/Coalesce.Domain/AppDbContext.cs b/playground/Coalesce.Domain/AppDbContext.cs index a6724f79c..d876695c7 100644 --- a/playground/Coalesce.Domain/AppDbContext.cs +++ b/playground/Coalesce.Domain/AppDbContext.cs @@ -7,6 +7,8 @@ using System; using System.Linq; +// [assembly: CoalesceConfiguration(NoAutoInclude = true)] + namespace Coalesce.Domain { diff --git a/src/IntelliTect.Coalesce/Application/CoalesceConfigurationAttribute.cs b/src/IntelliTect.Coalesce/Application/CoalesceConfigurationAttribute.cs new file mode 100644 index 000000000..301f67bba --- /dev/null +++ b/src/IntelliTect.Coalesce/Application/CoalesceConfigurationAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelliTect.Coalesce +{ + /// + /// Defines static, assembly-level configuration for the Coalesce models in the targeted assembly. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public sealed class CoalesceConfigurationAttribute : Attribute + { + /// + /// + /// If true, models defined in the targeted assembly will not be loaded by Coalesce's + /// method, + /// nor by the Default Loading Behavior, + /// which normally automatically includes the first level of navigation properties of any entity returned from a Coalesce-generated /get, /list, or /save API endpoint. + /// + /// + /// If true, you must always Include the desired navigation properties in a custom data source, or load entites directly from the type's /get or /list endpoints. + /// + /// + /// Use this to prevent accidental unrestricted, unfiltered loading of types that hold sensitive information. + /// + public bool NoAutoInclude { get; set; } + } +} diff --git a/src/IntelliTect.Coalesce/Helpers/QueryableExtensions.cs b/src/IntelliTect.Coalesce/Helpers/QueryableExtensions.cs index a1e0bc289..48b773ec0 100644 --- a/src/IntelliTect.Coalesce/Helpers/QueryableExtensions.cs +++ b/src/IntelliTect.Coalesce/Helpers/QueryableExtensions.cs @@ -15,7 +15,7 @@ public static class QueryableExtensions { /// /// Includes immediate children, as well as the other side of many-to-many relationships. - /// Does not include navigations or classes that have set. + /// Does not include navigations or classes that have or set. /// public static IQueryable IncludeChildren(this IQueryable query, ReflectionRepository? reflectionRepository = null) where T : class { diff --git a/src/IntelliTect.Coalesce/TypeDefinition/Helpers/ReflectionExtensions.cs b/src/IntelliTect.Coalesce/TypeDefinition/Helpers/ReflectionExtensions.cs index 9bbc7d1aa..429b1b615 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/Helpers/ReflectionExtensions.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/Helpers/ReflectionExtensions.cs @@ -38,6 +38,13 @@ public ReflectionAttributeViewModel(TAttribute instance, ReflectionRepository? r } } + internal class ReflectionAttributeProvider(ICustomAttributeProvider symbol) : IAttributeProvider + { + public IEnumerable> GetAttributes() + where TAttribute : Attribute + => symbol.GetAttributes(); + } + public static class ReflectionExtensions { public static IEnumerable> GetAttributes( diff --git a/src/IntelliTect.Coalesce/TypeDefinition/Helpers/SymbolExtensions.cs b/src/IntelliTect.Coalesce/TypeDefinition/Helpers/SymbolExtensions.cs index 7228475f6..786cb529d 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/Helpers/SymbolExtensions.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/Helpers/SymbolExtensions.cs @@ -54,7 +54,7 @@ public SymbolAttributeViewModel(AttributeData attributeData, ReflectionRepositor } } - public class SymbolAttributeProvider(ISymbol symbol) : IAttributeProvider + internal class SymbolAttributeProvider(ISymbol symbol) : IAttributeProvider { public IEnumerable> GetAttributes() where TAttribute : Attribute diff --git a/src/IntelliTect.Coalesce/TypeDefinition/PropertyViewModel.cs b/src/IntelliTect.Coalesce/TypeDefinition/PropertyViewModel.cs index b8fefd195..cdbdf53c2 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/PropertyViewModel.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/PropertyViewModel.cs @@ -736,6 +736,11 @@ public bool CanAutoInclude return false; } + if (PureType.Assembly.GetAttributeValue(a => a.NoAutoInclude) == true) + { + return false; + } + return true; } } diff --git a/src/IntelliTect.Coalesce/TypeDefinition/Reflection/ReflectionTypeViewModel.cs b/src/IntelliTect.Coalesce/TypeDefinition/Reflection/ReflectionTypeViewModel.cs index df3406b40..9d0a6fa8a 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/Reflection/ReflectionTypeViewModel.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/Reflection/ReflectionTypeViewModel.cs @@ -62,6 +62,8 @@ internal static ReflectionTypeViewModel GetOrCreate(ReflectionRepository? reflec return reflectionRepository?.GetOrAddType(type) ?? new ReflectionTypeViewModel(reflectionRepository, type); } + public override IAttributeProvider Assembly + => new ReflectionAttributeProvider(Info.Assembly); // TODO: why is an arity of 1 removed from the name? Seems to be an oversight // - If we're removing arity, we should remove any arity. diff --git a/src/IntelliTect.Coalesce/TypeDefinition/Symbol/SymbolTypeViewModel.cs b/src/IntelliTect.Coalesce/TypeDefinition/Symbol/SymbolTypeViewModel.cs index 2eb9bc657..28b7b37ca 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/Symbol/SymbolTypeViewModel.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/Symbol/SymbolTypeViewModel.cs @@ -60,6 +60,9 @@ internal static SymbolTypeViewModel GetOrCreate(ReflectionRepository? reflection return reflectionRepository?.GetOrAddType(symbol) ?? new SymbolTypeViewModel(reflectionRepository, symbol); } + public override IAttributeProvider Assembly + => Symbol.ContainingAssembly.GetAttributeProvider(); + protected override bool ShouldCreateClassViewModel => base.ShouldCreateClassViewModel && Symbol is INamedTypeSymbol; diff --git a/src/IntelliTect.Coalesce/TypeDefinition/TypeViewModel.cs b/src/IntelliTect.Coalesce/TypeDefinition/TypeViewModel.cs index 5eb107185..442ca630c 100644 --- a/src/IntelliTect.Coalesce/TypeDefinition/TypeViewModel.cs +++ b/src/IntelliTect.Coalesce/TypeDefinition/TypeViewModel.cs @@ -23,6 +23,8 @@ internal TypeViewModel(ReflectionRepository? reflectionRepository) : this() public ReflectionRepository? ReflectionRepository { get; internal set; } + public abstract IAttributeProvider Assembly { get; } + public abstract string Name { get; } ///