From 956b7ca0c25bca3ce678632f0039ce9ecede3863 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 29 Apr 2024 10:34:49 -0500 Subject: [PATCH 1/7] Use supergraph schema to extract auth info --- .../src/query_planner/bridge_query_planner.rs | 6 ++--- apollo-router/src/query_planner/fetch.rs | 13 +++++++---- apollo-router/src/query_planner/plan.rs | 22 +++++++++---------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index a266dadc76..671a114352 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -635,7 +635,7 @@ impl BridgeQueryPlanner { plan_success .data .query_plan - .extract_authorization_metadata(&self.subgraph_schemas, &key); + .extract_authorization_metadata(&self.schema.supergraph_schema(), &key); // the `statsReportKey` field should match the original query instead of the filtered query, to index them all under the same query let operation_signature = if matches!( @@ -1062,11 +1062,11 @@ impl QueryPlan { fn extract_authorization_metadata( &mut self, - subgraph_schemas: &SubgraphSchemas, + schema: &apollo_compiler::Schema, key: &CacheKeyMetadata, ) { if let Some(node) = self.node.as_mut() { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } } } diff --git a/apollo-router/src/query_planner/fetch.rs b/apollo-router/src/query_planner/fetch.rs index d10f47c7ab..40a78001da 100644 --- a/apollo-router/src/query_planner/fetch.rs +++ b/apollo-router/src/query_planner/fetch.rs @@ -619,13 +619,18 @@ impl FetchNode { pub(crate) fn extract_authorization_metadata( &mut self, - subgraph_schemas: &SubgraphSchemas, + schema: &apollo_compiler::Schema, global_authorisation_cache_key: &CacheKeyMetadata, ) { - let doc = self.parsed_operation(subgraph_schemas); - let schema = &subgraph_schemas[self.service_name.as_str()]; + let doc = ExecutableDocument::parse( + Valid::assume_valid_ref(schema), + &self.operation.as_serialized().to_string(), + "query.graphql", + ) + // Assume query planing creates a valid document: ignore parse errors + .unwrap_or_else(|invalid| invalid.partial); let subgraph_query_cache_key = AuthorizationPlugin::generate_cache_metadata( - doc, + &doc, self.operation_name.as_deref(), schema, !self.requires.is_empty(), diff --git a/apollo-router/src/query_planner/plan.rs b/apollo-router/src/query_planner/plan.rs index 4dd8e372b9..fcfe77014e 100644 --- a/apollo-router/src/query_planner/plan.rs +++ b/apollo-router/src/query_planner/plan.rs @@ -412,42 +412,40 @@ impl PlanNode { pub(crate) fn extract_authorization_metadata( &mut self, - subgraph_schemas: &SubgraphSchemas, + schema: &apollo_compiler::Schema, key: &CacheKeyMetadata, ) { match self { PlanNode::Fetch(fetch_node) => { - fetch_node.extract_authorization_metadata(subgraph_schemas, key); + fetch_node.extract_authorization_metadata(schema, key); } PlanNode::Sequence { nodes } => { for node in nodes { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } } PlanNode::Parallel { nodes } => { for node in nodes { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } } - PlanNode::Flatten(flatten) => flatten - .node - .extract_authorization_metadata(subgraph_schemas, key), + PlanNode::Flatten(flatten) => flatten.node.extract_authorization_metadata(schema, key), PlanNode::Defer { primary, deferred } => { if let Some(node) = primary.node.as_mut() { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } for deferred_node in deferred { if let Some(node) = deferred_node.node.take() { let mut new_node = (*node).clone(); - new_node.extract_authorization_metadata(subgraph_schemas, key); + new_node.extract_authorization_metadata(schema, key); deferred_node.node = Some(Arc::new(new_node)); } } } PlanNode::Subscription { primary: _, rest } => { if let Some(node) = rest.as_mut() { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } } PlanNode::Condition { @@ -456,10 +454,10 @@ impl PlanNode { else_clause, } => { if let Some(node) = if_clause.as_mut() { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } if let Some(node) = else_clause.as_mut() { - node.extract_authorization_metadata(subgraph_schemas, key); + node.extract_authorization_metadata(schema, key); } } } From bcc5db7160ee6de48c9164fe62ca83634cf678b6 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 29 Apr 2024 10:42:15 -0500 Subject: [PATCH 2/7] Fix lint --- apollo-router/src/query_planner/bridge_query_planner.rs | 2 +- apollo-router/src/query_planner/fetch.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 671a114352..f9b6548c81 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -635,7 +635,7 @@ impl BridgeQueryPlanner { plan_success .data .query_plan - .extract_authorization_metadata(&self.schema.supergraph_schema(), &key); + .extract_authorization_metadata(self.schema.supergraph_schema(), &key); // the `statsReportKey` field should match the original query instead of the filtered query, to index them all under the same query let operation_signature = if matches!( diff --git a/apollo-router/src/query_planner/fetch.rs b/apollo-router/src/query_planner/fetch.rs index 40a78001da..898da645ce 100644 --- a/apollo-router/src/query_planner/fetch.rs +++ b/apollo-router/src/query_planner/fetch.rs @@ -624,7 +624,7 @@ impl FetchNode { ) { let doc = ExecutableDocument::parse( Valid::assume_valid_ref(schema), - &self.operation.as_serialized().to_string(), + self.operation.as_serialized().to_string(), "query.graphql", ) // Assume query planing creates a valid document: ignore parse errors From cf25e1660cafe6eb323845f831a7eb546907c9e3 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 29 Apr 2024 11:34:34 -0500 Subject: [PATCH 3/7] Take advantage of existing schema validity to remove assumption in auth extraction --- apollo-router/src/query_planner/bridge_query_planner.rs | 2 +- apollo-router/src/query_planner/fetch.rs | 4 ++-- apollo-router/src/query_planner/plan.rs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index f9b6548c81..d611122dc5 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -1062,7 +1062,7 @@ impl QueryPlan { fn extract_authorization_metadata( &mut self, - schema: &apollo_compiler::Schema, + schema: &Valid, key: &CacheKeyMetadata, ) { if let Some(node) = self.node.as_mut() { diff --git a/apollo-router/src/query_planner/fetch.rs b/apollo-router/src/query_planner/fetch.rs index 898da645ce..54fd6d5598 100644 --- a/apollo-router/src/query_planner/fetch.rs +++ b/apollo-router/src/query_planner/fetch.rs @@ -619,11 +619,11 @@ impl FetchNode { pub(crate) fn extract_authorization_metadata( &mut self, - schema: &apollo_compiler::Schema, + schema: &Valid, global_authorisation_cache_key: &CacheKeyMetadata, ) { let doc = ExecutableDocument::parse( - Valid::assume_valid_ref(schema), + schema, self.operation.as_serialized().to_string(), "query.graphql", ) diff --git a/apollo-router/src/query_planner/plan.rs b/apollo-router/src/query_planner/plan.rs index fcfe77014e..8910e54558 100644 --- a/apollo-router/src/query_planner/plan.rs +++ b/apollo-router/src/query_planner/plan.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use apollo_compiler::validation::Valid; use apollo_compiler::NodeStr; use router_bridge::planner::PlanOptions; use router_bridge::planner::UsageReporting; @@ -412,7 +413,7 @@ impl PlanNode { pub(crate) fn extract_authorization_metadata( &mut self, - schema: &apollo_compiler::Schema, + schema: &Valid, key: &CacheKeyMetadata, ) { match self { From a482b53d27b55ddb0798b3b9332ca024d2764926 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 29 Apr 2024 11:40:00 -0500 Subject: [PATCH 4/7] Create changeset --- .changesets/fix_tninesling_undo_auth_changes.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changesets/fix_tninesling_undo_auth_changes.md diff --git a/.changesets/fix_tninesling_undo_auth_changes.md b/.changesets/fix_tninesling_undo_auth_changes.md new file mode 100644 index 0000000000..df02b9a084 --- /dev/null +++ b/.changesets/fix_tninesling_undo_auth_changes.md @@ -0,0 +1,5 @@ +### Use supergraph schema to extract auth info ([PR #5047](https://github.com/apollographql/router/pull/5047)) + +Use supergraph schema to extract auth info as auth information may not be available on the query planner's subgraph schemas. This undoes the auth changes made in #4975. + +By [@tninesling](https://github.com/tninesling) in https://github.com/apollographql/router/pull/5047 From 36bb5b9949735852c63aed766b0d2d1f4817e662 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 30 Apr 2024 15:43:37 +0200 Subject: [PATCH 5/7] add a test to guarantee correct CacheKeyMetadata generation (#5050) *Description here* Fixes #**issue_number** --- **Checklist** Complete the checklist (and note appropriate exceptions) before the PR is marked ready-for-review. - [ ] Changes are compatible[^1] - [ ] Documentation[^2] completed - [ ] Performance impact assessed and acceptable - Tests added and passing[^3] - [ ] Unit Tests - [ ] Integration Tests - [ ] Manual Tests **Exceptions** *Note any exceptions here* **Notes** [^1]: It may be appropriate to bring upcoming changes to the attention of other (impacted) groups. Please endeavour to do this before seeking PR approval. The mechanism for doing this will vary considerably, so use your judgement as to how and when to do this. [^2]: Configuration is an important part of many changes. Where applicable please try to document configuration examples. [^3]: Tick whichever testing boxes are applicable. If you are adding Manual Tests, please document the manual testing (extensively) in the Exceptions. --- ...horization__tests__cache_key_metadata.snap | 25 ++++ .../src/plugins/authorization/tests.rs | 140 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap diff --git a/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap new file mode 100644 index 0000000000..4c382bd481 --- /dev/null +++ b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap @@ -0,0 +1,25 @@ +--- +source: apollo-router/src/plugins/authorization/tests.rs +expression: response +--- +{ + "data": { + "currentUser": { + "id": 1, + "name": null, + "phone": "1234" + } + }, + "errors": [ + { + "message": "Unauthorized field or type", + "path": [ + "currentUser", + "name" + ], + "extensions": { + "code": "UNAUTHORIZED_FIELD_OR_TYPE" + } + } + ] +} diff --git a/apollo-router/src/plugins/authorization/tests.rs b/apollo-router/src/plugins/authorization/tests.rs index ed3f6bb585..80c7ca4032 100644 --- a/apollo-router/src/plugins/authorization/tests.rs +++ b/apollo-router/src/plugins/authorization/tests.rs @@ -6,7 +6,10 @@ use tower::ServiceExt; use crate::graphql; use crate::plugin::test::MockSubgraph; +use crate::plugin::test::MockSubgraphService; +use crate::plugins::authorization::CacheKeyMetadata; use crate::services::router; +use crate::services::subgraph; use crate::services::supergraph; use crate::Context; use crate::MockedSubgraphs; @@ -1015,3 +1018,140 @@ async fn errors_in_extensions() { insta::assert_json_snapshot!(response); } + +const CACHE_KEY_SCHEMA: &str = r#"schema +@link(url: "https://specs.apollo.dev/link/v1.0") +@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) +@link(url: "https://specs.apollo.dev/authenticated/v0.1", for: SECURITY) +@link(url: "https://specs.apollo.dev/requiresScopes/v0.1", for: SECURITY) +@link(url: "https://specs.apollo.dev/policy/v0.1", for: SECURITY) + +{ +query: Query +} +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION +directive @join__graph(name: String!, url: String!) on ENUM_VALUE +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +scalar link__Import +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +directive @authenticated on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM +scalar federation__Scope +directive @requiresScopes(scopes: [[federation__Scope!]!]!) on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM +directive @policy(policies: [[String!]!]!) on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM + +scalar join__FieldSet +enum join__Graph { + USER @join__graph(name: "user", url: "http://localhost:4001/graphql") + ORGA @join__graph(name: "orga", url: "http://localhost:4002/graphql") +} + +type Query +@join__type(graph: ORGA) +@join__type(graph: USER){ + currentUser: User @join__field(graph: USER) + orga(id: ID): Organization @join__field(graph: ORGA) +} +type User +@join__type(graph: ORGA, key: "id") +@join__type(graph: USER, key: "id"){ + id: ID! @requiresScopes(scopes: [["id"]]) + name: String @policy(policies: [["name"]]) + phone: String @authenticated + activeOrganization: Organization +} +type Organization +@join__type(graph: ORGA, key: "id") +@join__type(graph: USER, key: "id") { + id: ID @authenticated + creatorUser: User + name: String + nonNullId: ID! + suborga: [Organization] +}"#; + +#[tokio::test] +async fn cache_key_metadata() { + let query = "query { currentUser { id name phone } }"; + + let service = TestHarness::builder() + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { + "all": true + }, + "authorization": { + "directives": { + "enabled": true + } + } + })) + .unwrap() + .schema(CACHE_KEY_SCHEMA) + .subgraph_hook(|_name, _service| { + println!("calling service named: {_name:?}"); + let mut mock_subgraph_service = MockSubgraphService::new(); + mock_subgraph_service.expect_call().times(1).returning( + move |req: subgraph::Request| { + assert_eq!( + *req.authorization, + CacheKeyMetadata { + is_authenticated: true, + scopes: vec!["id".to_string()], + policies: vec!["profile".to_string()] + } + ); + + Ok(subgraph::Response::fake_builder() + .context(req.context) + .data(serde_json::json! {{ + + "currentUser": { + "id": 1, + "name": "A", + "phone": "1234" + } + + }}) + .build()) + }, + ); + mock_subgraph_service.boxed() + }) + // .extra_plugin(AuthzCheckPlugin) + .build_supergraph() + .await + .unwrap(); + + let context = Context::new(); + context + .insert( + "apollo_authentication::JWT::claims", + json! {{ "scope": "id test" }}, + ) + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(query) + .context(context) + .build() + .unwrap(); + let mut response = service.oneshot(request).await.unwrap(); + let response = response.next_response().await.unwrap(); + + insta::assert_json_snapshot!(response); +} From 0f2bc857fc0a3740a1d006aa378d262f10199d2b Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Tue, 30 Apr 2024 12:46:38 -0500 Subject: [PATCH 6/7] Fix auth test by wiring up QueryAnalysisLayer, which properly populates scopes and policies in the context. Also, tweak how policies are recorded because they were all being ignored when mapped to None. --- apollo-router/src/plugins/authorization/mod.rs | 6 ++++-- ...authorization__tests__cache_key_metadata.snap | 16 ++-------------- apollo-router/src/plugins/authorization/tests.rs | 13 ++++++++----- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apollo-router/src/plugins/authorization/mod.rs b/apollo-router/src/plugins/authorization/mod.rs index f3cb7431f4..d29119a16c 100644 --- a/apollo-router/src/plugins/authorization/mod.rs +++ b/apollo-router/src/plugins/authorization/mod.rs @@ -199,8 +199,10 @@ impl AuthorizationPlugin { } if !policies.is_empty() { - let policies: HashMap> = - policies.into_iter().map(|policy| (policy, None)).collect(); + let policies: HashMap> = policies + .into_iter() + .map(|policy| (policy, Some(true))) + .collect(); context.insert(REQUIRED_POLICIES_KEY, policies).unwrap(); } } diff --git a/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap index 4c382bd481..7a664e2f8a 100644 --- a/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap +++ b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap @@ -6,20 +6,8 @@ expression: response "data": { "currentUser": { "id": 1, - "name": null, + "name": "A", "phone": "1234" } - }, - "errors": [ - { - "message": "Unauthorized field or type", - "path": [ - "currentUser", - "name" - ], - "extensions": { - "code": "UNAUTHORIZED_FIELD_OR_TYPE" - } - } - ] + } } diff --git a/apollo-router/src/plugins/authorization/tests.rs b/apollo-router/src/plugins/authorization/tests.rs index 80c7ca4032..3562f55fc3 100644 --- a/apollo-router/src/plugins/authorization/tests.rs +++ b/apollo-router/src/plugins/authorization/tests.rs @@ -1112,7 +1112,7 @@ async fn cache_key_metadata() { CacheKeyMetadata { is_authenticated: true, scopes: vec!["id".to_string()], - policies: vec!["profile".to_string()] + policies: vec!["name".to_string()] } ); @@ -1132,8 +1132,7 @@ async fn cache_key_metadata() { ); mock_subgraph_service.boxed() }) - // .extra_plugin(AuthzCheckPlugin) - .build_supergraph() + .build_router() .await .unwrap(); @@ -1150,8 +1149,12 @@ async fn cache_key_metadata() { .context(context) .build() .unwrap(); - let mut response = service.oneshot(request).await.unwrap(); - let response = response.next_response().await.unwrap(); + let mut response = service + .oneshot(router::Request::try_from(request).unwrap()) + .await + .unwrap(); + let response = response.next_response().await.unwrap().unwrap(); + let response: serde_json::Value = serde_json::from_slice(&response).unwrap(); insta::assert_json_snapshot!(response); } From 503ae06e3038be0446553789292b0ff60ee294d0 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Tue, 30 Apr 2024 13:56:52 -0500 Subject: [PATCH 7/7] Undo the change to how policies are rolled up into context because coprocessors are meant to set policies to true, where applicable --- apollo-router/src/plugins/authorization/mod.rs | 6 ++---- ...authorization__tests__cache_key_metadata.snap | 16 ++++++++++++++-- apollo-router/src/plugins/authorization/tests.rs | 5 ++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apollo-router/src/plugins/authorization/mod.rs b/apollo-router/src/plugins/authorization/mod.rs index d29119a16c..f3cb7431f4 100644 --- a/apollo-router/src/plugins/authorization/mod.rs +++ b/apollo-router/src/plugins/authorization/mod.rs @@ -199,10 +199,8 @@ impl AuthorizationPlugin { } if !policies.is_empty() { - let policies: HashMap> = policies - .into_iter() - .map(|policy| (policy, Some(true))) - .collect(); + let policies: HashMap> = + policies.into_iter().map(|policy| (policy, None)).collect(); context.insert(REQUIRED_POLICIES_KEY, policies).unwrap(); } } diff --git a/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap index 7a664e2f8a..4c382bd481 100644 --- a/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap +++ b/apollo-router/src/plugins/authorization/snapshots/apollo_router__plugins__authorization__tests__cache_key_metadata.snap @@ -6,8 +6,20 @@ expression: response "data": { "currentUser": { "id": 1, - "name": "A", + "name": null, "phone": "1234" } - } + }, + "errors": [ + { + "message": "Unauthorized field or type", + "path": [ + "currentUser", + "name" + ], + "extensions": { + "code": "UNAUTHORIZED_FIELD_OR_TYPE" + } + } + ] } diff --git a/apollo-router/src/plugins/authorization/tests.rs b/apollo-router/src/plugins/authorization/tests.rs index 3562f55fc3..813533c184 100644 --- a/apollo-router/src/plugins/authorization/tests.rs +++ b/apollo-router/src/plugins/authorization/tests.rs @@ -1103,7 +1103,6 @@ async fn cache_key_metadata() { .unwrap() .schema(CACHE_KEY_SCHEMA) .subgraph_hook(|_name, _service| { - println!("calling service named: {_name:?}"); let mut mock_subgraph_service = MockSubgraphService::new(); mock_subgraph_service.expect_call().times(1).returning( move |req: subgraph::Request| { @@ -1112,7 +1111,7 @@ async fn cache_key_metadata() { CacheKeyMetadata { is_authenticated: true, scopes: vec!["id".to_string()], - policies: vec!["name".to_string()] + policies: vec![] } ); @@ -1122,7 +1121,7 @@ async fn cache_key_metadata() { "currentUser": { "id": 1, - "name": "A", + "name": "A", // This will be filtered because we don't have the policy "phone": "1234" }