@@ -776,6 +776,73 @@ private static bool HasAnyAvailableOperations(Entity entity)
776776 return false ;
777777 }
778778
779+ /// <summary>
780+ /// Filters the exposed column names based on the superset of available fields across all role permissions.
781+ /// A field is included if at least one role has access to it (through include/exclude settings).
782+ /// </summary>
783+ /// <param name="entity">The entity to check permissions for.</param>
784+ /// <param name="exposedColumnNames">All exposed column names from the database.</param>
785+ /// <returns>Filtered set of column names that are available based on permissions.</returns>
786+ private static HashSet < string > FilterFieldsByPermissions ( Entity entity , HashSet < string > exposedColumnNames )
787+ {
788+ if ( entity ? . Permissions is null || entity . Permissions . Length == 0 )
789+ {
790+ return exposedColumnNames ;
791+ }
792+
793+ HashSet < string > availableFields = new ( ) ;
794+
795+ foreach ( EntityPermission permission in entity . Permissions )
796+ {
797+ if ( permission . Actions is null )
798+ {
799+ continue ;
800+ }
801+
802+ foreach ( EntityAction action in permission . Actions )
803+ {
804+ // If Fields is null, all fields are available for this action
805+ if ( action . Fields is null )
806+ {
807+ availableFields . UnionWith ( exposedColumnNames ) ;
808+ continue ;
809+ }
810+
811+ // Determine included fields
812+ HashSet < string > actionFields ;
813+ if ( action . Fields . Include is null || action . Fields . Include . Contains ( "*" ) )
814+ {
815+ // Include is null or contains wildcard - start with all fields
816+ actionFields = new HashSet < string > ( exposedColumnNames ) ;
817+ }
818+ else
819+ {
820+ // Only include explicitly listed fields that exist in exposed columns
821+ actionFields = new HashSet < string > ( action . Fields . Include . Where ( f => exposedColumnNames . Contains ( f ) ) ) ;
822+ }
823+
824+ // Remove excluded fields
825+ if ( action . Fields . Exclude is not null && action . Fields . Exclude . Count > 0 )
826+ {
827+ if ( action . Fields . Exclude . Contains ( "*" ) )
828+ {
829+ // Exclude all - no fields available for this action
830+ actionFields . Clear ( ) ;
831+ }
832+ else
833+ {
834+ actionFields . ExceptWith ( action . Fields . Exclude ) ;
835+ }
836+ }
837+
838+ // Add to superset of available fields
839+ availableFields . UnionWith ( actionFields ) ;
840+ }
841+ }
842+
843+ return availableFields ;
844+ }
845+
779846 /// <summary>
780847 /// Creates the request body definition, which includes the expected media type (application/json)
781848 /// and reference to request body schema.
@@ -1088,6 +1155,10 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas(RuntimeEntities
10881155
10891156 SourceDefinition sourceDefinition = metadataProvider . GetSourceDefinition ( entityName ) ;
10901157 HashSet < string > exposedColumnNames = GetExposedColumnNames ( entityName , sourceDefinition . Columns . Keys . ToList ( ) , metadataProvider ) ;
1158+
1159+ // Filter fields based on the superset of permissions across all roles
1160+ exposedColumnNames = FilterFieldsByPermissions ( entity , exposedColumnNames ) ;
1161+
10911162 HashSet < string > nonAutoGeneratedPKColumnNames = new ( ) ;
10921163
10931164 if ( dbObject . SourceType is EntitySourceType . StoredProcedure )
0 commit comments