22// Licensed under the MIT License.
33
44using System ;
5+ using System . Collections . Generic ;
56using System . Text . Json ;
67using System . Threading . Tasks ;
8+ using Azure . DataApiBuilder . Config ;
9+ using Azure . DataApiBuilder . Service . Exceptions ;
710using Azure . DataApiBuilder . Service . Resolvers ;
811using Microsoft . Azure . Cosmos ;
912using Microsoft . Extensions . DependencyInjection ;
@@ -20,6 +23,7 @@ public class QueryFilterTests : TestBase
2023 private static readonly string _containerName = Guid . NewGuid ( ) . ToString ( ) ;
2124 private static int _pageSize = 10 ;
2225 private static readonly string _graphQLQueryName = "planets" ;
26+ private static List < string > _idList ;
2327
2428 [ ClassInitialize ]
2529 public static void TestFixtureSetup ( TestContext context )
@@ -28,8 +32,11 @@ public static void TestFixtureSetup(TestContext context)
2832 CosmosClient cosmosClient = _application . Services . GetService < CosmosClientProvider > ( ) . Client ;
2933 cosmosClient . CreateDatabaseIfNotExistsAsync ( DATABASE_NAME ) . Wait ( ) ;
3034 cosmosClient . GetDatabase ( DATABASE_NAME ) . CreateContainerIfNotExistsAsync ( _containerName , "/id" ) . Wait ( ) ;
31- CreateItems ( DATABASE_NAME , _containerName , 10 ) ;
35+ _idList = CreateItems ( DATABASE_NAME , _containerName , 10 ) ;
3236 OverrideEntityContainer ( "Planet" , _containerName ) ;
37+ OverrideEntityContainer ( "Earth" , _containerName ) ;
38+ OverrideEntityContainer ( "StarAlias" , _containerName ) ;
39+ OverrideEntityContainer ( "TagAlias" , _containerName ) ;
3340 }
3441
3542 /// <summary>
@@ -642,6 +649,193 @@ public async Task TestFilterOnInnerNestedFields()
642649 await ExecuteAndValidateResult ( _graphQLQueryName , gqlQuery , dbQuery ) ;
643650 }
644651
652+ /// <summary>
653+ /// Test filters when entity names are using alias.
654+ /// This exercises the scenario when top level entity name is using an alias,
655+ /// as well as the nested level entity name is using an alias,
656+ /// in both layers, the entity name to GraphQL type lookup is successfully performed.
657+ /// </summary>
658+ [ TestMethod ]
659+ public async Task TestFilterWithEntityNameAlias ( )
660+ {
661+ string gqlQuery = @"{
662+ stars(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {tag : {name : {eq : ""test name""}}})
663+ {
664+ items {
665+ tag {
666+ id
667+ name
668+ }
669+ }
670+ }
671+ }" ;
672+
673+ string dbQuery = "SELECT top 1 c.tag FROM c where c.tag.name = \" test name\" " ;
674+ await ExecuteAndValidateResult ( "stars" , gqlQuery , dbQuery ) ;
675+ }
676+
677+ #region Field Level Auth
678+ /// <summary>
679+ /// Tests that the field level query filter succeeds requests when filter fields are authorized
680+ /// </summary>
681+ [ TestMethod ]
682+ public async Task TestQueryFilterFieldAuth_AuthorizedField ( )
683+ {
684+ string gqlQuery = @"{
685+ earths(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {id : {eq : """ + _idList [ 0 ] + @"""}})
686+ {
687+ items {
688+ id
689+ }
690+ }
691+ }" ;
692+
693+ string dbQuery = $ "SELECT top 1 c.id FROM c where c.id = \" { _idList [ 0 ] } \" ";
694+ await ExecuteAndValidateResult ( "earths" , gqlQuery , dbQuery ) ;
695+ }
696+
697+ /// <summary>
698+ /// Tests that the field level query filter fails authorization when filter fields are
699+ /// unauthorized because the field 'name' on object type 'earth' is an excluded field of the read
700+ /// operation permissions defined for the anonymous role.
701+ /// </summary>
702+ [ TestMethod ]
703+ public async Task TestQueryFilterFieldAuth_UnauthorizedField ( )
704+ {
705+ // Run query
706+ string gqlQuery = @"{
707+ earths(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {name : {eq : ""test name""}})
708+ {
709+ items {
710+ name
711+ }
712+ }
713+ }" ;
714+ string clientRoleHeader = AuthorizationType . Anonymous . ToString ( ) ;
715+ JsonElement response = await ExecuteGraphQLRequestAsync (
716+ queryName : "earths" ,
717+ query : gqlQuery ,
718+ variables : new ( ) { { "name" , "test name" } } ,
719+ authToken : AuthTestHelper . CreateStaticWebAppsEasyAuthToken ( specificRole : clientRoleHeader ) ,
720+ clientRoleHeader : clientRoleHeader ) ;
721+
722+ // Validate the result contains the GraphQL authorization error code.
723+ string errorMessage = response . ToString ( ) ;
724+ Assert . IsTrue ( errorMessage . Contains ( DataApiBuilderException . GRAPHQL_FILTER_FIELD_AUTHZ_FAILURE ) ) ;
725+ }
726+
727+ /// <summary>
728+ /// Tests that the field level query filter succeeds requests when filter fields are authorized
729+ /// </summary>
730+ [ TestMethod ]
731+ public async Task TestQueryFilterFieldAuth_AuthorizedWildCard ( )
732+ {
733+ // Run query
734+ string gqlQuery = @"{
735+ planets(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {name : {eq : ""Earth""}})
736+ {
737+ items {
738+ name
739+ }
740+ }
741+ }" ;
742+ string clientRoleHeader = AuthorizationType . Anonymous . ToString ( ) ;
743+ JsonElement response = await ExecuteGraphQLRequestAsync (
744+ queryName : "planets" ,
745+ query : gqlQuery ,
746+ variables : new ( ) { } ,
747+ authToken : AuthTestHelper . CreateStaticWebAppsEasyAuthToken ( specificRole : clientRoleHeader ) ,
748+ clientRoleHeader : clientRoleHeader ) ;
749+
750+ Assert . AreEqual ( response . GetProperty ( "items" ) [ 0 ] . GetProperty ( "name" ) . ToString ( ) , "Earth" ) ;
751+ }
752+
753+ /// <summary>
754+ /// Tests that the nested field level query filter passes authorization when nested filter fields are authorized
755+ /// because the field 'id' on object type 'earth' is an included field of the read operation
756+ /// permissions defined for the anonymous role.
757+ /// </summary>
758+ [ TestMethod ]
759+ public async Task TestQueryFilterNestedFieldAuth_AuthorizedNestedField ( )
760+ {
761+ string gqlQuery = @"{
762+ planets(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {earth : {id : {eq : """ + _idList [ 0 ] + @"""}}})
763+ {
764+ items {
765+ earth {
766+ id
767+ }
768+ }
769+ }
770+ }" ;
771+
772+ JsonElement actual = await ExecuteGraphQLRequestAsync ( _graphQLQueryName , query : gqlQuery ) ;
773+ Assert . AreEqual ( actual . GetProperty ( "items" ) [ 0 ] . GetProperty ( "earth" ) . GetProperty ( "id" ) . ToString ( ) , _idList [ 0 ] ) ;
774+ }
775+
776+ /// <summary>
777+ /// Tests that the nested field level query filter fails authorization when nested filter fields are
778+ /// unauthorized because the field 'name' on object type 'earth' is an excluded field of the read
779+ /// operation permissions defined for the anonymous role.
780+ /// </summary>
781+ [ TestMethod ]
782+ public async Task TestQueryFilterNestedFieldAuth_UnauthorizedNestedField ( )
783+ {
784+ // Run query
785+ string gqlQuery = @"{
786+ planets(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {earth : {name : {eq : ""test name""}}})
787+ {
788+ items {
789+ id
790+ name
791+ earth {
792+ name
793+ }
794+ }
795+ }
796+ }" ;
797+
798+ string clientRoleHeader = AuthorizationType . Anonymous . ToString ( ) ;
799+ JsonElement response = await ExecuteGraphQLRequestAsync (
800+ queryName : _graphQLQueryName ,
801+ query : gqlQuery ,
802+ variables : new ( ) { { "name" , "test name" } } ,
803+ authToken : AuthTestHelper . CreateStaticWebAppsEasyAuthToken ( specificRole : clientRoleHeader ) ,
804+ clientRoleHeader : clientRoleHeader ) ;
805+
806+ // Validate the result contains the GraphQL authorization error code.
807+ string errorMessage = response . ToString ( ) ;
808+ Assert . IsTrue ( errorMessage . Contains ( DataApiBuilderException . GRAPHQL_FILTER_FIELD_AUTHZ_FAILURE ) ) ;
809+ }
810+
811+ /// <summary>
812+ /// This is for testing the scenario when the filter field is authorized, but the query field is unauthorized.
813+ /// For "type" field in "Earth" GraphQL type, it has @authorize(policy: "authenticated") directive in the test schema,
814+ /// but in the runtime config, this field is marked as included field for read operation with anonymous role,
815+ /// this should return unauthorized.
816+ /// </summary>
817+ [ TestMethod ]
818+ public async Task TestQueryFieldAuthConflictingWithFilterFieldAuth_Unauthorized ( )
819+ {
820+ // Run query
821+ string gqlQuery = @"{
822+ earths(first: 1, " + QueryBuilder . FILTER_FIELD_NAME + @" : {id : {eq : """ + _idList [ 0 ] + @"""}})
823+ {
824+ items {
825+ id
826+ type
827+ }
828+ }
829+ }" ;
830+
831+ JsonElement response = await ExecuteGraphQLRequestAsync ( _graphQLQueryName , query : gqlQuery ) ;
832+
833+ // Validate the result contains the GraphQL authorization error code.
834+ string errorMessage = response . ToString ( ) ;
835+ Assert . IsTrue ( errorMessage . Contains ( "The current user is not authorized to access this resource." ) ) ;
836+ }
837+ #endregion
838+
645839 [ ClassCleanup ]
646840 public static void TestFixtureTearDown ( )
647841 {
0 commit comments